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(server_id) => {
1837 editor.refresh_inlay_hints(
1838 InlayHintRefreshReason::RefreshRequested(*server_id),
1839 cx,
1840 );
1841 }
1842 project::Event::LanguageServerRemoved(..) => {
1843 if editor.tasks_update_task.is_none() {
1844 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1845 }
1846 editor.registered_buffers.clear();
1847 editor.register_visible_buffers(cx);
1848 }
1849 project::Event::LanguageServerAdded(..) => {
1850 if editor.tasks_update_task.is_none() {
1851 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1852 }
1853 }
1854 project::Event::SnippetEdit(id, snippet_edits) => {
1855 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1856 let focus_handle = editor.focus_handle(cx);
1857 if focus_handle.is_focused(window) {
1858 let snapshot = buffer.read(cx).snapshot();
1859 for (range, snippet) in snippet_edits {
1860 let editor_range =
1861 language::range_from_lsp(*range).to_offset(&snapshot);
1862 editor
1863 .insert_snippet(
1864 &[editor_range],
1865 snippet.clone(),
1866 window,
1867 cx,
1868 )
1869 .ok();
1870 }
1871 }
1872 }
1873 }
1874 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1875 let buffer_id = *buffer_id;
1876 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1877 editor.register_buffer(buffer_id, cx);
1878 editor.update_lsp_data(Some(buffer_id), window, cx);
1879 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1880 refresh_linked_ranges(editor, window, cx);
1881 editor.refresh_code_actions(window, cx);
1882 editor.refresh_document_highlights(cx);
1883 }
1884 }
1885
1886 project::Event::EntryRenamed(transaction) => {
1887 let Some(workspace) = editor.workspace() else {
1888 return;
1889 };
1890 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1891 else {
1892 return;
1893 };
1894 if active_editor.entity_id() == cx.entity_id() {
1895 let edited_buffers_already_open = {
1896 let other_editors: Vec<Entity<Editor>> = workspace
1897 .read(cx)
1898 .panes()
1899 .iter()
1900 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1901 .filter(|editor| editor.entity_id() != cx.entity_id())
1902 .collect();
1903
1904 transaction.0.keys().all(|buffer| {
1905 other_editors.iter().any(|editor| {
1906 let multi_buffer = editor.read(cx).buffer();
1907 multi_buffer.read(cx).is_singleton()
1908 && multi_buffer.read(cx).as_singleton().map_or(
1909 false,
1910 |singleton| {
1911 singleton.entity_id() == buffer.entity_id()
1912 },
1913 )
1914 })
1915 })
1916 };
1917
1918 if !edited_buffers_already_open {
1919 let workspace = workspace.downgrade();
1920 let transaction = transaction.clone();
1921 cx.defer_in(window, move |_, window, cx| {
1922 cx.spawn_in(window, async move |editor, cx| {
1923 Self::open_project_transaction(
1924 &editor,
1925 workspace,
1926 transaction,
1927 "Rename".to_string(),
1928 cx,
1929 )
1930 .await
1931 .ok()
1932 })
1933 .detach();
1934 });
1935 }
1936 }
1937 }
1938
1939 _ => {}
1940 },
1941 ));
1942 if let Some(task_inventory) = project
1943 .read(cx)
1944 .task_store()
1945 .read(cx)
1946 .task_inventory()
1947 .cloned()
1948 {
1949 project_subscriptions.push(cx.observe_in(
1950 &task_inventory,
1951 window,
1952 |editor, _, window, cx| {
1953 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1954 },
1955 ));
1956 };
1957
1958 project_subscriptions.push(cx.subscribe_in(
1959 &project.read(cx).breakpoint_store(),
1960 window,
1961 |editor, _, event, window, cx| match event {
1962 BreakpointStoreEvent::ClearDebugLines => {
1963 editor.clear_row_highlights::<ActiveDebugLine>();
1964 editor.refresh_inline_values(cx);
1965 }
1966 BreakpointStoreEvent::SetDebugLine => {
1967 if editor.go_to_active_debug_line(window, cx) {
1968 cx.stop_propagation();
1969 }
1970
1971 editor.refresh_inline_values(cx);
1972 }
1973 _ => {}
1974 },
1975 ));
1976 let git_store = project.read(cx).git_store().clone();
1977 let project = project.clone();
1978 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1979 if let GitStoreEvent::RepositoryAdded = event {
1980 this.load_diff_task = Some(
1981 update_uncommitted_diff_for_buffer(
1982 cx.entity(),
1983 &project,
1984 this.buffer.read(cx).all_buffers(),
1985 this.buffer.clone(),
1986 cx,
1987 )
1988 .shared(),
1989 );
1990 }
1991 }));
1992 }
1993
1994 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1995
1996 let inlay_hint_settings =
1997 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1998 let focus_handle = cx.focus_handle();
1999 if !is_minimap {
2000 cx.on_focus(&focus_handle, window, Self::handle_focus)
2001 .detach();
2002 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2003 .detach();
2004 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2005 .detach();
2006 cx.on_blur(&focus_handle, window, Self::handle_blur)
2007 .detach();
2008 cx.observe_pending_input(window, Self::observe_pending_input)
2009 .detach();
2010 }
2011
2012 let show_indent_guides =
2013 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2014 Some(false)
2015 } else {
2016 None
2017 };
2018
2019 let breakpoint_store = match (&mode, project.as_ref()) {
2020 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2021 _ => None,
2022 };
2023
2024 let mut code_action_providers = Vec::new();
2025 let mut load_uncommitted_diff = None;
2026 if let Some(project) = project.clone() {
2027 load_uncommitted_diff = Some(
2028 update_uncommitted_diff_for_buffer(
2029 cx.entity(),
2030 &project,
2031 multi_buffer.read(cx).all_buffers(),
2032 multi_buffer.clone(),
2033 cx,
2034 )
2035 .shared(),
2036 );
2037 code_action_providers.push(Rc::new(project) as Rc<_>);
2038 }
2039
2040 let mut editor = Self {
2041 focus_handle,
2042 show_cursor_when_unfocused: false,
2043 last_focused_descendant: None,
2044 buffer: multi_buffer.clone(),
2045 display_map: display_map.clone(),
2046 placeholder_display_map: None,
2047 selections,
2048 scroll_manager: ScrollManager::new(cx),
2049 columnar_selection_state: None,
2050 add_selections_state: None,
2051 select_next_state: None,
2052 select_prev_state: None,
2053 selection_history: SelectionHistory::default(),
2054 defer_selection_effects: false,
2055 deferred_selection_effects_state: None,
2056 autoclose_regions: Vec::new(),
2057 snippet_stack: InvalidationStack::default(),
2058 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2059 ime_transaction: None,
2060 active_diagnostics: ActiveDiagnostic::None,
2061 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2062 inline_diagnostics_update: Task::ready(()),
2063 inline_diagnostics: Vec::new(),
2064 soft_wrap_mode_override,
2065 diagnostics_max_severity,
2066 hard_wrap: None,
2067 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2068 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2069 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2070 project,
2071 blink_manager: blink_manager.clone(),
2072 show_local_selections: true,
2073 show_scrollbars: ScrollbarAxes {
2074 horizontal: full_mode,
2075 vertical: full_mode,
2076 },
2077 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2078 offset_content: !matches!(mode, EditorMode::SingleLine),
2079 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2080 show_gutter: full_mode,
2081 show_line_numbers: (!full_mode).then_some(false),
2082 use_relative_line_numbers: None,
2083 disable_expand_excerpt_buttons: !full_mode,
2084 show_git_diff_gutter: None,
2085 show_code_actions: None,
2086 show_runnables: None,
2087 show_breakpoints: None,
2088 show_wrap_guides: None,
2089 show_indent_guides,
2090 highlight_order: 0,
2091 highlighted_rows: HashMap::default(),
2092 background_highlights: HashMap::default(),
2093 gutter_highlights: HashMap::default(),
2094 scrollbar_marker_state: ScrollbarMarkerState::default(),
2095 active_indent_guides_state: ActiveIndentGuidesState::default(),
2096 nav_history: None,
2097 context_menu: RefCell::new(None),
2098 context_menu_options: None,
2099 mouse_context_menu: None,
2100 completion_tasks: Vec::new(),
2101 inline_blame_popover: None,
2102 inline_blame_popover_show_task: None,
2103 signature_help_state: SignatureHelpState::default(),
2104 auto_signature_help: None,
2105 find_all_references_task_sources: Vec::new(),
2106 next_completion_id: 0,
2107 next_inlay_id: 0,
2108 code_action_providers,
2109 available_code_actions: None,
2110 code_actions_task: None,
2111 quick_selection_highlight_task: None,
2112 debounced_selection_highlight_task: None,
2113 document_highlights_task: None,
2114 linked_editing_range_task: None,
2115 pending_rename: None,
2116 searchable: !is_minimap,
2117 cursor_shape: EditorSettings::get_global(cx)
2118 .cursor_shape
2119 .unwrap_or_default(),
2120 current_line_highlight: None,
2121 autoindent_mode: Some(AutoindentMode::EachLine),
2122 collapse_matches: false,
2123 workspace: None,
2124 input_enabled: !is_minimap,
2125 use_modal_editing: full_mode,
2126 read_only: is_minimap,
2127 use_autoclose: true,
2128 use_auto_surround: true,
2129 auto_replace_emoji_shortcode: false,
2130 jsx_tag_auto_close_enabled_in_any_buffer: false,
2131 leader_id: None,
2132 remote_id: None,
2133 hover_state: HoverState::default(),
2134 pending_mouse_down: None,
2135 hovered_link_state: None,
2136 edit_prediction_provider: None,
2137 active_edit_prediction: None,
2138 stale_edit_prediction_in_menu: None,
2139 edit_prediction_preview: EditPredictionPreview::Inactive {
2140 released_too_fast: false,
2141 },
2142 inline_diagnostics_enabled: full_mode,
2143 diagnostics_enabled: full_mode,
2144 word_completions_enabled: full_mode,
2145 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2146 gutter_hovered: false,
2147 pixel_position_of_newest_cursor: None,
2148 last_bounds: None,
2149 last_position_map: None,
2150 expect_bounds_change: None,
2151 gutter_dimensions: GutterDimensions::default(),
2152 style: None,
2153 show_cursor_names: false,
2154 hovered_cursors: HashMap::default(),
2155 next_editor_action_id: EditorActionId::default(),
2156 editor_actions: Rc::default(),
2157 edit_predictions_hidden_for_vim_mode: false,
2158 show_edit_predictions_override: None,
2159 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2160 edit_prediction_settings: EditPredictionSettings::Disabled,
2161 edit_prediction_indent_conflict: false,
2162 edit_prediction_requires_modifier_in_indent_conflict: true,
2163 custom_context_menu: None,
2164 show_git_blame_gutter: false,
2165 show_git_blame_inline: false,
2166 show_selection_menu: None,
2167 show_git_blame_inline_delay_task: None,
2168 git_blame_inline_enabled: full_mode
2169 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2170 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2171 serialize_dirty_buffers: !is_minimap
2172 && ProjectSettings::get_global(cx)
2173 .session
2174 .restore_unsaved_buffers,
2175 blame: None,
2176 blame_subscription: None,
2177 tasks: BTreeMap::default(),
2178
2179 breakpoint_store,
2180 gutter_breakpoint_indicator: (None, None),
2181 hovered_diff_hunk_row: None,
2182 _subscriptions: (!is_minimap)
2183 .then(|| {
2184 vec![
2185 cx.observe(&multi_buffer, Self::on_buffer_changed),
2186 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2187 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2188 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2189 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2190 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2191 cx.observe_window_activation(window, |editor, window, cx| {
2192 let active = window.is_window_active();
2193 editor.blink_manager.update(cx, |blink_manager, cx| {
2194 if active {
2195 blink_manager.enable(cx);
2196 } else {
2197 blink_manager.disable(cx);
2198 }
2199 });
2200 if active {
2201 editor.show_mouse_cursor(cx);
2202 }
2203 }),
2204 ]
2205 })
2206 .unwrap_or_default(),
2207 tasks_update_task: None,
2208 pull_diagnostics_task: Task::ready(()),
2209 colors: None,
2210 refresh_colors_task: Task::ready(()),
2211 inlay_hints: None,
2212 next_color_inlay_id: 0,
2213 post_scroll_update: Task::ready(()),
2214 linked_edit_ranges: Default::default(),
2215 in_project_search: false,
2216 previous_search_ranges: None,
2217 breadcrumb_header: None,
2218 focused_block: None,
2219 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2220 addons: HashMap::default(),
2221 registered_buffers: HashMap::default(),
2222 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2223 selection_mark_mode: false,
2224 toggle_fold_multiple_buffers: Task::ready(()),
2225 serialize_selections: Task::ready(()),
2226 serialize_folds: Task::ready(()),
2227 text_style_refinement: None,
2228 load_diff_task: load_uncommitted_diff,
2229 temporary_diff_override: false,
2230 mouse_cursor_hidden: false,
2231 minimap: None,
2232 hide_mouse_mode: EditorSettings::get_global(cx)
2233 .hide_mouse
2234 .unwrap_or_default(),
2235 change_list: ChangeList::new(),
2236 mode,
2237 selection_drag_state: SelectionDragState::None,
2238 folding_newlines: Task::ready(()),
2239 lookup_key: None,
2240 };
2241
2242 if is_minimap {
2243 return editor;
2244 }
2245
2246 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2247 editor
2248 ._subscriptions
2249 .push(cx.observe(breakpoints, |_, _, cx| {
2250 cx.notify();
2251 }));
2252 }
2253 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2254 editor._subscriptions.extend(project_subscriptions);
2255
2256 editor._subscriptions.push(cx.subscribe_in(
2257 &cx.entity(),
2258 window,
2259 |editor, _, e: &EditorEvent, window, cx| match e {
2260 EditorEvent::ScrollPositionChanged { local, .. } => {
2261 if *local {
2262 let new_anchor = editor.scroll_manager.anchor();
2263 let snapshot = editor.snapshot(window, cx);
2264 editor.update_restoration_data(cx, move |data| {
2265 data.scroll_position = (
2266 new_anchor.top_row(snapshot.buffer_snapshot()),
2267 new_anchor.offset,
2268 );
2269 });
2270 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2271 editor.inline_blame_popover.take();
2272 }
2273 }
2274 EditorEvent::Edited { .. } => {
2275 if !vim_enabled(cx) {
2276 let display_map = editor.display_snapshot(cx);
2277 let selections = editor.selections.all_adjusted_display(&display_map);
2278 let pop_state = editor
2279 .change_list
2280 .last()
2281 .map(|previous| {
2282 previous.len() == selections.len()
2283 && previous.iter().enumerate().all(|(ix, p)| {
2284 p.to_display_point(&display_map).row()
2285 == selections[ix].head().row()
2286 })
2287 })
2288 .unwrap_or(false);
2289 let new_positions = selections
2290 .into_iter()
2291 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2292 .collect();
2293 editor
2294 .change_list
2295 .push_to_change_list(pop_state, new_positions);
2296 }
2297 }
2298 _ => (),
2299 },
2300 ));
2301
2302 if let Some(dap_store) = editor
2303 .project
2304 .as_ref()
2305 .map(|project| project.read(cx).dap_store())
2306 {
2307 let weak_editor = cx.weak_entity();
2308
2309 editor
2310 ._subscriptions
2311 .push(
2312 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2313 let session_entity = cx.entity();
2314 weak_editor
2315 .update(cx, |editor, cx| {
2316 editor._subscriptions.push(
2317 cx.subscribe(&session_entity, Self::on_debug_session_event),
2318 );
2319 })
2320 .ok();
2321 }),
2322 );
2323
2324 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2325 editor
2326 ._subscriptions
2327 .push(cx.subscribe(&session, Self::on_debug_session_event));
2328 }
2329 }
2330
2331 // skip adding the initial selection to selection history
2332 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2333 editor.end_selection(window, cx);
2334 editor.selection_history.mode = SelectionHistoryMode::Normal;
2335
2336 editor.scroll_manager.show_scrollbars(window, cx);
2337 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2338
2339 if full_mode {
2340 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2341 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2342
2343 if editor.git_blame_inline_enabled {
2344 editor.start_git_blame_inline(false, window, cx);
2345 }
2346
2347 editor.go_to_active_debug_line(window, cx);
2348
2349 editor.minimap =
2350 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2351 editor.colors = Some(LspColorData::new(cx));
2352 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2353
2354 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2355 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2356 }
2357 editor.update_lsp_data(None, window, cx);
2358 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2359 }
2360
2361 editor
2362 }
2363
2364 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2365 self.selections.display_map(cx)
2366 }
2367
2368 pub fn deploy_mouse_context_menu(
2369 &mut self,
2370 position: gpui::Point<Pixels>,
2371 context_menu: Entity<ContextMenu>,
2372 window: &mut Window,
2373 cx: &mut Context<Self>,
2374 ) {
2375 self.mouse_context_menu = Some(MouseContextMenu::new(
2376 self,
2377 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2378 context_menu,
2379 window,
2380 cx,
2381 ));
2382 }
2383
2384 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2385 self.mouse_context_menu
2386 .as_ref()
2387 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2388 }
2389
2390 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2391 if self
2392 .selections
2393 .pending_anchor()
2394 .is_some_and(|pending_selection| {
2395 let snapshot = self.buffer().read(cx).snapshot(cx);
2396 pending_selection.range().includes(range, &snapshot)
2397 })
2398 {
2399 return true;
2400 }
2401
2402 self.selections
2403 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2404 .into_iter()
2405 .any(|selection| {
2406 // This is needed to cover a corner case, if we just check for an existing
2407 // selection in the fold range, having a cursor at the start of the fold
2408 // marks it as selected. Non-empty selections don't cause this.
2409 let length = selection.end - selection.start;
2410 length > 0
2411 })
2412 }
2413
2414 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2415 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2416 }
2417
2418 fn key_context_internal(
2419 &self,
2420 has_active_edit_prediction: bool,
2421 window: &mut Window,
2422 cx: &mut App,
2423 ) -> KeyContext {
2424 let mut key_context = KeyContext::new_with_defaults();
2425 key_context.add("Editor");
2426 let mode = match self.mode {
2427 EditorMode::SingleLine => "single_line",
2428 EditorMode::AutoHeight { .. } => "auto_height",
2429 EditorMode::Minimap { .. } => "minimap",
2430 EditorMode::Full { .. } => "full",
2431 };
2432
2433 if EditorSettings::jupyter_enabled(cx) {
2434 key_context.add("jupyter");
2435 }
2436
2437 key_context.set("mode", mode);
2438 if self.pending_rename.is_some() {
2439 key_context.add("renaming");
2440 }
2441
2442 match self.context_menu.borrow().as_ref() {
2443 Some(CodeContextMenu::Completions(menu)) => {
2444 if menu.visible() {
2445 key_context.add("menu");
2446 key_context.add("showing_completions");
2447 }
2448 }
2449 Some(CodeContextMenu::CodeActions(menu)) => {
2450 if menu.visible() {
2451 key_context.add("menu");
2452 key_context.add("showing_code_actions")
2453 }
2454 }
2455 None => {}
2456 }
2457
2458 if self.signature_help_state.has_multiple_signatures() {
2459 key_context.add("showing_signature_help");
2460 }
2461
2462 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2463 if !self.focus_handle(cx).contains_focused(window, cx)
2464 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2465 {
2466 for addon in self.addons.values() {
2467 addon.extend_key_context(&mut key_context, cx)
2468 }
2469 }
2470
2471 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2472 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2473 Some(
2474 file.full_path(cx)
2475 .extension()?
2476 .to_string_lossy()
2477 .into_owned(),
2478 )
2479 }) {
2480 key_context.set("extension", extension);
2481 }
2482 } else {
2483 key_context.add("multibuffer");
2484 }
2485
2486 if has_active_edit_prediction {
2487 if self.edit_prediction_in_conflict() {
2488 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2489 } else {
2490 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2491 key_context.add("copilot_suggestion");
2492 }
2493 }
2494
2495 if self.selection_mark_mode {
2496 key_context.add("selection_mode");
2497 }
2498
2499 let disjoint = self.selections.disjoint_anchors();
2500 let snapshot = self.snapshot(window, cx);
2501 let snapshot = snapshot.buffer_snapshot();
2502 if self.mode == EditorMode::SingleLine
2503 && let [selection] = disjoint
2504 && selection.start == selection.end
2505 && selection.end.to_offset(snapshot) == snapshot.len()
2506 {
2507 key_context.add("end_of_input");
2508 }
2509
2510 key_context
2511 }
2512
2513 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2514 self.last_bounds.as_ref()
2515 }
2516
2517 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2518 if self.mouse_cursor_hidden {
2519 self.mouse_cursor_hidden = false;
2520 cx.notify();
2521 }
2522 }
2523
2524 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2525 let hide_mouse_cursor = match origin {
2526 HideMouseCursorOrigin::TypingAction => {
2527 matches!(
2528 self.hide_mouse_mode,
2529 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2530 )
2531 }
2532 HideMouseCursorOrigin::MovementAction => {
2533 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2534 }
2535 };
2536 if self.mouse_cursor_hidden != hide_mouse_cursor {
2537 self.mouse_cursor_hidden = hide_mouse_cursor;
2538 cx.notify();
2539 }
2540 }
2541
2542 pub fn edit_prediction_in_conflict(&self) -> bool {
2543 if !self.show_edit_predictions_in_menu() {
2544 return false;
2545 }
2546
2547 let showing_completions = self
2548 .context_menu
2549 .borrow()
2550 .as_ref()
2551 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2552
2553 showing_completions
2554 || self.edit_prediction_requires_modifier()
2555 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2556 // bindings to insert tab characters.
2557 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2558 }
2559
2560 pub fn accept_edit_prediction_keybind(
2561 &self,
2562 accept_partial: bool,
2563 window: &mut Window,
2564 cx: &mut App,
2565 ) -> AcceptEditPredictionBinding {
2566 let key_context = self.key_context_internal(true, window, cx);
2567 let in_conflict = self.edit_prediction_in_conflict();
2568
2569 let bindings = if accept_partial {
2570 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2571 } else {
2572 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2573 };
2574
2575 // TODO: if the binding contains multiple keystrokes, display all of them, not
2576 // just the first one.
2577 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2578 !in_conflict
2579 || binding
2580 .keystrokes()
2581 .first()
2582 .is_some_and(|keystroke| keystroke.modifiers().modified())
2583 }))
2584 }
2585
2586 pub fn new_file(
2587 workspace: &mut Workspace,
2588 _: &workspace::NewFile,
2589 window: &mut Window,
2590 cx: &mut Context<Workspace>,
2591 ) {
2592 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2593 "Failed to create buffer",
2594 window,
2595 cx,
2596 |e, _, _| match e.error_code() {
2597 ErrorCode::RemoteUpgradeRequired => Some(format!(
2598 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2599 e.error_tag("required").unwrap_or("the latest version")
2600 )),
2601 _ => None,
2602 },
2603 );
2604 }
2605
2606 pub fn new_in_workspace(
2607 workspace: &mut Workspace,
2608 window: &mut Window,
2609 cx: &mut Context<Workspace>,
2610 ) -> Task<Result<Entity<Editor>>> {
2611 let project = workspace.project().clone();
2612 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2613
2614 cx.spawn_in(window, async move |workspace, cx| {
2615 let buffer = create.await?;
2616 workspace.update_in(cx, |workspace, window, cx| {
2617 let editor =
2618 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2619 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2620 editor
2621 })
2622 })
2623 }
2624
2625 fn new_file_vertical(
2626 workspace: &mut Workspace,
2627 _: &workspace::NewFileSplitVertical,
2628 window: &mut Window,
2629 cx: &mut Context<Workspace>,
2630 ) {
2631 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2632 }
2633
2634 fn new_file_horizontal(
2635 workspace: &mut Workspace,
2636 _: &workspace::NewFileSplitHorizontal,
2637 window: &mut Window,
2638 cx: &mut Context<Workspace>,
2639 ) {
2640 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2641 }
2642
2643 fn new_file_split(
2644 workspace: &mut Workspace,
2645 action: &workspace::NewFileSplit,
2646 window: &mut Window,
2647 cx: &mut Context<Workspace>,
2648 ) {
2649 Self::new_file_in_direction(workspace, action.0, window, cx)
2650 }
2651
2652 fn new_file_in_direction(
2653 workspace: &mut Workspace,
2654 direction: SplitDirection,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 let project = workspace.project().clone();
2659 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2660
2661 cx.spawn_in(window, async move |workspace, cx| {
2662 let buffer = create.await?;
2663 workspace.update_in(cx, move |workspace, window, cx| {
2664 workspace.split_item(
2665 direction,
2666 Box::new(
2667 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2668 ),
2669 window,
2670 cx,
2671 )
2672 })?;
2673 anyhow::Ok(())
2674 })
2675 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2676 match e.error_code() {
2677 ErrorCode::RemoteUpgradeRequired => Some(format!(
2678 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2679 e.error_tag("required").unwrap_or("the latest version")
2680 )),
2681 _ => None,
2682 }
2683 });
2684 }
2685
2686 pub fn leader_id(&self) -> Option<CollaboratorId> {
2687 self.leader_id
2688 }
2689
2690 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2691 &self.buffer
2692 }
2693
2694 pub fn project(&self) -> Option<&Entity<Project>> {
2695 self.project.as_ref()
2696 }
2697
2698 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2699 self.workspace.as_ref()?.0.upgrade()
2700 }
2701
2702 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2703 self.buffer().read(cx).title(cx)
2704 }
2705
2706 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2707 let git_blame_gutter_max_author_length = self
2708 .render_git_blame_gutter(cx)
2709 .then(|| {
2710 if let Some(blame) = self.blame.as_ref() {
2711 let max_author_length =
2712 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2713 Some(max_author_length)
2714 } else {
2715 None
2716 }
2717 })
2718 .flatten();
2719
2720 EditorSnapshot {
2721 mode: self.mode.clone(),
2722 show_gutter: self.show_gutter,
2723 show_line_numbers: self.show_line_numbers,
2724 show_git_diff_gutter: self.show_git_diff_gutter,
2725 show_code_actions: self.show_code_actions,
2726 show_runnables: self.show_runnables,
2727 show_breakpoints: self.show_breakpoints,
2728 git_blame_gutter_max_author_length,
2729 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2730 placeholder_display_snapshot: self
2731 .placeholder_display_map
2732 .as_ref()
2733 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2734 scroll_anchor: self.scroll_manager.anchor(),
2735 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2736 is_focused: self.focus_handle.is_focused(window),
2737 current_line_highlight: self
2738 .current_line_highlight
2739 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2740 gutter_hovered: self.gutter_hovered,
2741 }
2742 }
2743
2744 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2745 self.buffer.read(cx).language_at(point, cx)
2746 }
2747
2748 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2749 self.buffer.read(cx).read(cx).file_at(point).cloned()
2750 }
2751
2752 pub fn active_excerpt(
2753 &self,
2754 cx: &App,
2755 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2756 self.buffer
2757 .read(cx)
2758 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2759 }
2760
2761 pub fn mode(&self) -> &EditorMode {
2762 &self.mode
2763 }
2764
2765 pub fn set_mode(&mut self, mode: EditorMode) {
2766 self.mode = mode;
2767 }
2768
2769 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2770 self.collaboration_hub.as_deref()
2771 }
2772
2773 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2774 self.collaboration_hub = Some(hub);
2775 }
2776
2777 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2778 self.in_project_search = in_project_search;
2779 }
2780
2781 pub fn set_custom_context_menu(
2782 &mut self,
2783 f: impl 'static
2784 + Fn(
2785 &mut Self,
2786 DisplayPoint,
2787 &mut Window,
2788 &mut Context<Self>,
2789 ) -> Option<Entity<ui::ContextMenu>>,
2790 ) {
2791 self.custom_context_menu = Some(Box::new(f))
2792 }
2793
2794 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2795 self.completion_provider = provider;
2796 }
2797
2798 #[cfg(any(test, feature = "test-support"))]
2799 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2800 self.completion_provider.clone()
2801 }
2802
2803 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2804 self.semantics_provider.clone()
2805 }
2806
2807 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2808 self.semantics_provider = provider;
2809 }
2810
2811 pub fn set_edit_prediction_provider<T>(
2812 &mut self,
2813 provider: Option<Entity<T>>,
2814 window: &mut Window,
2815 cx: &mut Context<Self>,
2816 ) where
2817 T: EditPredictionProvider,
2818 {
2819 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2820 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2821 if this.focus_handle.is_focused(window) {
2822 this.update_visible_edit_prediction(window, cx);
2823 }
2824 }),
2825 provider: Arc::new(provider),
2826 });
2827 self.update_edit_prediction_settings(cx);
2828 self.refresh_edit_prediction(false, false, window, cx);
2829 }
2830
2831 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2832 self.placeholder_display_map
2833 .as_ref()
2834 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2835 }
2836
2837 pub fn set_placeholder_text(
2838 &mut self,
2839 placeholder_text: &str,
2840 window: &mut Window,
2841 cx: &mut Context<Self>,
2842 ) {
2843 let multibuffer = cx
2844 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2845
2846 let style = window.text_style();
2847
2848 self.placeholder_display_map = Some(cx.new(|cx| {
2849 DisplayMap::new(
2850 multibuffer,
2851 style.font(),
2852 style.font_size.to_pixels(window.rem_size()),
2853 None,
2854 FILE_HEADER_HEIGHT,
2855 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2856 Default::default(),
2857 DiagnosticSeverity::Off,
2858 cx,
2859 )
2860 }));
2861 cx.notify();
2862 }
2863
2864 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2865 self.cursor_shape = cursor_shape;
2866
2867 // Disrupt blink for immediate user feedback that the cursor shape has changed
2868 self.blink_manager.update(cx, BlinkManager::show_cursor);
2869
2870 cx.notify();
2871 }
2872
2873 pub fn set_current_line_highlight(
2874 &mut self,
2875 current_line_highlight: Option<CurrentLineHighlight>,
2876 ) {
2877 self.current_line_highlight = current_line_highlight;
2878 }
2879
2880 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2881 self.collapse_matches = collapse_matches;
2882 }
2883
2884 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2885 if self.collapse_matches {
2886 return range.start..range.start;
2887 }
2888 range.clone()
2889 }
2890
2891 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2892 if self.display_map.read(cx).clip_at_line_ends != clip {
2893 self.display_map
2894 .update(cx, |map, _| map.clip_at_line_ends = clip);
2895 }
2896 }
2897
2898 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2899 self.input_enabled = input_enabled;
2900 }
2901
2902 pub fn set_edit_predictions_hidden_for_vim_mode(
2903 &mut self,
2904 hidden: bool,
2905 window: &mut Window,
2906 cx: &mut Context<Self>,
2907 ) {
2908 if hidden != self.edit_predictions_hidden_for_vim_mode {
2909 self.edit_predictions_hidden_for_vim_mode = hidden;
2910 if hidden {
2911 self.update_visible_edit_prediction(window, cx);
2912 } else {
2913 self.refresh_edit_prediction(true, false, window, cx);
2914 }
2915 }
2916 }
2917
2918 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2919 self.menu_edit_predictions_policy = value;
2920 }
2921
2922 pub fn set_autoindent(&mut self, autoindent: bool) {
2923 if autoindent {
2924 self.autoindent_mode = Some(AutoindentMode::EachLine);
2925 } else {
2926 self.autoindent_mode = None;
2927 }
2928 }
2929
2930 pub fn read_only(&self, cx: &App) -> bool {
2931 self.read_only || self.buffer.read(cx).read_only()
2932 }
2933
2934 pub fn set_read_only(&mut self, read_only: bool) {
2935 self.read_only = read_only;
2936 }
2937
2938 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2939 self.use_autoclose = autoclose;
2940 }
2941
2942 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2943 self.use_auto_surround = auto_surround;
2944 }
2945
2946 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2947 self.auto_replace_emoji_shortcode = auto_replace;
2948 }
2949
2950 pub fn toggle_edit_predictions(
2951 &mut self,
2952 _: &ToggleEditPrediction,
2953 window: &mut Window,
2954 cx: &mut Context<Self>,
2955 ) {
2956 if self.show_edit_predictions_override.is_some() {
2957 self.set_show_edit_predictions(None, window, cx);
2958 } else {
2959 let show_edit_predictions = !self.edit_predictions_enabled();
2960 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2961 }
2962 }
2963
2964 pub fn set_show_edit_predictions(
2965 &mut self,
2966 show_edit_predictions: Option<bool>,
2967 window: &mut Window,
2968 cx: &mut Context<Self>,
2969 ) {
2970 self.show_edit_predictions_override = show_edit_predictions;
2971 self.update_edit_prediction_settings(cx);
2972
2973 if let Some(false) = show_edit_predictions {
2974 self.discard_edit_prediction(false, cx);
2975 } else {
2976 self.refresh_edit_prediction(false, true, window, cx);
2977 }
2978 }
2979
2980 fn edit_predictions_disabled_in_scope(
2981 &self,
2982 buffer: &Entity<Buffer>,
2983 buffer_position: language::Anchor,
2984 cx: &App,
2985 ) -> bool {
2986 let snapshot = buffer.read(cx).snapshot();
2987 let settings = snapshot.settings_at(buffer_position, cx);
2988
2989 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2990 return false;
2991 };
2992
2993 scope.override_name().is_some_and(|scope_name| {
2994 settings
2995 .edit_predictions_disabled_in
2996 .iter()
2997 .any(|s| s == scope_name)
2998 })
2999 }
3000
3001 pub fn set_use_modal_editing(&mut self, to: bool) {
3002 self.use_modal_editing = to;
3003 }
3004
3005 pub fn use_modal_editing(&self) -> bool {
3006 self.use_modal_editing
3007 }
3008
3009 fn selections_did_change(
3010 &mut self,
3011 local: bool,
3012 old_cursor_position: &Anchor,
3013 effects: SelectionEffects,
3014 window: &mut Window,
3015 cx: &mut Context<Self>,
3016 ) {
3017 window.invalidate_character_coordinates();
3018
3019 // Copy selections to primary selection buffer
3020 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3021 if local {
3022 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3023 let buffer_handle = self.buffer.read(cx).read(cx);
3024
3025 let mut text = String::new();
3026 for (index, selection) in selections.iter().enumerate() {
3027 let text_for_selection = buffer_handle
3028 .text_for_range(selection.start..selection.end)
3029 .collect::<String>();
3030
3031 text.push_str(&text_for_selection);
3032 if index != selections.len() - 1 {
3033 text.push('\n');
3034 }
3035 }
3036
3037 if !text.is_empty() {
3038 cx.write_to_primary(ClipboardItem::new_string(text));
3039 }
3040 }
3041
3042 let selection_anchors = self.selections.disjoint_anchors_arc();
3043
3044 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3045 self.buffer.update(cx, |buffer, cx| {
3046 buffer.set_active_selections(
3047 &selection_anchors,
3048 self.selections.line_mode(),
3049 self.cursor_shape,
3050 cx,
3051 )
3052 });
3053 }
3054 let display_map = self
3055 .display_map
3056 .update(cx, |display_map, cx| display_map.snapshot(cx));
3057 let buffer = display_map.buffer_snapshot();
3058 if self.selections.count() == 1 {
3059 self.add_selections_state = None;
3060 }
3061 self.select_next_state = None;
3062 self.select_prev_state = None;
3063 self.select_syntax_node_history.try_clear();
3064 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3065 self.snippet_stack.invalidate(&selection_anchors, buffer);
3066 self.take_rename(false, window, cx);
3067
3068 let newest_selection = self.selections.newest_anchor();
3069 let new_cursor_position = newest_selection.head();
3070 let selection_start = newest_selection.start;
3071
3072 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3073 self.push_to_nav_history(
3074 *old_cursor_position,
3075 Some(new_cursor_position.to_point(buffer)),
3076 false,
3077 effects.nav_history == Some(true),
3078 cx,
3079 );
3080 }
3081
3082 if local {
3083 if let Some(buffer_id) = new_cursor_position.buffer_id {
3084 self.register_buffer(buffer_id, cx);
3085 }
3086
3087 let mut context_menu = self.context_menu.borrow_mut();
3088 let completion_menu = match context_menu.as_ref() {
3089 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3090 Some(CodeContextMenu::CodeActions(_)) => {
3091 *context_menu = None;
3092 None
3093 }
3094 None => None,
3095 };
3096 let completion_position = completion_menu.map(|menu| menu.initial_position);
3097 drop(context_menu);
3098
3099 if effects.completions
3100 && let Some(completion_position) = completion_position
3101 {
3102 let start_offset = selection_start.to_offset(buffer);
3103 let position_matches = start_offset == completion_position.to_offset(buffer);
3104 let continue_showing = if position_matches {
3105 if self.snippet_stack.is_empty() {
3106 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3107 == Some(CharKind::Word)
3108 } else {
3109 // Snippet choices can be shown even when the cursor is in whitespace.
3110 // Dismissing the menu with actions like backspace is handled by
3111 // invalidation regions.
3112 true
3113 }
3114 } else {
3115 false
3116 };
3117
3118 if continue_showing {
3119 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3120 } else {
3121 self.hide_context_menu(window, cx);
3122 }
3123 }
3124
3125 hide_hover(self, cx);
3126
3127 if old_cursor_position.to_display_point(&display_map).row()
3128 != new_cursor_position.to_display_point(&display_map).row()
3129 {
3130 self.available_code_actions.take();
3131 }
3132 self.refresh_code_actions(window, cx);
3133 self.refresh_document_highlights(cx);
3134 refresh_linked_ranges(self, window, cx);
3135
3136 self.refresh_selected_text_highlights(false, window, cx);
3137 self.refresh_matching_bracket_highlights(window, cx);
3138 self.update_visible_edit_prediction(window, cx);
3139 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3140 self.inline_blame_popover.take();
3141 if self.git_blame_inline_enabled {
3142 self.start_inline_blame_timer(window, cx);
3143 }
3144 }
3145
3146 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3147 cx.emit(EditorEvent::SelectionsChanged { local });
3148
3149 let selections = &self.selections.disjoint_anchors_arc();
3150 if selections.len() == 1 {
3151 cx.emit(SearchEvent::ActiveMatchChanged)
3152 }
3153 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3154 let inmemory_selections = selections
3155 .iter()
3156 .map(|s| {
3157 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3158 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3159 })
3160 .collect();
3161 self.update_restoration_data(cx, |data| {
3162 data.selections = inmemory_selections;
3163 });
3164
3165 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3166 && let Some(workspace_id) =
3167 self.workspace.as_ref().and_then(|workspace| workspace.1)
3168 {
3169 let snapshot = self.buffer().read(cx).snapshot(cx);
3170 let selections = selections.clone();
3171 let background_executor = cx.background_executor().clone();
3172 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3173 self.serialize_selections = cx.background_spawn(async move {
3174 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3175 let db_selections = selections
3176 .iter()
3177 .map(|selection| {
3178 (
3179 selection.start.to_offset(&snapshot),
3180 selection.end.to_offset(&snapshot),
3181 )
3182 })
3183 .collect();
3184
3185 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3186 .await
3187 .with_context(|| {
3188 format!(
3189 "persisting editor selections for editor {editor_id}, \
3190 workspace {workspace_id:?}"
3191 )
3192 })
3193 .log_err();
3194 });
3195 }
3196 }
3197
3198 cx.notify();
3199 }
3200
3201 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3202 use text::ToOffset as _;
3203 use text::ToPoint as _;
3204
3205 if self.mode.is_minimap()
3206 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3207 {
3208 return;
3209 }
3210
3211 if !self.buffer().read(cx).is_singleton() {
3212 return;
3213 }
3214
3215 let display_snapshot = self
3216 .display_map
3217 .update(cx, |display_map, cx| display_map.snapshot(cx));
3218 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3219 return;
3220 };
3221 let inmemory_folds = display_snapshot
3222 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3223 .map(|fold| {
3224 fold.range.start.text_anchor.to_point(&snapshot)
3225 ..fold.range.end.text_anchor.to_point(&snapshot)
3226 })
3227 .collect();
3228 self.update_restoration_data(cx, |data| {
3229 data.folds = inmemory_folds;
3230 });
3231
3232 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3233 return;
3234 };
3235 let background_executor = cx.background_executor().clone();
3236 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3237 let db_folds = display_snapshot
3238 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3239 .map(|fold| {
3240 (
3241 fold.range.start.text_anchor.to_offset(&snapshot),
3242 fold.range.end.text_anchor.to_offset(&snapshot),
3243 )
3244 })
3245 .collect();
3246 self.serialize_folds = cx.background_spawn(async move {
3247 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3248 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3249 .await
3250 .with_context(|| {
3251 format!(
3252 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3253 )
3254 })
3255 .log_err();
3256 });
3257 }
3258
3259 pub fn sync_selections(
3260 &mut self,
3261 other: Entity<Editor>,
3262 cx: &mut Context<Self>,
3263 ) -> gpui::Subscription {
3264 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3265 if !other_selections.is_empty() {
3266 self.selections.change_with(cx, |selections| {
3267 selections.select_anchors(other_selections);
3268 });
3269 }
3270
3271 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3272 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3273 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3274 if other_selections.is_empty() {
3275 return;
3276 }
3277 this.selections.change_with(cx, |selections| {
3278 selections.select_anchors(other_selections);
3279 });
3280 }
3281 });
3282
3283 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3284 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3285 let these_selections = this.selections.disjoint_anchors().to_vec();
3286 if these_selections.is_empty() {
3287 return;
3288 }
3289 other.update(cx, |other_editor, cx| {
3290 other_editor.selections.change_with(cx, |selections| {
3291 selections.select_anchors(these_selections);
3292 })
3293 });
3294 }
3295 });
3296
3297 Subscription::join(other_subscription, this_subscription)
3298 }
3299
3300 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3301 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3302 /// effects of selection change occur at the end of the transaction.
3303 pub fn change_selections<R>(
3304 &mut self,
3305 effects: SelectionEffects,
3306 window: &mut Window,
3307 cx: &mut Context<Self>,
3308 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3309 ) -> R {
3310 if let Some(state) = &mut self.deferred_selection_effects_state {
3311 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3312 state.effects.completions = effects.completions;
3313 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3314 let (changed, result) = self.selections.change_with(cx, change);
3315 state.changed |= changed;
3316 return result;
3317 }
3318 let mut state = DeferredSelectionEffectsState {
3319 changed: false,
3320 effects,
3321 old_cursor_position: self.selections.newest_anchor().head(),
3322 history_entry: SelectionHistoryEntry {
3323 selections: self.selections.disjoint_anchors_arc(),
3324 select_next_state: self.select_next_state.clone(),
3325 select_prev_state: self.select_prev_state.clone(),
3326 add_selections_state: self.add_selections_state.clone(),
3327 },
3328 };
3329 let (changed, result) = self.selections.change_with(cx, change);
3330 state.changed = state.changed || changed;
3331 if self.defer_selection_effects {
3332 self.deferred_selection_effects_state = Some(state);
3333 } else {
3334 self.apply_selection_effects(state, window, cx);
3335 }
3336 result
3337 }
3338
3339 /// Defers the effects of selection change, so that the effects of multiple calls to
3340 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3341 /// to selection history and the state of popovers based on selection position aren't
3342 /// erroneously updated.
3343 pub fn with_selection_effects_deferred<R>(
3344 &mut self,
3345 window: &mut Window,
3346 cx: &mut Context<Self>,
3347 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3348 ) -> R {
3349 let already_deferred = self.defer_selection_effects;
3350 self.defer_selection_effects = true;
3351 let result = update(self, window, cx);
3352 if !already_deferred {
3353 self.defer_selection_effects = false;
3354 if let Some(state) = self.deferred_selection_effects_state.take() {
3355 self.apply_selection_effects(state, window, cx);
3356 }
3357 }
3358 result
3359 }
3360
3361 fn apply_selection_effects(
3362 &mut self,
3363 state: DeferredSelectionEffectsState,
3364 window: &mut Window,
3365 cx: &mut Context<Self>,
3366 ) {
3367 if state.changed {
3368 self.selection_history.push(state.history_entry);
3369
3370 if let Some(autoscroll) = state.effects.scroll {
3371 self.request_autoscroll(autoscroll, cx);
3372 }
3373
3374 let old_cursor_position = &state.old_cursor_position;
3375
3376 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3377
3378 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3379 self.show_signature_help(&ShowSignatureHelp, window, cx);
3380 }
3381 }
3382 }
3383
3384 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3385 where
3386 I: IntoIterator<Item = (Range<S>, T)>,
3387 S: ToOffset,
3388 T: Into<Arc<str>>,
3389 {
3390 if self.read_only(cx) {
3391 return;
3392 }
3393
3394 self.buffer
3395 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3396 }
3397
3398 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3399 where
3400 I: IntoIterator<Item = (Range<S>, T)>,
3401 S: ToOffset,
3402 T: Into<Arc<str>>,
3403 {
3404 if self.read_only(cx) {
3405 return;
3406 }
3407
3408 self.buffer.update(cx, |buffer, cx| {
3409 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3410 });
3411 }
3412
3413 pub fn edit_with_block_indent<I, S, T>(
3414 &mut self,
3415 edits: I,
3416 original_indent_columns: Vec<Option<u32>>,
3417 cx: &mut Context<Self>,
3418 ) where
3419 I: IntoIterator<Item = (Range<S>, T)>,
3420 S: ToOffset,
3421 T: Into<Arc<str>>,
3422 {
3423 if self.read_only(cx) {
3424 return;
3425 }
3426
3427 self.buffer.update(cx, |buffer, cx| {
3428 buffer.edit(
3429 edits,
3430 Some(AutoindentMode::Block {
3431 original_indent_columns,
3432 }),
3433 cx,
3434 )
3435 });
3436 }
3437
3438 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3439 self.hide_context_menu(window, cx);
3440
3441 match phase {
3442 SelectPhase::Begin {
3443 position,
3444 add,
3445 click_count,
3446 } => self.begin_selection(position, add, click_count, window, cx),
3447 SelectPhase::BeginColumnar {
3448 position,
3449 goal_column,
3450 reset,
3451 mode,
3452 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3453 SelectPhase::Extend {
3454 position,
3455 click_count,
3456 } => self.extend_selection(position, click_count, window, cx),
3457 SelectPhase::Update {
3458 position,
3459 goal_column,
3460 scroll_delta,
3461 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3462 SelectPhase::End => self.end_selection(window, cx),
3463 }
3464 }
3465
3466 fn extend_selection(
3467 &mut self,
3468 position: DisplayPoint,
3469 click_count: usize,
3470 window: &mut Window,
3471 cx: &mut Context<Self>,
3472 ) {
3473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3474 let tail = self.selections.newest::<usize>(&display_map).tail();
3475 let click_count = click_count.max(match self.selections.select_mode() {
3476 SelectMode::Character => 1,
3477 SelectMode::Word(_) => 2,
3478 SelectMode::Line(_) => 3,
3479 SelectMode::All => 4,
3480 });
3481 self.begin_selection(position, false, click_count, window, cx);
3482
3483 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3484
3485 let current_selection = match self.selections.select_mode() {
3486 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3487 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3488 };
3489
3490 let mut pending_selection = self
3491 .selections
3492 .pending_anchor()
3493 .cloned()
3494 .expect("extend_selection not called with pending selection");
3495
3496 if pending_selection
3497 .start
3498 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3499 == Ordering::Greater
3500 {
3501 pending_selection.start = current_selection.start;
3502 }
3503 if pending_selection
3504 .end
3505 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3506 == Ordering::Less
3507 {
3508 pending_selection.end = current_selection.end;
3509 pending_selection.reversed = true;
3510 }
3511
3512 let mut pending_mode = self.selections.pending_mode().unwrap();
3513 match &mut pending_mode {
3514 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3515 _ => {}
3516 }
3517
3518 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3519 SelectionEffects::scroll(Autoscroll::fit())
3520 } else {
3521 SelectionEffects::no_scroll()
3522 };
3523
3524 self.change_selections(effects, window, cx, |s| {
3525 s.set_pending(pending_selection.clone(), pending_mode);
3526 s.set_is_extending(true);
3527 });
3528 }
3529
3530 fn begin_selection(
3531 &mut self,
3532 position: DisplayPoint,
3533 add: bool,
3534 click_count: usize,
3535 window: &mut Window,
3536 cx: &mut Context<Self>,
3537 ) {
3538 if !self.focus_handle.is_focused(window) {
3539 self.last_focused_descendant = None;
3540 window.focus(&self.focus_handle);
3541 }
3542
3543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3544 let buffer = display_map.buffer_snapshot();
3545 let position = display_map.clip_point(position, Bias::Left);
3546
3547 let start;
3548 let end;
3549 let mode;
3550 let mut auto_scroll;
3551 match click_count {
3552 1 => {
3553 start = buffer.anchor_before(position.to_point(&display_map));
3554 end = start;
3555 mode = SelectMode::Character;
3556 auto_scroll = true;
3557 }
3558 2 => {
3559 let position = display_map
3560 .clip_point(position, Bias::Left)
3561 .to_offset(&display_map, Bias::Left);
3562 let (range, _) = buffer.surrounding_word(position, None);
3563 start = buffer.anchor_before(range.start);
3564 end = buffer.anchor_before(range.end);
3565 mode = SelectMode::Word(start..end);
3566 auto_scroll = true;
3567 }
3568 3 => {
3569 let position = display_map
3570 .clip_point(position, Bias::Left)
3571 .to_point(&display_map);
3572 let line_start = display_map.prev_line_boundary(position).0;
3573 let next_line_start = buffer.clip_point(
3574 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3575 Bias::Left,
3576 );
3577 start = buffer.anchor_before(line_start);
3578 end = buffer.anchor_before(next_line_start);
3579 mode = SelectMode::Line(start..end);
3580 auto_scroll = true;
3581 }
3582 _ => {
3583 start = buffer.anchor_before(0);
3584 end = buffer.anchor_before(buffer.len());
3585 mode = SelectMode::All;
3586 auto_scroll = false;
3587 }
3588 }
3589 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3590
3591 let point_to_delete: Option<usize> = {
3592 let selected_points: Vec<Selection<Point>> =
3593 self.selections.disjoint_in_range(start..end, &display_map);
3594
3595 if !add || click_count > 1 {
3596 None
3597 } else if !selected_points.is_empty() {
3598 Some(selected_points[0].id)
3599 } else {
3600 let clicked_point_already_selected =
3601 self.selections.disjoint_anchors().iter().find(|selection| {
3602 selection.start.to_point(buffer) == start.to_point(buffer)
3603 || selection.end.to_point(buffer) == end.to_point(buffer)
3604 });
3605
3606 clicked_point_already_selected.map(|selection| selection.id)
3607 }
3608 };
3609
3610 let selections_count = self.selections.count();
3611 let effects = if auto_scroll {
3612 SelectionEffects::default()
3613 } else {
3614 SelectionEffects::no_scroll()
3615 };
3616
3617 self.change_selections(effects, window, cx, |s| {
3618 if let Some(point_to_delete) = point_to_delete {
3619 s.delete(point_to_delete);
3620
3621 if selections_count == 1 {
3622 s.set_pending_anchor_range(start..end, mode);
3623 }
3624 } else {
3625 if !add {
3626 s.clear_disjoint();
3627 }
3628
3629 s.set_pending_anchor_range(start..end, mode);
3630 }
3631 });
3632 }
3633
3634 fn begin_columnar_selection(
3635 &mut self,
3636 position: DisplayPoint,
3637 goal_column: u32,
3638 reset: bool,
3639 mode: ColumnarMode,
3640 window: &mut Window,
3641 cx: &mut Context<Self>,
3642 ) {
3643 if !self.focus_handle.is_focused(window) {
3644 self.last_focused_descendant = None;
3645 window.focus(&self.focus_handle);
3646 }
3647
3648 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3649
3650 if reset {
3651 let pointer_position = display_map
3652 .buffer_snapshot()
3653 .anchor_before(position.to_point(&display_map));
3654
3655 self.change_selections(
3656 SelectionEffects::scroll(Autoscroll::newest()),
3657 window,
3658 cx,
3659 |s| {
3660 s.clear_disjoint();
3661 s.set_pending_anchor_range(
3662 pointer_position..pointer_position,
3663 SelectMode::Character,
3664 );
3665 },
3666 );
3667 };
3668
3669 let tail = self.selections.newest::<Point>(&display_map).tail();
3670 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3671 self.columnar_selection_state = match mode {
3672 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3673 selection_tail: selection_anchor,
3674 display_point: if reset {
3675 if position.column() != goal_column {
3676 Some(DisplayPoint::new(position.row(), goal_column))
3677 } else {
3678 None
3679 }
3680 } else {
3681 None
3682 },
3683 }),
3684 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3685 selection_tail: selection_anchor,
3686 }),
3687 };
3688
3689 if !reset {
3690 self.select_columns(position, goal_column, &display_map, window, cx);
3691 }
3692 }
3693
3694 fn update_selection(
3695 &mut self,
3696 position: DisplayPoint,
3697 goal_column: u32,
3698 scroll_delta: gpui::Point<f32>,
3699 window: &mut Window,
3700 cx: &mut Context<Self>,
3701 ) {
3702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3703
3704 if self.columnar_selection_state.is_some() {
3705 self.select_columns(position, goal_column, &display_map, window, cx);
3706 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3707 let buffer = display_map.buffer_snapshot();
3708 let head;
3709 let tail;
3710 let mode = self.selections.pending_mode().unwrap();
3711 match &mode {
3712 SelectMode::Character => {
3713 head = position.to_point(&display_map);
3714 tail = pending.tail().to_point(buffer);
3715 }
3716 SelectMode::Word(original_range) => {
3717 let offset = display_map
3718 .clip_point(position, Bias::Left)
3719 .to_offset(&display_map, Bias::Left);
3720 let original_range = original_range.to_offset(buffer);
3721
3722 let head_offset = if buffer.is_inside_word(offset, None)
3723 || original_range.contains(&offset)
3724 {
3725 let (word_range, _) = buffer.surrounding_word(offset, None);
3726 if word_range.start < original_range.start {
3727 word_range.start
3728 } else {
3729 word_range.end
3730 }
3731 } else {
3732 offset
3733 };
3734
3735 head = head_offset.to_point(buffer);
3736 if head_offset <= original_range.start {
3737 tail = original_range.end.to_point(buffer);
3738 } else {
3739 tail = original_range.start.to_point(buffer);
3740 }
3741 }
3742 SelectMode::Line(original_range) => {
3743 let original_range = original_range.to_point(display_map.buffer_snapshot());
3744
3745 let position = display_map
3746 .clip_point(position, Bias::Left)
3747 .to_point(&display_map);
3748 let line_start = display_map.prev_line_boundary(position).0;
3749 let next_line_start = buffer.clip_point(
3750 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3751 Bias::Left,
3752 );
3753
3754 if line_start < original_range.start {
3755 head = line_start
3756 } else {
3757 head = next_line_start
3758 }
3759
3760 if head <= original_range.start {
3761 tail = original_range.end;
3762 } else {
3763 tail = original_range.start;
3764 }
3765 }
3766 SelectMode::All => {
3767 return;
3768 }
3769 };
3770
3771 if head < tail {
3772 pending.start = buffer.anchor_before(head);
3773 pending.end = buffer.anchor_before(tail);
3774 pending.reversed = true;
3775 } else {
3776 pending.start = buffer.anchor_before(tail);
3777 pending.end = buffer.anchor_before(head);
3778 pending.reversed = false;
3779 }
3780
3781 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3782 s.set_pending(pending.clone(), mode);
3783 });
3784 } else {
3785 log::error!("update_selection dispatched with no pending selection");
3786 return;
3787 }
3788
3789 self.apply_scroll_delta(scroll_delta, window, cx);
3790 cx.notify();
3791 }
3792
3793 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3794 self.columnar_selection_state.take();
3795 if let Some(pending_mode) = self.selections.pending_mode() {
3796 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3797 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3798 s.select(selections);
3799 s.clear_pending();
3800 if s.is_extending() {
3801 s.set_is_extending(false);
3802 } else {
3803 s.set_select_mode(pending_mode);
3804 }
3805 });
3806 }
3807 }
3808
3809 fn select_columns(
3810 &mut self,
3811 head: DisplayPoint,
3812 goal_column: u32,
3813 display_map: &DisplaySnapshot,
3814 window: &mut Window,
3815 cx: &mut Context<Self>,
3816 ) {
3817 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3818 return;
3819 };
3820
3821 let tail = match columnar_state {
3822 ColumnarSelectionState::FromMouse {
3823 selection_tail,
3824 display_point,
3825 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3826 ColumnarSelectionState::FromSelection { selection_tail } => {
3827 selection_tail.to_display_point(display_map)
3828 }
3829 };
3830
3831 let start_row = cmp::min(tail.row(), head.row());
3832 let end_row = cmp::max(tail.row(), head.row());
3833 let start_column = cmp::min(tail.column(), goal_column);
3834 let end_column = cmp::max(tail.column(), goal_column);
3835 let reversed = start_column < tail.column();
3836
3837 let selection_ranges = (start_row.0..=end_row.0)
3838 .map(DisplayRow)
3839 .filter_map(|row| {
3840 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3841 || start_column <= display_map.line_len(row))
3842 && !display_map.is_block_line(row)
3843 {
3844 let start = display_map
3845 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3846 .to_point(display_map);
3847 let end = display_map
3848 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3849 .to_point(display_map);
3850 if reversed {
3851 Some(end..start)
3852 } else {
3853 Some(start..end)
3854 }
3855 } else {
3856 None
3857 }
3858 })
3859 .collect::<Vec<_>>();
3860 if selection_ranges.is_empty() {
3861 return;
3862 }
3863
3864 let ranges = match columnar_state {
3865 ColumnarSelectionState::FromMouse { .. } => {
3866 let mut non_empty_ranges = selection_ranges
3867 .iter()
3868 .filter(|selection_range| selection_range.start != selection_range.end)
3869 .peekable();
3870 if non_empty_ranges.peek().is_some() {
3871 non_empty_ranges.cloned().collect()
3872 } else {
3873 selection_ranges
3874 }
3875 }
3876 _ => selection_ranges,
3877 };
3878
3879 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3880 s.select_ranges(ranges);
3881 });
3882 cx.notify();
3883 }
3884
3885 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3886 self.selections
3887 .all_adjusted(snapshot)
3888 .iter()
3889 .any(|selection| !selection.is_empty())
3890 }
3891
3892 pub fn has_pending_nonempty_selection(&self) -> bool {
3893 let pending_nonempty_selection = match self.selections.pending_anchor() {
3894 Some(Selection { start, end, .. }) => start != end,
3895 None => false,
3896 };
3897
3898 pending_nonempty_selection
3899 || (self.columnar_selection_state.is_some()
3900 && self.selections.disjoint_anchors().len() > 1)
3901 }
3902
3903 pub fn has_pending_selection(&self) -> bool {
3904 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3905 }
3906
3907 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3908 self.selection_mark_mode = false;
3909 self.selection_drag_state = SelectionDragState::None;
3910
3911 if self.clear_expanded_diff_hunks(cx) {
3912 cx.notify();
3913 return;
3914 }
3915 if self.dismiss_menus_and_popups(true, window, cx) {
3916 return;
3917 }
3918
3919 if self.mode.is_full()
3920 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3921 {
3922 return;
3923 }
3924
3925 cx.propagate();
3926 }
3927
3928 pub fn dismiss_menus_and_popups(
3929 &mut self,
3930 is_user_requested: bool,
3931 window: &mut Window,
3932 cx: &mut Context<Self>,
3933 ) -> bool {
3934 if self.take_rename(false, window, cx).is_some() {
3935 return true;
3936 }
3937
3938 if self.hide_blame_popover(true, cx) {
3939 return true;
3940 }
3941
3942 if hide_hover(self, cx) {
3943 return true;
3944 }
3945
3946 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3947 return true;
3948 }
3949
3950 if self.hide_context_menu(window, cx).is_some() {
3951 return true;
3952 }
3953
3954 if self.mouse_context_menu.take().is_some() {
3955 return true;
3956 }
3957
3958 if is_user_requested && self.discard_edit_prediction(true, cx) {
3959 return true;
3960 }
3961
3962 if self.snippet_stack.pop().is_some() {
3963 return true;
3964 }
3965
3966 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3967 self.dismiss_diagnostics(cx);
3968 return true;
3969 }
3970
3971 false
3972 }
3973
3974 fn linked_editing_ranges_for(
3975 &self,
3976 selection: Range<text::Anchor>,
3977 cx: &App,
3978 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3979 if self.linked_edit_ranges.is_empty() {
3980 return None;
3981 }
3982 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3983 selection.end.buffer_id.and_then(|end_buffer_id| {
3984 if selection.start.buffer_id != Some(end_buffer_id) {
3985 return None;
3986 }
3987 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3988 let snapshot = buffer.read(cx).snapshot();
3989 self.linked_edit_ranges
3990 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3991 .map(|ranges| (ranges, snapshot, buffer))
3992 })?;
3993 use text::ToOffset as TO;
3994 // find offset from the start of current range to current cursor position
3995 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3996
3997 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3998 let start_difference = start_offset - start_byte_offset;
3999 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4000 let end_difference = end_offset - start_byte_offset;
4001 // Current range has associated linked ranges.
4002 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4003 for range in linked_ranges.iter() {
4004 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4005 let end_offset = start_offset + end_difference;
4006 let start_offset = start_offset + start_difference;
4007 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4008 continue;
4009 }
4010 if self.selections.disjoint_anchor_ranges().any(|s| {
4011 if s.start.buffer_id != selection.start.buffer_id
4012 || s.end.buffer_id != selection.end.buffer_id
4013 {
4014 return false;
4015 }
4016 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4017 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4018 }) {
4019 continue;
4020 }
4021 let start = buffer_snapshot.anchor_after(start_offset);
4022 let end = buffer_snapshot.anchor_after(end_offset);
4023 linked_edits
4024 .entry(buffer.clone())
4025 .or_default()
4026 .push(start..end);
4027 }
4028 Some(linked_edits)
4029 }
4030
4031 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4032 let text: Arc<str> = text.into();
4033
4034 if self.read_only(cx) {
4035 return;
4036 }
4037
4038 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4039
4040 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4041 let mut bracket_inserted = false;
4042 let mut edits = Vec::new();
4043 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4044 let mut new_selections = Vec::with_capacity(selections.len());
4045 let mut new_autoclose_regions = Vec::new();
4046 let snapshot = self.buffer.read(cx).read(cx);
4047 let mut clear_linked_edit_ranges = false;
4048
4049 for (selection, autoclose_region) in
4050 self.selections_with_autoclose_regions(selections, &snapshot)
4051 {
4052 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4053 // Determine if the inserted text matches the opening or closing
4054 // bracket of any of this language's bracket pairs.
4055 let mut bracket_pair = None;
4056 let mut is_bracket_pair_start = false;
4057 let mut is_bracket_pair_end = false;
4058 if !text.is_empty() {
4059 let mut bracket_pair_matching_end = None;
4060 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4061 // and they are removing the character that triggered IME popup.
4062 for (pair, enabled) in scope.brackets() {
4063 if !pair.close && !pair.surround {
4064 continue;
4065 }
4066
4067 if enabled && pair.start.ends_with(text.as_ref()) {
4068 let prefix_len = pair.start.len() - text.len();
4069 let preceding_text_matches_prefix = prefix_len == 0
4070 || (selection.start.column >= (prefix_len as u32)
4071 && snapshot.contains_str_at(
4072 Point::new(
4073 selection.start.row,
4074 selection.start.column - (prefix_len as u32),
4075 ),
4076 &pair.start[..prefix_len],
4077 ));
4078 if preceding_text_matches_prefix {
4079 bracket_pair = Some(pair.clone());
4080 is_bracket_pair_start = true;
4081 break;
4082 }
4083 }
4084 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4085 {
4086 // take first bracket pair matching end, but don't break in case a later bracket
4087 // pair matches start
4088 bracket_pair_matching_end = Some(pair.clone());
4089 }
4090 }
4091 if let Some(end) = bracket_pair_matching_end
4092 && bracket_pair.is_none()
4093 {
4094 bracket_pair = Some(end);
4095 is_bracket_pair_end = true;
4096 }
4097 }
4098
4099 if let Some(bracket_pair) = bracket_pair {
4100 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4101 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4102 let auto_surround =
4103 self.use_auto_surround && snapshot_settings.use_auto_surround;
4104 if selection.is_empty() {
4105 if is_bracket_pair_start {
4106 // If the inserted text is a suffix of an opening bracket and the
4107 // selection is preceded by the rest of the opening bracket, then
4108 // insert the closing bracket.
4109 let following_text_allows_autoclose = snapshot
4110 .chars_at(selection.start)
4111 .next()
4112 .is_none_or(|c| scope.should_autoclose_before(c));
4113
4114 let preceding_text_allows_autoclose = selection.start.column == 0
4115 || snapshot
4116 .reversed_chars_at(selection.start)
4117 .next()
4118 .is_none_or(|c| {
4119 bracket_pair.start != bracket_pair.end
4120 || !snapshot
4121 .char_classifier_at(selection.start)
4122 .is_word(c)
4123 });
4124
4125 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4126 && bracket_pair.start.len() == 1
4127 {
4128 let target = bracket_pair.start.chars().next().unwrap();
4129 let current_line_count = snapshot
4130 .reversed_chars_at(selection.start)
4131 .take_while(|&c| c != '\n')
4132 .filter(|&c| c == target)
4133 .count();
4134 current_line_count % 2 == 1
4135 } else {
4136 false
4137 };
4138
4139 if autoclose
4140 && bracket_pair.close
4141 && following_text_allows_autoclose
4142 && preceding_text_allows_autoclose
4143 && !is_closing_quote
4144 {
4145 let anchor = snapshot.anchor_before(selection.end);
4146 new_selections.push((selection.map(|_| anchor), text.len()));
4147 new_autoclose_regions.push((
4148 anchor,
4149 text.len(),
4150 selection.id,
4151 bracket_pair.clone(),
4152 ));
4153 edits.push((
4154 selection.range(),
4155 format!("{}{}", text, bracket_pair.end).into(),
4156 ));
4157 bracket_inserted = true;
4158 continue;
4159 }
4160 }
4161
4162 if let Some(region) = autoclose_region {
4163 // If the selection is followed by an auto-inserted closing bracket,
4164 // then don't insert that closing bracket again; just move the selection
4165 // past the closing bracket.
4166 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4167 && text.as_ref() == region.pair.end.as_str()
4168 && snapshot.contains_str_at(region.range.end, text.as_ref());
4169 if should_skip {
4170 let anchor = snapshot.anchor_after(selection.end);
4171 new_selections
4172 .push((selection.map(|_| anchor), region.pair.end.len()));
4173 continue;
4174 }
4175 }
4176
4177 let always_treat_brackets_as_autoclosed = snapshot
4178 .language_settings_at(selection.start, cx)
4179 .always_treat_brackets_as_autoclosed;
4180 if always_treat_brackets_as_autoclosed
4181 && is_bracket_pair_end
4182 && snapshot.contains_str_at(selection.end, text.as_ref())
4183 {
4184 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4185 // and the inserted text is a closing bracket and the selection is followed
4186 // by the closing bracket then move the selection past the closing bracket.
4187 let anchor = snapshot.anchor_after(selection.end);
4188 new_selections.push((selection.map(|_| anchor), text.len()));
4189 continue;
4190 }
4191 }
4192 // If an opening bracket is 1 character long and is typed while
4193 // text is selected, then surround that text with the bracket pair.
4194 else if auto_surround
4195 && bracket_pair.surround
4196 && is_bracket_pair_start
4197 && bracket_pair.start.chars().count() == 1
4198 {
4199 edits.push((selection.start..selection.start, text.clone()));
4200 edits.push((
4201 selection.end..selection.end,
4202 bracket_pair.end.as_str().into(),
4203 ));
4204 bracket_inserted = true;
4205 new_selections.push((
4206 Selection {
4207 id: selection.id,
4208 start: snapshot.anchor_after(selection.start),
4209 end: snapshot.anchor_before(selection.end),
4210 reversed: selection.reversed,
4211 goal: selection.goal,
4212 },
4213 0,
4214 ));
4215 continue;
4216 }
4217 }
4218 }
4219
4220 if self.auto_replace_emoji_shortcode
4221 && selection.is_empty()
4222 && text.as_ref().ends_with(':')
4223 && let Some(possible_emoji_short_code) =
4224 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4225 && !possible_emoji_short_code.is_empty()
4226 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4227 {
4228 let emoji_shortcode_start = Point::new(
4229 selection.start.row,
4230 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4231 );
4232
4233 // Remove shortcode from buffer
4234 edits.push((
4235 emoji_shortcode_start..selection.start,
4236 "".to_string().into(),
4237 ));
4238 new_selections.push((
4239 Selection {
4240 id: selection.id,
4241 start: snapshot.anchor_after(emoji_shortcode_start),
4242 end: snapshot.anchor_before(selection.start),
4243 reversed: selection.reversed,
4244 goal: selection.goal,
4245 },
4246 0,
4247 ));
4248
4249 // Insert emoji
4250 let selection_start_anchor = snapshot.anchor_after(selection.start);
4251 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4252 edits.push((selection.start..selection.end, emoji.to_string().into()));
4253
4254 continue;
4255 }
4256
4257 // If not handling any auto-close operation, then just replace the selected
4258 // text with the given input and move the selection to the end of the
4259 // newly inserted text.
4260 let anchor = snapshot.anchor_after(selection.end);
4261 if !self.linked_edit_ranges.is_empty() {
4262 let start_anchor = snapshot.anchor_before(selection.start);
4263
4264 let is_word_char = text.chars().next().is_none_or(|char| {
4265 let classifier = snapshot
4266 .char_classifier_at(start_anchor.to_offset(&snapshot))
4267 .scope_context(Some(CharScopeContext::LinkedEdit));
4268 classifier.is_word(char)
4269 });
4270
4271 if is_word_char {
4272 if let Some(ranges) = self
4273 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4274 {
4275 for (buffer, edits) in ranges {
4276 linked_edits
4277 .entry(buffer.clone())
4278 .or_default()
4279 .extend(edits.into_iter().map(|range| (range, text.clone())));
4280 }
4281 }
4282 } else {
4283 clear_linked_edit_ranges = true;
4284 }
4285 }
4286
4287 new_selections.push((selection.map(|_| anchor), 0));
4288 edits.push((selection.start..selection.end, text.clone()));
4289 }
4290
4291 drop(snapshot);
4292
4293 self.transact(window, cx, |this, window, cx| {
4294 if clear_linked_edit_ranges {
4295 this.linked_edit_ranges.clear();
4296 }
4297 let initial_buffer_versions =
4298 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4299
4300 this.buffer.update(cx, |buffer, cx| {
4301 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4302 });
4303 for (buffer, edits) in linked_edits {
4304 buffer.update(cx, |buffer, cx| {
4305 let snapshot = buffer.snapshot();
4306 let edits = edits
4307 .into_iter()
4308 .map(|(range, text)| {
4309 use text::ToPoint as TP;
4310 let end_point = TP::to_point(&range.end, &snapshot);
4311 let start_point = TP::to_point(&range.start, &snapshot);
4312 (start_point..end_point, text)
4313 })
4314 .sorted_by_key(|(range, _)| range.start);
4315 buffer.edit(edits, None, cx);
4316 })
4317 }
4318 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4319 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4320 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4321 let new_selections =
4322 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4323 .zip(new_selection_deltas)
4324 .map(|(selection, delta)| Selection {
4325 id: selection.id,
4326 start: selection.start + delta,
4327 end: selection.end + delta,
4328 reversed: selection.reversed,
4329 goal: SelectionGoal::None,
4330 })
4331 .collect::<Vec<_>>();
4332
4333 let mut i = 0;
4334 for (position, delta, selection_id, pair) in new_autoclose_regions {
4335 let position = position.to_offset(map.buffer_snapshot()) + delta;
4336 let start = map.buffer_snapshot().anchor_before(position);
4337 let end = map.buffer_snapshot().anchor_after(position);
4338 while let Some(existing_state) = this.autoclose_regions.get(i) {
4339 match existing_state
4340 .range
4341 .start
4342 .cmp(&start, map.buffer_snapshot())
4343 {
4344 Ordering::Less => i += 1,
4345 Ordering::Greater => break,
4346 Ordering::Equal => {
4347 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4348 Ordering::Less => i += 1,
4349 Ordering::Equal => break,
4350 Ordering::Greater => break,
4351 }
4352 }
4353 }
4354 }
4355 this.autoclose_regions.insert(
4356 i,
4357 AutocloseRegion {
4358 selection_id,
4359 range: start..end,
4360 pair,
4361 },
4362 );
4363 }
4364
4365 let had_active_edit_prediction = this.has_active_edit_prediction();
4366 this.change_selections(
4367 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4368 window,
4369 cx,
4370 |s| s.select(new_selections),
4371 );
4372
4373 if !bracket_inserted
4374 && let Some(on_type_format_task) =
4375 this.trigger_on_type_formatting(text.to_string(), window, cx)
4376 {
4377 on_type_format_task.detach_and_log_err(cx);
4378 }
4379
4380 let editor_settings = EditorSettings::get_global(cx);
4381 if bracket_inserted
4382 && (editor_settings.auto_signature_help
4383 || editor_settings.show_signature_help_after_edits)
4384 {
4385 this.show_signature_help(&ShowSignatureHelp, window, cx);
4386 }
4387
4388 let trigger_in_words =
4389 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4390 if this.hard_wrap.is_some() {
4391 let latest: Range<Point> = this.selections.newest(&map).range();
4392 if latest.is_empty()
4393 && this
4394 .buffer()
4395 .read(cx)
4396 .snapshot(cx)
4397 .line_len(MultiBufferRow(latest.start.row))
4398 == latest.start.column
4399 {
4400 this.rewrap_impl(
4401 RewrapOptions {
4402 override_language_settings: true,
4403 preserve_existing_whitespace: true,
4404 },
4405 cx,
4406 )
4407 }
4408 }
4409 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4410 refresh_linked_ranges(this, window, cx);
4411 this.refresh_edit_prediction(true, false, window, cx);
4412 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4413 });
4414 }
4415
4416 fn find_possible_emoji_shortcode_at_position(
4417 snapshot: &MultiBufferSnapshot,
4418 position: Point,
4419 ) -> Option<String> {
4420 let mut chars = Vec::new();
4421 let mut found_colon = false;
4422 for char in snapshot.reversed_chars_at(position).take(100) {
4423 // Found a possible emoji shortcode in the middle of the buffer
4424 if found_colon {
4425 if char.is_whitespace() {
4426 chars.reverse();
4427 return Some(chars.iter().collect());
4428 }
4429 // If the previous character is not a whitespace, we are in the middle of a word
4430 // and we only want to complete the shortcode if the word is made up of other emojis
4431 let mut containing_word = String::new();
4432 for ch in snapshot
4433 .reversed_chars_at(position)
4434 .skip(chars.len() + 1)
4435 .take(100)
4436 {
4437 if ch.is_whitespace() {
4438 break;
4439 }
4440 containing_word.push(ch);
4441 }
4442 let containing_word = containing_word.chars().rev().collect::<String>();
4443 if util::word_consists_of_emojis(containing_word.as_str()) {
4444 chars.reverse();
4445 return Some(chars.iter().collect());
4446 }
4447 }
4448
4449 if char.is_whitespace() || !char.is_ascii() {
4450 return None;
4451 }
4452 if char == ':' {
4453 found_colon = true;
4454 } else {
4455 chars.push(char);
4456 }
4457 }
4458 // Found a possible emoji shortcode at the beginning of the buffer
4459 chars.reverse();
4460 Some(chars.iter().collect())
4461 }
4462
4463 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4465 self.transact(window, cx, |this, window, cx| {
4466 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4467 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4468 let multi_buffer = this.buffer.read(cx);
4469 let buffer = multi_buffer.snapshot(cx);
4470 selections
4471 .iter()
4472 .map(|selection| {
4473 let start_point = selection.start.to_point(&buffer);
4474 let mut existing_indent =
4475 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4476 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4477 let start = selection.start;
4478 let end = selection.end;
4479 let selection_is_empty = start == end;
4480 let language_scope = buffer.language_scope_at(start);
4481 let (
4482 comment_delimiter,
4483 doc_delimiter,
4484 insert_extra_newline,
4485 indent_on_newline,
4486 indent_on_extra_newline,
4487 ) = if let Some(language) = &language_scope {
4488 let mut insert_extra_newline =
4489 insert_extra_newline_brackets(&buffer, start..end, language)
4490 || insert_extra_newline_tree_sitter(&buffer, start..end);
4491
4492 // Comment extension on newline is allowed only for cursor selections
4493 let comment_delimiter = maybe!({
4494 if !selection_is_empty {
4495 return None;
4496 }
4497
4498 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4499 return None;
4500 }
4501
4502 let delimiters = language.line_comment_prefixes();
4503 let max_len_of_delimiter =
4504 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4505 let (snapshot, range) =
4506 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4507
4508 let num_of_whitespaces = snapshot
4509 .chars_for_range(range.clone())
4510 .take_while(|c| c.is_whitespace())
4511 .count();
4512 let comment_candidate = snapshot
4513 .chars_for_range(range.clone())
4514 .skip(num_of_whitespaces)
4515 .take(max_len_of_delimiter)
4516 .collect::<String>();
4517 let (delimiter, trimmed_len) = delimiters
4518 .iter()
4519 .filter_map(|delimiter| {
4520 let prefix = delimiter.trim_end();
4521 if comment_candidate.starts_with(prefix) {
4522 Some((delimiter, prefix.len()))
4523 } else {
4524 None
4525 }
4526 })
4527 .max_by_key(|(_, len)| *len)?;
4528
4529 if let Some(BlockCommentConfig {
4530 start: block_start, ..
4531 }) = language.block_comment()
4532 {
4533 let block_start_trimmed = block_start.trim_end();
4534 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4535 let line_content = snapshot
4536 .chars_for_range(range)
4537 .skip(num_of_whitespaces)
4538 .take(block_start_trimmed.len())
4539 .collect::<String>();
4540
4541 if line_content.starts_with(block_start_trimmed) {
4542 return None;
4543 }
4544 }
4545 }
4546
4547 let cursor_is_placed_after_comment_marker =
4548 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4549 if cursor_is_placed_after_comment_marker {
4550 Some(delimiter.clone())
4551 } else {
4552 None
4553 }
4554 });
4555
4556 let mut indent_on_newline = IndentSize::spaces(0);
4557 let mut indent_on_extra_newline = IndentSize::spaces(0);
4558
4559 let doc_delimiter = maybe!({
4560 if !selection_is_empty {
4561 return None;
4562 }
4563
4564 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4565 return None;
4566 }
4567
4568 let BlockCommentConfig {
4569 start: start_tag,
4570 end: end_tag,
4571 prefix: delimiter,
4572 tab_size: len,
4573 } = language.documentation_comment()?;
4574 let is_within_block_comment = buffer
4575 .language_scope_at(start_point)
4576 .is_some_and(|scope| scope.override_name() == Some("comment"));
4577 if !is_within_block_comment {
4578 return None;
4579 }
4580
4581 let (snapshot, range) =
4582 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4583
4584 let num_of_whitespaces = snapshot
4585 .chars_for_range(range.clone())
4586 .take_while(|c| c.is_whitespace())
4587 .count();
4588
4589 // 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.
4590 let column = start_point.column;
4591 let cursor_is_after_start_tag = {
4592 let start_tag_len = start_tag.len();
4593 let start_tag_line = snapshot
4594 .chars_for_range(range.clone())
4595 .skip(num_of_whitespaces)
4596 .take(start_tag_len)
4597 .collect::<String>();
4598 if start_tag_line.starts_with(start_tag.as_ref()) {
4599 num_of_whitespaces + start_tag_len <= column as usize
4600 } else {
4601 false
4602 }
4603 };
4604
4605 let cursor_is_after_delimiter = {
4606 let delimiter_trim = delimiter.trim_end();
4607 let delimiter_line = snapshot
4608 .chars_for_range(range.clone())
4609 .skip(num_of_whitespaces)
4610 .take(delimiter_trim.len())
4611 .collect::<String>();
4612 if delimiter_line.starts_with(delimiter_trim) {
4613 num_of_whitespaces + delimiter_trim.len() <= column as usize
4614 } else {
4615 false
4616 }
4617 };
4618
4619 let cursor_is_before_end_tag_if_exists = {
4620 let mut char_position = 0u32;
4621 let mut end_tag_offset = None;
4622
4623 'outer: for chunk in snapshot.text_for_range(range) {
4624 if let Some(byte_pos) = chunk.find(&**end_tag) {
4625 let chars_before_match =
4626 chunk[..byte_pos].chars().count() as u32;
4627 end_tag_offset =
4628 Some(char_position + chars_before_match);
4629 break 'outer;
4630 }
4631 char_position += chunk.chars().count() as u32;
4632 }
4633
4634 if let Some(end_tag_offset) = end_tag_offset {
4635 let cursor_is_before_end_tag = column <= end_tag_offset;
4636 if cursor_is_after_start_tag {
4637 if cursor_is_before_end_tag {
4638 insert_extra_newline = true;
4639 }
4640 let cursor_is_at_start_of_end_tag =
4641 column == end_tag_offset;
4642 if cursor_is_at_start_of_end_tag {
4643 indent_on_extra_newline.len = *len;
4644 }
4645 }
4646 cursor_is_before_end_tag
4647 } else {
4648 true
4649 }
4650 };
4651
4652 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4653 && cursor_is_before_end_tag_if_exists
4654 {
4655 if cursor_is_after_start_tag {
4656 indent_on_newline.len = *len;
4657 }
4658 Some(delimiter.clone())
4659 } else {
4660 None
4661 }
4662 });
4663
4664 (
4665 comment_delimiter,
4666 doc_delimiter,
4667 insert_extra_newline,
4668 indent_on_newline,
4669 indent_on_extra_newline,
4670 )
4671 } else {
4672 (
4673 None,
4674 None,
4675 false,
4676 IndentSize::default(),
4677 IndentSize::default(),
4678 )
4679 };
4680
4681 let prevent_auto_indent = doc_delimiter.is_some();
4682 let delimiter = comment_delimiter.or(doc_delimiter);
4683
4684 let capacity_for_delimiter =
4685 delimiter.as_deref().map(str::len).unwrap_or_default();
4686 let mut new_text = String::with_capacity(
4687 1 + capacity_for_delimiter
4688 + existing_indent.len as usize
4689 + indent_on_newline.len as usize
4690 + indent_on_extra_newline.len as usize,
4691 );
4692 new_text.push('\n');
4693 new_text.extend(existing_indent.chars());
4694 new_text.extend(indent_on_newline.chars());
4695
4696 if let Some(delimiter) = &delimiter {
4697 new_text.push_str(delimiter);
4698 }
4699
4700 if insert_extra_newline {
4701 new_text.push('\n');
4702 new_text.extend(existing_indent.chars());
4703 new_text.extend(indent_on_extra_newline.chars());
4704 }
4705
4706 let anchor = buffer.anchor_after(end);
4707 let new_selection = selection.map(|_| anchor);
4708 (
4709 ((start..end, new_text), prevent_auto_indent),
4710 (insert_extra_newline, new_selection),
4711 )
4712 })
4713 .unzip()
4714 };
4715
4716 let mut auto_indent_edits = Vec::new();
4717 let mut edits = Vec::new();
4718 for (edit, prevent_auto_indent) in edits_with_flags {
4719 if prevent_auto_indent {
4720 edits.push(edit);
4721 } else {
4722 auto_indent_edits.push(edit);
4723 }
4724 }
4725 if !edits.is_empty() {
4726 this.edit(edits, cx);
4727 }
4728 if !auto_indent_edits.is_empty() {
4729 this.edit_with_autoindent(auto_indent_edits, cx);
4730 }
4731
4732 let buffer = this.buffer.read(cx).snapshot(cx);
4733 let new_selections = selection_info
4734 .into_iter()
4735 .map(|(extra_newline_inserted, new_selection)| {
4736 let mut cursor = new_selection.end.to_point(&buffer);
4737 if extra_newline_inserted {
4738 cursor.row -= 1;
4739 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4740 }
4741 new_selection.map(|_| cursor)
4742 })
4743 .collect();
4744
4745 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4746 this.refresh_edit_prediction(true, false, window, cx);
4747 });
4748 }
4749
4750 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4752
4753 let buffer = self.buffer.read(cx);
4754 let snapshot = buffer.snapshot(cx);
4755
4756 let mut edits = Vec::new();
4757 let mut rows = Vec::new();
4758
4759 for (rows_inserted, selection) in self
4760 .selections
4761 .all_adjusted(&self.display_snapshot(cx))
4762 .into_iter()
4763 .enumerate()
4764 {
4765 let cursor = selection.head();
4766 let row = cursor.row;
4767
4768 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4769
4770 let newline = "\n".to_string();
4771 edits.push((start_of_line..start_of_line, newline));
4772
4773 rows.push(row + rows_inserted as u32);
4774 }
4775
4776 self.transact(window, cx, |editor, window, cx| {
4777 editor.edit(edits, cx);
4778
4779 editor.change_selections(Default::default(), window, cx, |s| {
4780 let mut index = 0;
4781 s.move_cursors_with(|map, _, _| {
4782 let row = rows[index];
4783 index += 1;
4784
4785 let point = Point::new(row, 0);
4786 let boundary = map.next_line_boundary(point).1;
4787 let clipped = map.clip_point(boundary, Bias::Left);
4788
4789 (clipped, SelectionGoal::None)
4790 });
4791 });
4792
4793 let mut indent_edits = Vec::new();
4794 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4795 for row in rows {
4796 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4797 for (row, indent) in indents {
4798 if indent.len == 0 {
4799 continue;
4800 }
4801
4802 let text = match indent.kind {
4803 IndentKind::Space => " ".repeat(indent.len as usize),
4804 IndentKind::Tab => "\t".repeat(indent.len as usize),
4805 };
4806 let point = Point::new(row.0, 0);
4807 indent_edits.push((point..point, text));
4808 }
4809 }
4810 editor.edit(indent_edits, cx);
4811 });
4812 }
4813
4814 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4816
4817 let buffer = self.buffer.read(cx);
4818 let snapshot = buffer.snapshot(cx);
4819
4820 let mut edits = Vec::new();
4821 let mut rows = Vec::new();
4822 let mut rows_inserted = 0;
4823
4824 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4825 let cursor = selection.head();
4826 let row = cursor.row;
4827
4828 let point = Point::new(row + 1, 0);
4829 let start_of_line = snapshot.clip_point(point, Bias::Left);
4830
4831 let newline = "\n".to_string();
4832 edits.push((start_of_line..start_of_line, newline));
4833
4834 rows_inserted += 1;
4835 rows.push(row + rows_inserted);
4836 }
4837
4838 self.transact(window, cx, |editor, window, cx| {
4839 editor.edit(edits, cx);
4840
4841 editor.change_selections(Default::default(), window, cx, |s| {
4842 let mut index = 0;
4843 s.move_cursors_with(|map, _, _| {
4844 let row = rows[index];
4845 index += 1;
4846
4847 let point = Point::new(row, 0);
4848 let boundary = map.next_line_boundary(point).1;
4849 let clipped = map.clip_point(boundary, Bias::Left);
4850
4851 (clipped, SelectionGoal::None)
4852 });
4853 });
4854
4855 let mut indent_edits = Vec::new();
4856 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4857 for row in rows {
4858 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4859 for (row, indent) in indents {
4860 if indent.len == 0 {
4861 continue;
4862 }
4863
4864 let text = match indent.kind {
4865 IndentKind::Space => " ".repeat(indent.len as usize),
4866 IndentKind::Tab => "\t".repeat(indent.len as usize),
4867 };
4868 let point = Point::new(row.0, 0);
4869 indent_edits.push((point..point, text));
4870 }
4871 }
4872 editor.edit(indent_edits, cx);
4873 });
4874 }
4875
4876 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4877 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4878 original_indent_columns: Vec::new(),
4879 });
4880 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4881 }
4882
4883 fn insert_with_autoindent_mode(
4884 &mut self,
4885 text: &str,
4886 autoindent_mode: Option<AutoindentMode>,
4887 window: &mut Window,
4888 cx: &mut Context<Self>,
4889 ) {
4890 if self.read_only(cx) {
4891 return;
4892 }
4893
4894 let text: Arc<str> = text.into();
4895 self.transact(window, cx, |this, window, cx| {
4896 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4897 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4898 let anchors = {
4899 let snapshot = buffer.read(cx);
4900 old_selections
4901 .iter()
4902 .map(|s| {
4903 let anchor = snapshot.anchor_after(s.head());
4904 s.map(|_| anchor)
4905 })
4906 .collect::<Vec<_>>()
4907 };
4908 buffer.edit(
4909 old_selections
4910 .iter()
4911 .map(|s| (s.start..s.end, text.clone())),
4912 autoindent_mode,
4913 cx,
4914 );
4915 anchors
4916 });
4917
4918 this.change_selections(Default::default(), window, cx, |s| {
4919 s.select_anchors(selection_anchors);
4920 });
4921
4922 cx.notify();
4923 });
4924 }
4925
4926 fn trigger_completion_on_input(
4927 &mut self,
4928 text: &str,
4929 trigger_in_words: bool,
4930 window: &mut Window,
4931 cx: &mut Context<Self>,
4932 ) {
4933 let completions_source = self
4934 .context_menu
4935 .borrow()
4936 .as_ref()
4937 .and_then(|menu| match menu {
4938 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4939 CodeContextMenu::CodeActions(_) => None,
4940 });
4941
4942 match completions_source {
4943 Some(CompletionsMenuSource::Words { .. }) => {
4944 self.open_or_update_completions_menu(
4945 Some(CompletionsMenuSource::Words {
4946 ignore_threshold: false,
4947 }),
4948 None,
4949 window,
4950 cx,
4951 );
4952 }
4953 Some(CompletionsMenuSource::Normal)
4954 | Some(CompletionsMenuSource::SnippetChoices)
4955 | None
4956 if self.is_completion_trigger(
4957 text,
4958 trigger_in_words,
4959 completions_source.is_some(),
4960 cx,
4961 ) =>
4962 {
4963 self.show_completions(
4964 &ShowCompletions {
4965 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4966 },
4967 window,
4968 cx,
4969 )
4970 }
4971 _ => {
4972 self.hide_context_menu(window, cx);
4973 }
4974 }
4975 }
4976
4977 fn is_completion_trigger(
4978 &self,
4979 text: &str,
4980 trigger_in_words: bool,
4981 menu_is_open: bool,
4982 cx: &mut Context<Self>,
4983 ) -> bool {
4984 let position = self.selections.newest_anchor().head();
4985 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4986 return false;
4987 };
4988
4989 if let Some(completion_provider) = &self.completion_provider {
4990 completion_provider.is_completion_trigger(
4991 &buffer,
4992 position.text_anchor,
4993 text,
4994 trigger_in_words,
4995 menu_is_open,
4996 cx,
4997 )
4998 } else {
4999 false
5000 }
5001 }
5002
5003 /// If any empty selections is touching the start of its innermost containing autoclose
5004 /// region, expand it to select the brackets.
5005 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5006 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5007 let buffer = self.buffer.read(cx).read(cx);
5008 let new_selections = self
5009 .selections_with_autoclose_regions(selections, &buffer)
5010 .map(|(mut selection, region)| {
5011 if !selection.is_empty() {
5012 return selection;
5013 }
5014
5015 if let Some(region) = region {
5016 let mut range = region.range.to_offset(&buffer);
5017 if selection.start == range.start && range.start >= region.pair.start.len() {
5018 range.start -= region.pair.start.len();
5019 if buffer.contains_str_at(range.start, ®ion.pair.start)
5020 && buffer.contains_str_at(range.end, ®ion.pair.end)
5021 {
5022 range.end += region.pair.end.len();
5023 selection.start = range.start;
5024 selection.end = range.end;
5025
5026 return selection;
5027 }
5028 }
5029 }
5030
5031 let always_treat_brackets_as_autoclosed = buffer
5032 .language_settings_at(selection.start, cx)
5033 .always_treat_brackets_as_autoclosed;
5034
5035 if !always_treat_brackets_as_autoclosed {
5036 return selection;
5037 }
5038
5039 if let Some(scope) = buffer.language_scope_at(selection.start) {
5040 for (pair, enabled) in scope.brackets() {
5041 if !enabled || !pair.close {
5042 continue;
5043 }
5044
5045 if buffer.contains_str_at(selection.start, &pair.end) {
5046 let pair_start_len = pair.start.len();
5047 if buffer.contains_str_at(
5048 selection.start.saturating_sub(pair_start_len),
5049 &pair.start,
5050 ) {
5051 selection.start -= pair_start_len;
5052 selection.end += pair.end.len();
5053
5054 return selection;
5055 }
5056 }
5057 }
5058 }
5059
5060 selection
5061 })
5062 .collect();
5063
5064 drop(buffer);
5065 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5066 selections.select(new_selections)
5067 });
5068 }
5069
5070 /// Iterate the given selections, and for each one, find the smallest surrounding
5071 /// autoclose region. This uses the ordering of the selections and the autoclose
5072 /// regions to avoid repeated comparisons.
5073 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5074 &'a self,
5075 selections: impl IntoIterator<Item = Selection<D>>,
5076 buffer: &'a MultiBufferSnapshot,
5077 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5078 let mut i = 0;
5079 let mut regions = self.autoclose_regions.as_slice();
5080 selections.into_iter().map(move |selection| {
5081 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5082
5083 let mut enclosing = None;
5084 while let Some(pair_state) = regions.get(i) {
5085 if pair_state.range.end.to_offset(buffer) < range.start {
5086 regions = ®ions[i + 1..];
5087 i = 0;
5088 } else if pair_state.range.start.to_offset(buffer) > range.end {
5089 break;
5090 } else {
5091 if pair_state.selection_id == selection.id {
5092 enclosing = Some(pair_state);
5093 }
5094 i += 1;
5095 }
5096 }
5097
5098 (selection, enclosing)
5099 })
5100 }
5101
5102 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5103 fn invalidate_autoclose_regions(
5104 &mut self,
5105 mut selections: &[Selection<Anchor>],
5106 buffer: &MultiBufferSnapshot,
5107 ) {
5108 self.autoclose_regions.retain(|state| {
5109 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5110 return false;
5111 }
5112
5113 let mut i = 0;
5114 while let Some(selection) = selections.get(i) {
5115 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5116 selections = &selections[1..];
5117 continue;
5118 }
5119 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5120 break;
5121 }
5122 if selection.id == state.selection_id {
5123 return true;
5124 } else {
5125 i += 1;
5126 }
5127 }
5128 false
5129 });
5130 }
5131
5132 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5133 let offset = position.to_offset(buffer);
5134 let (word_range, kind) =
5135 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5136 if offset > word_range.start && kind == Some(CharKind::Word) {
5137 Some(
5138 buffer
5139 .text_for_range(word_range.start..offset)
5140 .collect::<String>(),
5141 )
5142 } else {
5143 None
5144 }
5145 }
5146
5147 pub fn visible_excerpts(
5148 &self,
5149 cx: &mut Context<Editor>,
5150 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5151 let Some(project) = self.project() else {
5152 return HashMap::default();
5153 };
5154 let project = project.read(cx);
5155 let multi_buffer = self.buffer().read(cx);
5156 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5157 let multi_buffer_visible_start = self
5158 .scroll_manager
5159 .anchor()
5160 .anchor
5161 .to_point(&multi_buffer_snapshot);
5162 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5163 multi_buffer_visible_start
5164 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5165 Bias::Left,
5166 );
5167 multi_buffer_snapshot
5168 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5169 .into_iter()
5170 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5171 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5172 let buffer_file = project::File::from_dyn(buffer.file())?;
5173 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5174 let worktree_entry = buffer_worktree
5175 .read(cx)
5176 .entry_for_id(buffer_file.project_entry_id()?)?;
5177 if worktree_entry.is_ignored {
5178 None
5179 } else {
5180 Some((
5181 excerpt_id,
5182 (
5183 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5184 buffer.version().clone(),
5185 excerpt_visible_range,
5186 ),
5187 ))
5188 }
5189 })
5190 .collect()
5191 }
5192
5193 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5194 TextLayoutDetails {
5195 text_system: window.text_system().clone(),
5196 editor_style: self.style.clone().unwrap(),
5197 rem_size: window.rem_size(),
5198 scroll_anchor: self.scroll_manager.anchor(),
5199 visible_rows: self.visible_line_count(),
5200 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5201 }
5202 }
5203
5204 fn trigger_on_type_formatting(
5205 &self,
5206 input: String,
5207 window: &mut Window,
5208 cx: &mut Context<Self>,
5209 ) -> Option<Task<Result<()>>> {
5210 if input.len() != 1 {
5211 return None;
5212 }
5213
5214 let project = self.project()?;
5215 let position = self.selections.newest_anchor().head();
5216 let (buffer, buffer_position) = self
5217 .buffer
5218 .read(cx)
5219 .text_anchor_for_position(position, cx)?;
5220
5221 let settings = language_settings::language_settings(
5222 buffer
5223 .read(cx)
5224 .language_at(buffer_position)
5225 .map(|l| l.name()),
5226 buffer.read(cx).file(),
5227 cx,
5228 );
5229 if !settings.use_on_type_format {
5230 return None;
5231 }
5232
5233 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5234 // hence we do LSP request & edit on host side only — add formats to host's history.
5235 let push_to_lsp_host_history = true;
5236 // If this is not the host, append its history with new edits.
5237 let push_to_client_history = project.read(cx).is_via_collab();
5238
5239 let on_type_formatting = project.update(cx, |project, cx| {
5240 project.on_type_format(
5241 buffer.clone(),
5242 buffer_position,
5243 input,
5244 push_to_lsp_host_history,
5245 cx,
5246 )
5247 });
5248 Some(cx.spawn_in(window, async move |editor, cx| {
5249 if let Some(transaction) = on_type_formatting.await? {
5250 if push_to_client_history {
5251 buffer
5252 .update(cx, |buffer, _| {
5253 buffer.push_transaction(transaction, Instant::now());
5254 buffer.finalize_last_transaction();
5255 })
5256 .ok();
5257 }
5258 editor.update(cx, |editor, cx| {
5259 editor.refresh_document_highlights(cx);
5260 })?;
5261 }
5262 Ok(())
5263 }))
5264 }
5265
5266 pub fn show_word_completions(
5267 &mut self,
5268 _: &ShowWordCompletions,
5269 window: &mut Window,
5270 cx: &mut Context<Self>,
5271 ) {
5272 self.open_or_update_completions_menu(
5273 Some(CompletionsMenuSource::Words {
5274 ignore_threshold: true,
5275 }),
5276 None,
5277 window,
5278 cx,
5279 );
5280 }
5281
5282 pub fn show_completions(
5283 &mut self,
5284 options: &ShowCompletions,
5285 window: &mut Window,
5286 cx: &mut Context<Self>,
5287 ) {
5288 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5289 }
5290
5291 fn open_or_update_completions_menu(
5292 &mut self,
5293 requested_source: Option<CompletionsMenuSource>,
5294 trigger: Option<&str>,
5295 window: &mut Window,
5296 cx: &mut Context<Self>,
5297 ) {
5298 if self.pending_rename.is_some() {
5299 return;
5300 }
5301
5302 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5303
5304 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5305 // inserted and selected. To handle that case, the start of the selection is used so that
5306 // the menu starts with all choices.
5307 let position = self
5308 .selections
5309 .newest_anchor()
5310 .start
5311 .bias_right(&multibuffer_snapshot);
5312 if position.diff_base_anchor.is_some() {
5313 return;
5314 }
5315 let buffer_position = multibuffer_snapshot.anchor_before(position);
5316 let Some(buffer) = buffer_position
5317 .buffer_id
5318 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5319 else {
5320 return;
5321 };
5322 let buffer_snapshot = buffer.read(cx).snapshot();
5323
5324 let query: Option<Arc<String>> =
5325 Self::completion_query(&multibuffer_snapshot, buffer_position)
5326 .map(|query| query.into());
5327
5328 drop(multibuffer_snapshot);
5329
5330 // Hide the current completions menu when query is empty. Without this, cached
5331 // completions from before the trigger char may be reused (#32774).
5332 if query.is_none() {
5333 let menu_is_open = matches!(
5334 self.context_menu.borrow().as_ref(),
5335 Some(CodeContextMenu::Completions(_))
5336 );
5337 if menu_is_open {
5338 self.hide_context_menu(window, cx);
5339 }
5340 }
5341
5342 let mut ignore_word_threshold = false;
5343 let provider = match requested_source {
5344 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5345 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5346 ignore_word_threshold = ignore_threshold;
5347 None
5348 }
5349 Some(CompletionsMenuSource::SnippetChoices) => {
5350 log::error!("bug: SnippetChoices requested_source is not handled");
5351 None
5352 }
5353 };
5354
5355 let sort_completions = provider
5356 .as_ref()
5357 .is_some_and(|provider| provider.sort_completions());
5358
5359 let filter_completions = provider
5360 .as_ref()
5361 .is_none_or(|provider| provider.filter_completions());
5362
5363 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5364 if filter_completions {
5365 menu.filter(query.clone(), provider.clone(), window, cx);
5366 }
5367 // When `is_incomplete` is false, no need to re-query completions when the current query
5368 // is a suffix of the initial query.
5369 if !menu.is_incomplete {
5370 // If the new query is a suffix of the old query (typing more characters) and
5371 // the previous result was complete, the existing completions can be filtered.
5372 //
5373 // Note that this is always true for snippet completions.
5374 let query_matches = match (&menu.initial_query, &query) {
5375 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5376 (None, _) => true,
5377 _ => false,
5378 };
5379 if query_matches {
5380 let position_matches = if menu.initial_position == position {
5381 true
5382 } else {
5383 let snapshot = self.buffer.read(cx).read(cx);
5384 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5385 };
5386 if position_matches {
5387 return;
5388 }
5389 }
5390 }
5391 };
5392
5393 let trigger_kind = match trigger {
5394 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5395 CompletionTriggerKind::TRIGGER_CHARACTER
5396 }
5397 _ => CompletionTriggerKind::INVOKED,
5398 };
5399 let completion_context = CompletionContext {
5400 trigger_character: trigger.and_then(|trigger| {
5401 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5402 Some(String::from(trigger))
5403 } else {
5404 None
5405 }
5406 }),
5407 trigger_kind,
5408 };
5409
5410 let Anchor {
5411 excerpt_id: buffer_excerpt_id,
5412 text_anchor: buffer_position,
5413 ..
5414 } = buffer_position;
5415
5416 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5417 buffer_snapshot.surrounding_word(buffer_position, None)
5418 {
5419 let word_to_exclude = buffer_snapshot
5420 .text_for_range(word_range.clone())
5421 .collect::<String>();
5422 (
5423 buffer_snapshot.anchor_before(word_range.start)
5424 ..buffer_snapshot.anchor_after(buffer_position),
5425 Some(word_to_exclude),
5426 )
5427 } else {
5428 (buffer_position..buffer_position, None)
5429 };
5430
5431 let language = buffer_snapshot
5432 .language_at(buffer_position)
5433 .map(|language| language.name());
5434
5435 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5436 .completions
5437 .clone();
5438
5439 let show_completion_documentation = buffer_snapshot
5440 .settings_at(buffer_position, cx)
5441 .show_completion_documentation;
5442
5443 // The document can be large, so stay in reasonable bounds when searching for words,
5444 // otherwise completion pop-up might be slow to appear.
5445 const WORD_LOOKUP_ROWS: u32 = 5_000;
5446 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5447 let min_word_search = buffer_snapshot.clip_point(
5448 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5449 Bias::Left,
5450 );
5451 let max_word_search = buffer_snapshot.clip_point(
5452 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5453 Bias::Right,
5454 );
5455 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5456 ..buffer_snapshot.point_to_offset(max_word_search);
5457
5458 let skip_digits = query
5459 .as_ref()
5460 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5461
5462 let omit_word_completions = !self.word_completions_enabled
5463 || (!ignore_word_threshold
5464 && match &query {
5465 Some(query) => query.chars().count() < completion_settings.words_min_length,
5466 None => completion_settings.words_min_length != 0,
5467 });
5468
5469 let (mut words, provider_responses) = match &provider {
5470 Some(provider) => {
5471 let provider_responses = provider.completions(
5472 buffer_excerpt_id,
5473 &buffer,
5474 buffer_position,
5475 completion_context,
5476 window,
5477 cx,
5478 );
5479
5480 let words = match (omit_word_completions, completion_settings.words) {
5481 (true, _) | (_, WordsCompletionMode::Disabled) => {
5482 Task::ready(BTreeMap::default())
5483 }
5484 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5485 .background_spawn(async move {
5486 buffer_snapshot.words_in_range(WordsQuery {
5487 fuzzy_contents: None,
5488 range: word_search_range,
5489 skip_digits,
5490 })
5491 }),
5492 };
5493
5494 (words, provider_responses)
5495 }
5496 None => {
5497 let words = if omit_word_completions {
5498 Task::ready(BTreeMap::default())
5499 } else {
5500 cx.background_spawn(async move {
5501 buffer_snapshot.words_in_range(WordsQuery {
5502 fuzzy_contents: None,
5503 range: word_search_range,
5504 skip_digits,
5505 })
5506 })
5507 };
5508 (words, Task::ready(Ok(Vec::new())))
5509 }
5510 };
5511
5512 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5513
5514 let id = post_inc(&mut self.next_completion_id);
5515 let task = cx.spawn_in(window, async move |editor, cx| {
5516 let Ok(()) = editor.update(cx, |this, _| {
5517 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5518 }) else {
5519 return;
5520 };
5521
5522 // TODO: Ideally completions from different sources would be selectively re-queried, so
5523 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5524 let mut completions = Vec::new();
5525 let mut is_incomplete = false;
5526 let mut display_options: Option<CompletionDisplayOptions> = None;
5527 if let Some(provider_responses) = provider_responses.await.log_err()
5528 && !provider_responses.is_empty()
5529 {
5530 for response in provider_responses {
5531 completions.extend(response.completions);
5532 is_incomplete = is_incomplete || response.is_incomplete;
5533 match display_options.as_mut() {
5534 None => {
5535 display_options = Some(response.display_options);
5536 }
5537 Some(options) => options.merge(&response.display_options),
5538 }
5539 }
5540 if completion_settings.words == WordsCompletionMode::Fallback {
5541 words = Task::ready(BTreeMap::default());
5542 }
5543 }
5544 let display_options = display_options.unwrap_or_default();
5545
5546 let mut words = words.await;
5547 if let Some(word_to_exclude) = &word_to_exclude {
5548 words.remove(word_to_exclude);
5549 }
5550 for lsp_completion in &completions {
5551 words.remove(&lsp_completion.new_text);
5552 }
5553 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5554 replace_range: word_replace_range.clone(),
5555 new_text: word.clone(),
5556 label: CodeLabel::plain(word, None),
5557 icon_path: None,
5558 documentation: None,
5559 source: CompletionSource::BufferWord {
5560 word_range,
5561 resolved: false,
5562 },
5563 insert_text_mode: Some(InsertTextMode::AS_IS),
5564 confirm: None,
5565 }));
5566
5567 let menu = if completions.is_empty() {
5568 None
5569 } else {
5570 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5571 let languages = editor
5572 .workspace
5573 .as_ref()
5574 .and_then(|(workspace, _)| workspace.upgrade())
5575 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5576 let menu = CompletionsMenu::new(
5577 id,
5578 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5579 sort_completions,
5580 show_completion_documentation,
5581 position,
5582 query.clone(),
5583 is_incomplete,
5584 buffer.clone(),
5585 completions.into(),
5586 display_options,
5587 snippet_sort_order,
5588 languages,
5589 language,
5590 cx,
5591 );
5592
5593 let query = if filter_completions { query } else { None };
5594 let matches_task = if let Some(query) = query {
5595 menu.do_async_filtering(query, cx)
5596 } else {
5597 Task::ready(menu.unfiltered_matches())
5598 };
5599 (menu, matches_task)
5600 }) else {
5601 return;
5602 };
5603
5604 let matches = matches_task.await;
5605
5606 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5607 // Newer menu already set, so exit.
5608 if let Some(CodeContextMenu::Completions(prev_menu)) =
5609 editor.context_menu.borrow().as_ref()
5610 && prev_menu.id > id
5611 {
5612 return;
5613 };
5614
5615 // Only valid to take prev_menu because it the new menu is immediately set
5616 // below, or the menu is hidden.
5617 if let Some(CodeContextMenu::Completions(prev_menu)) =
5618 editor.context_menu.borrow_mut().take()
5619 {
5620 let position_matches =
5621 if prev_menu.initial_position == menu.initial_position {
5622 true
5623 } else {
5624 let snapshot = editor.buffer.read(cx).read(cx);
5625 prev_menu.initial_position.to_offset(&snapshot)
5626 == menu.initial_position.to_offset(&snapshot)
5627 };
5628 if position_matches {
5629 // Preserve markdown cache before `set_filter_results` because it will
5630 // try to populate the documentation cache.
5631 menu.preserve_markdown_cache(prev_menu);
5632 }
5633 };
5634
5635 menu.set_filter_results(matches, provider, window, cx);
5636 }) else {
5637 return;
5638 };
5639
5640 menu.visible().then_some(menu)
5641 };
5642
5643 editor
5644 .update_in(cx, |editor, window, cx| {
5645 if editor.focus_handle.is_focused(window)
5646 && let Some(menu) = menu
5647 {
5648 *editor.context_menu.borrow_mut() =
5649 Some(CodeContextMenu::Completions(menu));
5650
5651 crate::hover_popover::hide_hover(editor, cx);
5652 if editor.show_edit_predictions_in_menu() {
5653 editor.update_visible_edit_prediction(window, cx);
5654 } else {
5655 editor.discard_edit_prediction(false, cx);
5656 }
5657
5658 cx.notify();
5659 return;
5660 }
5661
5662 if editor.completion_tasks.len() <= 1 {
5663 // If there are no more completion tasks and the last menu was empty, we should hide it.
5664 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5665 // If it was already hidden and we don't show edit predictions in the menu,
5666 // we should also show the edit prediction when available.
5667 if was_hidden && editor.show_edit_predictions_in_menu() {
5668 editor.update_visible_edit_prediction(window, cx);
5669 }
5670 }
5671 })
5672 .ok();
5673 });
5674
5675 self.completion_tasks.push((id, task));
5676 }
5677
5678 #[cfg(feature = "test-support")]
5679 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5680 let menu = self.context_menu.borrow();
5681 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5682 let completions = menu.completions.borrow();
5683 Some(completions.to_vec())
5684 } else {
5685 None
5686 }
5687 }
5688
5689 pub fn with_completions_menu_matching_id<R>(
5690 &self,
5691 id: CompletionId,
5692 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5693 ) -> R {
5694 let mut context_menu = self.context_menu.borrow_mut();
5695 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5696 return f(None);
5697 };
5698 if completions_menu.id != id {
5699 return f(None);
5700 }
5701 f(Some(completions_menu))
5702 }
5703
5704 pub fn confirm_completion(
5705 &mut self,
5706 action: &ConfirmCompletion,
5707 window: &mut Window,
5708 cx: &mut Context<Self>,
5709 ) -> Option<Task<Result<()>>> {
5710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5711 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5712 }
5713
5714 pub fn confirm_completion_insert(
5715 &mut self,
5716 _: &ConfirmCompletionInsert,
5717 window: &mut Window,
5718 cx: &mut Context<Self>,
5719 ) -> Option<Task<Result<()>>> {
5720 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5721 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5722 }
5723
5724 pub fn confirm_completion_replace(
5725 &mut self,
5726 _: &ConfirmCompletionReplace,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) -> Option<Task<Result<()>>> {
5730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5731 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5732 }
5733
5734 pub fn compose_completion(
5735 &mut self,
5736 action: &ComposeCompletion,
5737 window: &mut Window,
5738 cx: &mut Context<Self>,
5739 ) -> Option<Task<Result<()>>> {
5740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5741 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5742 }
5743
5744 fn do_completion(
5745 &mut self,
5746 item_ix: Option<usize>,
5747 intent: CompletionIntent,
5748 window: &mut Window,
5749 cx: &mut Context<Editor>,
5750 ) -> Option<Task<Result<()>>> {
5751 use language::ToOffset as _;
5752
5753 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5754 else {
5755 return None;
5756 };
5757
5758 let candidate_id = {
5759 let entries = completions_menu.entries.borrow();
5760 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5761 if self.show_edit_predictions_in_menu() {
5762 self.discard_edit_prediction(true, cx);
5763 }
5764 mat.candidate_id
5765 };
5766
5767 let completion = completions_menu
5768 .completions
5769 .borrow()
5770 .get(candidate_id)?
5771 .clone();
5772 cx.stop_propagation();
5773
5774 let buffer_handle = completions_menu.buffer.clone();
5775
5776 let CompletionEdit {
5777 new_text,
5778 snippet,
5779 replace_range,
5780 } = process_completion_for_edit(
5781 &completion,
5782 intent,
5783 &buffer_handle,
5784 &completions_menu.initial_position.text_anchor,
5785 cx,
5786 );
5787
5788 let buffer = buffer_handle.read(cx);
5789 let snapshot = self.buffer.read(cx).snapshot(cx);
5790 let newest_anchor = self.selections.newest_anchor();
5791 let replace_range_multibuffer = {
5792 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5793 excerpt.map_range_from_buffer(replace_range.clone())
5794 };
5795 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5796 return None;
5797 }
5798
5799 let old_text = buffer
5800 .text_for_range(replace_range.clone())
5801 .collect::<String>();
5802 let lookbehind = newest_anchor
5803 .start
5804 .text_anchor
5805 .to_offset(buffer)
5806 .saturating_sub(replace_range.start);
5807 let lookahead = replace_range
5808 .end
5809 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5810 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5811 let suffix = &old_text[lookbehind.min(old_text.len())..];
5812
5813 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5814 let mut ranges = Vec::new();
5815 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5816
5817 for selection in &selections {
5818 let range = if selection.id == newest_anchor.id {
5819 replace_range_multibuffer.clone()
5820 } else {
5821 let mut range = selection.range();
5822
5823 // if prefix is present, don't duplicate it
5824 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5825 range.start = range.start.saturating_sub(lookbehind);
5826
5827 // if suffix is also present, mimic the newest cursor and replace it
5828 if selection.id != newest_anchor.id
5829 && snapshot.contains_str_at(range.end, suffix)
5830 {
5831 range.end += lookahead;
5832 }
5833 }
5834 range
5835 };
5836
5837 ranges.push(range.clone());
5838
5839 if !self.linked_edit_ranges.is_empty() {
5840 let start_anchor = snapshot.anchor_before(range.start);
5841 let end_anchor = snapshot.anchor_after(range.end);
5842 if let Some(ranges) = self
5843 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5844 {
5845 for (buffer, edits) in ranges {
5846 linked_edits
5847 .entry(buffer.clone())
5848 .or_default()
5849 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5850 }
5851 }
5852 }
5853 }
5854
5855 let common_prefix_len = old_text
5856 .chars()
5857 .zip(new_text.chars())
5858 .take_while(|(a, b)| a == b)
5859 .map(|(a, _)| a.len_utf8())
5860 .sum::<usize>();
5861
5862 cx.emit(EditorEvent::InputHandled {
5863 utf16_range_to_replace: None,
5864 text: new_text[common_prefix_len..].into(),
5865 });
5866
5867 self.transact(window, cx, |editor, window, cx| {
5868 if let Some(mut snippet) = snippet {
5869 snippet.text = new_text.to_string();
5870 editor
5871 .insert_snippet(&ranges, snippet, window, cx)
5872 .log_err();
5873 } else {
5874 editor.buffer.update(cx, |multi_buffer, cx| {
5875 let auto_indent = match completion.insert_text_mode {
5876 Some(InsertTextMode::AS_IS) => None,
5877 _ => editor.autoindent_mode.clone(),
5878 };
5879 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5880 multi_buffer.edit(edits, auto_indent, cx);
5881 });
5882 }
5883 for (buffer, edits) in linked_edits {
5884 buffer.update(cx, |buffer, cx| {
5885 let snapshot = buffer.snapshot();
5886 let edits = edits
5887 .into_iter()
5888 .map(|(range, text)| {
5889 use text::ToPoint as TP;
5890 let end_point = TP::to_point(&range.end, &snapshot);
5891 let start_point = TP::to_point(&range.start, &snapshot);
5892 (start_point..end_point, text)
5893 })
5894 .sorted_by_key(|(range, _)| range.start);
5895 buffer.edit(edits, None, cx);
5896 })
5897 }
5898
5899 editor.refresh_edit_prediction(true, false, window, cx);
5900 });
5901 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5902
5903 let show_new_completions_on_confirm = completion
5904 .confirm
5905 .as_ref()
5906 .is_some_and(|confirm| confirm(intent, window, cx));
5907 if show_new_completions_on_confirm {
5908 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5909 }
5910
5911 let provider = self.completion_provider.as_ref()?;
5912 drop(completion);
5913 let apply_edits = provider.apply_additional_edits_for_completion(
5914 buffer_handle,
5915 completions_menu.completions.clone(),
5916 candidate_id,
5917 true,
5918 cx,
5919 );
5920
5921 let editor_settings = EditorSettings::get_global(cx);
5922 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5923 // After the code completion is finished, users often want to know what signatures are needed.
5924 // so we should automatically call signature_help
5925 self.show_signature_help(&ShowSignatureHelp, window, cx);
5926 }
5927
5928 Some(cx.foreground_executor().spawn(async move {
5929 apply_edits.await?;
5930 Ok(())
5931 }))
5932 }
5933
5934 pub fn toggle_code_actions(
5935 &mut self,
5936 action: &ToggleCodeActions,
5937 window: &mut Window,
5938 cx: &mut Context<Self>,
5939 ) {
5940 let quick_launch = action.quick_launch;
5941 let mut context_menu = self.context_menu.borrow_mut();
5942 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5943 if code_actions.deployed_from == action.deployed_from {
5944 // Toggle if we're selecting the same one
5945 *context_menu = None;
5946 cx.notify();
5947 return;
5948 } else {
5949 // Otherwise, clear it and start a new one
5950 *context_menu = None;
5951 cx.notify();
5952 }
5953 }
5954 drop(context_menu);
5955 let snapshot = self.snapshot(window, cx);
5956 let deployed_from = action.deployed_from.clone();
5957 let action = action.clone();
5958 self.completion_tasks.clear();
5959 self.discard_edit_prediction(false, cx);
5960
5961 let multibuffer_point = match &action.deployed_from {
5962 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5963 DisplayPoint::new(*row, 0).to_point(&snapshot)
5964 }
5965 _ => self
5966 .selections
5967 .newest::<Point>(&snapshot.display_snapshot)
5968 .head(),
5969 };
5970 let Some((buffer, buffer_row)) = snapshot
5971 .buffer_snapshot()
5972 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5973 .and_then(|(buffer_snapshot, range)| {
5974 self.buffer()
5975 .read(cx)
5976 .buffer(buffer_snapshot.remote_id())
5977 .map(|buffer| (buffer, range.start.row))
5978 })
5979 else {
5980 return;
5981 };
5982 let buffer_id = buffer.read(cx).remote_id();
5983 let tasks = self
5984 .tasks
5985 .get(&(buffer_id, buffer_row))
5986 .map(|t| Arc::new(t.to_owned()));
5987
5988 if !self.focus_handle.is_focused(window) {
5989 return;
5990 }
5991 let project = self.project.clone();
5992
5993 let code_actions_task = match deployed_from {
5994 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5995 _ => self.code_actions(buffer_row, window, cx),
5996 };
5997
5998 let runnable_task = match deployed_from {
5999 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6000 _ => {
6001 let mut task_context_task = Task::ready(None);
6002 if let Some(tasks) = &tasks
6003 && let Some(project) = project
6004 {
6005 task_context_task =
6006 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6007 }
6008
6009 cx.spawn_in(window, {
6010 let buffer = buffer.clone();
6011 async move |editor, cx| {
6012 let task_context = task_context_task.await;
6013
6014 let resolved_tasks =
6015 tasks
6016 .zip(task_context.clone())
6017 .map(|(tasks, task_context)| ResolvedTasks {
6018 templates: tasks.resolve(&task_context).collect(),
6019 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6020 multibuffer_point.row,
6021 tasks.column,
6022 )),
6023 });
6024 let debug_scenarios = editor
6025 .update(cx, |editor, cx| {
6026 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6027 })?
6028 .await;
6029 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6030 }
6031 })
6032 }
6033 };
6034
6035 cx.spawn_in(window, async move |editor, cx| {
6036 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6037 let code_actions = code_actions_task.await;
6038 let spawn_straight_away = quick_launch
6039 && resolved_tasks
6040 .as_ref()
6041 .is_some_and(|tasks| tasks.templates.len() == 1)
6042 && code_actions
6043 .as_ref()
6044 .is_none_or(|actions| actions.is_empty())
6045 && debug_scenarios.is_empty();
6046
6047 editor.update_in(cx, |editor, window, cx| {
6048 crate::hover_popover::hide_hover(editor, cx);
6049 let actions = CodeActionContents::new(
6050 resolved_tasks,
6051 code_actions,
6052 debug_scenarios,
6053 task_context.unwrap_or_default(),
6054 );
6055
6056 // Don't show the menu if there are no actions available
6057 if actions.is_empty() {
6058 cx.notify();
6059 return Task::ready(Ok(()));
6060 }
6061
6062 *editor.context_menu.borrow_mut() =
6063 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6064 buffer,
6065 actions,
6066 selected_item: Default::default(),
6067 scroll_handle: UniformListScrollHandle::default(),
6068 deployed_from,
6069 }));
6070 cx.notify();
6071 if spawn_straight_away
6072 && let Some(task) = editor.confirm_code_action(
6073 &ConfirmCodeAction { item_ix: Some(0) },
6074 window,
6075 cx,
6076 )
6077 {
6078 return task;
6079 }
6080
6081 Task::ready(Ok(()))
6082 })
6083 })
6084 .detach_and_log_err(cx);
6085 }
6086
6087 fn debug_scenarios(
6088 &mut self,
6089 resolved_tasks: &Option<ResolvedTasks>,
6090 buffer: &Entity<Buffer>,
6091 cx: &mut App,
6092 ) -> Task<Vec<task::DebugScenario>> {
6093 maybe!({
6094 let project = self.project()?;
6095 let dap_store = project.read(cx).dap_store();
6096 let mut scenarios = vec![];
6097 let resolved_tasks = resolved_tasks.as_ref()?;
6098 let buffer = buffer.read(cx);
6099 let language = buffer.language()?;
6100 let file = buffer.file();
6101 let debug_adapter = language_settings(language.name().into(), file, cx)
6102 .debuggers
6103 .first()
6104 .map(SharedString::from)
6105 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6106
6107 dap_store.update(cx, |dap_store, cx| {
6108 for (_, task) in &resolved_tasks.templates {
6109 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6110 task.original_task().clone(),
6111 debug_adapter.clone().into(),
6112 task.display_label().to_owned().into(),
6113 cx,
6114 );
6115 scenarios.push(maybe_scenario);
6116 }
6117 });
6118 Some(cx.background_spawn(async move {
6119 futures::future::join_all(scenarios)
6120 .await
6121 .into_iter()
6122 .flatten()
6123 .collect::<Vec<_>>()
6124 }))
6125 })
6126 .unwrap_or_else(|| Task::ready(vec![]))
6127 }
6128
6129 fn code_actions(
6130 &mut self,
6131 buffer_row: u32,
6132 window: &mut Window,
6133 cx: &mut Context<Self>,
6134 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6135 let mut task = self.code_actions_task.take();
6136 cx.spawn_in(window, async move |editor, cx| {
6137 while let Some(prev_task) = task {
6138 prev_task.await.log_err();
6139 task = editor
6140 .update(cx, |this, _| this.code_actions_task.take())
6141 .ok()?;
6142 }
6143
6144 editor
6145 .update(cx, |editor, cx| {
6146 editor
6147 .available_code_actions
6148 .clone()
6149 .and_then(|(location, code_actions)| {
6150 let snapshot = location.buffer.read(cx).snapshot();
6151 let point_range = location.range.to_point(&snapshot);
6152 let point_range = point_range.start.row..=point_range.end.row;
6153 if point_range.contains(&buffer_row) {
6154 Some(code_actions)
6155 } else {
6156 None
6157 }
6158 })
6159 })
6160 .ok()
6161 .flatten()
6162 })
6163 }
6164
6165 pub fn confirm_code_action(
6166 &mut self,
6167 action: &ConfirmCodeAction,
6168 window: &mut Window,
6169 cx: &mut Context<Self>,
6170 ) -> Option<Task<Result<()>>> {
6171 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6172
6173 let actions_menu =
6174 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6175 menu
6176 } else {
6177 return None;
6178 };
6179
6180 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6181 let action = actions_menu.actions.get(action_ix)?;
6182 let title = action.label();
6183 let buffer = actions_menu.buffer;
6184 let workspace = self.workspace()?;
6185
6186 match action {
6187 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6188 workspace.update(cx, |workspace, cx| {
6189 workspace.schedule_resolved_task(
6190 task_source_kind,
6191 resolved_task,
6192 false,
6193 window,
6194 cx,
6195 );
6196
6197 Some(Task::ready(Ok(())))
6198 })
6199 }
6200 CodeActionsItem::CodeAction {
6201 excerpt_id,
6202 action,
6203 provider,
6204 } => {
6205 let apply_code_action =
6206 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6207 let workspace = workspace.downgrade();
6208 Some(cx.spawn_in(window, async move |editor, cx| {
6209 let project_transaction = apply_code_action.await?;
6210 Self::open_project_transaction(
6211 &editor,
6212 workspace,
6213 project_transaction,
6214 title,
6215 cx,
6216 )
6217 .await
6218 }))
6219 }
6220 CodeActionsItem::DebugScenario(scenario) => {
6221 let context = actions_menu.actions.context;
6222
6223 workspace.update(cx, |workspace, cx| {
6224 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6225 workspace.start_debug_session(
6226 scenario,
6227 context,
6228 Some(buffer),
6229 None,
6230 window,
6231 cx,
6232 );
6233 });
6234 Some(Task::ready(Ok(())))
6235 }
6236 }
6237 }
6238
6239 pub async fn open_project_transaction(
6240 editor: &WeakEntity<Editor>,
6241 workspace: WeakEntity<Workspace>,
6242 transaction: ProjectTransaction,
6243 title: String,
6244 cx: &mut AsyncWindowContext,
6245 ) -> Result<()> {
6246 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6247 cx.update(|_, cx| {
6248 entries.sort_unstable_by_key(|(buffer, _)| {
6249 buffer.read(cx).file().map(|f| f.path().clone())
6250 });
6251 })?;
6252 if entries.is_empty() {
6253 return Ok(());
6254 }
6255
6256 // If the project transaction's edits are all contained within this editor, then
6257 // avoid opening a new editor to display them.
6258
6259 if let [(buffer, transaction)] = &*entries {
6260 let excerpt = editor.update(cx, |editor, cx| {
6261 editor
6262 .buffer()
6263 .read(cx)
6264 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6265 })?;
6266 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6267 && excerpted_buffer == *buffer
6268 {
6269 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6270 let excerpt_range = excerpt_range.to_offset(buffer);
6271 buffer
6272 .edited_ranges_for_transaction::<usize>(transaction)
6273 .all(|range| {
6274 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6275 })
6276 })?;
6277
6278 if all_edits_within_excerpt {
6279 return Ok(());
6280 }
6281 }
6282 }
6283
6284 let mut ranges_to_highlight = Vec::new();
6285 let excerpt_buffer = cx.new(|cx| {
6286 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6287 for (buffer_handle, transaction) in &entries {
6288 let edited_ranges = buffer_handle
6289 .read(cx)
6290 .edited_ranges_for_transaction::<Point>(transaction)
6291 .collect::<Vec<_>>();
6292 let (ranges, _) = multibuffer.set_excerpts_for_path(
6293 PathKey::for_buffer(buffer_handle, cx),
6294 buffer_handle.clone(),
6295 edited_ranges,
6296 multibuffer_context_lines(cx),
6297 cx,
6298 );
6299
6300 ranges_to_highlight.extend(ranges);
6301 }
6302 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6303 multibuffer
6304 })?;
6305
6306 workspace.update_in(cx, |workspace, window, cx| {
6307 let project = workspace.project().clone();
6308 let editor =
6309 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6310 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6311 editor.update(cx, |editor, cx| {
6312 editor.highlight_background::<Self>(
6313 &ranges_to_highlight,
6314 |theme| theme.colors().editor_highlighted_line_background,
6315 cx,
6316 );
6317 });
6318 })?;
6319
6320 Ok(())
6321 }
6322
6323 pub fn clear_code_action_providers(&mut self) {
6324 self.code_action_providers.clear();
6325 self.available_code_actions.take();
6326 }
6327
6328 pub fn add_code_action_provider(
6329 &mut self,
6330 provider: Rc<dyn CodeActionProvider>,
6331 window: &mut Window,
6332 cx: &mut Context<Self>,
6333 ) {
6334 if self
6335 .code_action_providers
6336 .iter()
6337 .any(|existing_provider| existing_provider.id() == provider.id())
6338 {
6339 return;
6340 }
6341
6342 self.code_action_providers.push(provider);
6343 self.refresh_code_actions(window, cx);
6344 }
6345
6346 pub fn remove_code_action_provider(
6347 &mut self,
6348 id: Arc<str>,
6349 window: &mut Window,
6350 cx: &mut Context<Self>,
6351 ) {
6352 self.code_action_providers
6353 .retain(|provider| provider.id() != id);
6354 self.refresh_code_actions(window, cx);
6355 }
6356
6357 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6358 !self.code_action_providers.is_empty()
6359 && EditorSettings::get_global(cx).toolbar.code_actions
6360 }
6361
6362 pub fn has_available_code_actions(&self) -> bool {
6363 self.available_code_actions
6364 .as_ref()
6365 .is_some_and(|(_, actions)| !actions.is_empty())
6366 }
6367
6368 fn render_inline_code_actions(
6369 &self,
6370 icon_size: ui::IconSize,
6371 display_row: DisplayRow,
6372 is_active: bool,
6373 cx: &mut Context<Self>,
6374 ) -> AnyElement {
6375 let show_tooltip = !self.context_menu_visible();
6376 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6377 .icon_size(icon_size)
6378 .shape(ui::IconButtonShape::Square)
6379 .icon_color(ui::Color::Hidden)
6380 .toggle_state(is_active)
6381 .when(show_tooltip, |this| {
6382 this.tooltip({
6383 let focus_handle = self.focus_handle.clone();
6384 move |_window, cx| {
6385 Tooltip::for_action_in(
6386 "Toggle Code Actions",
6387 &ToggleCodeActions {
6388 deployed_from: None,
6389 quick_launch: false,
6390 },
6391 &focus_handle,
6392 cx,
6393 )
6394 }
6395 })
6396 })
6397 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6398 window.focus(&editor.focus_handle(cx));
6399 editor.toggle_code_actions(
6400 &crate::actions::ToggleCodeActions {
6401 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6402 display_row,
6403 )),
6404 quick_launch: false,
6405 },
6406 window,
6407 cx,
6408 );
6409 }))
6410 .into_any_element()
6411 }
6412
6413 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6414 &self.context_menu
6415 }
6416
6417 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6418 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6419 cx.background_executor()
6420 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6421 .await;
6422
6423 let (start_buffer, start, _, end, newest_selection) = this
6424 .update(cx, |this, cx| {
6425 let newest_selection = this.selections.newest_anchor().clone();
6426 if newest_selection.head().diff_base_anchor.is_some() {
6427 return None;
6428 }
6429 let display_snapshot = this.display_snapshot(cx);
6430 let newest_selection_adjusted =
6431 this.selections.newest_adjusted(&display_snapshot);
6432 let buffer = this.buffer.read(cx);
6433
6434 let (start_buffer, start) =
6435 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6436 let (end_buffer, end) =
6437 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6438
6439 Some((start_buffer, start, end_buffer, end, newest_selection))
6440 })?
6441 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6442 .context(
6443 "Expected selection to lie in a single buffer when refreshing code actions",
6444 )?;
6445 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6446 let providers = this.code_action_providers.clone();
6447 let tasks = this
6448 .code_action_providers
6449 .iter()
6450 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6451 .collect::<Vec<_>>();
6452 (providers, tasks)
6453 })?;
6454
6455 let mut actions = Vec::new();
6456 for (provider, provider_actions) in
6457 providers.into_iter().zip(future::join_all(tasks).await)
6458 {
6459 if let Some(provider_actions) = provider_actions.log_err() {
6460 actions.extend(provider_actions.into_iter().map(|action| {
6461 AvailableCodeAction {
6462 excerpt_id: newest_selection.start.excerpt_id,
6463 action,
6464 provider: provider.clone(),
6465 }
6466 }));
6467 }
6468 }
6469
6470 this.update(cx, |this, cx| {
6471 this.available_code_actions = if actions.is_empty() {
6472 None
6473 } else {
6474 Some((
6475 Location {
6476 buffer: start_buffer,
6477 range: start..end,
6478 },
6479 actions.into(),
6480 ))
6481 };
6482 cx.notify();
6483 })
6484 }));
6485 }
6486
6487 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6488 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6489 self.show_git_blame_inline = false;
6490
6491 self.show_git_blame_inline_delay_task =
6492 Some(cx.spawn_in(window, async move |this, cx| {
6493 cx.background_executor().timer(delay).await;
6494
6495 this.update(cx, |this, cx| {
6496 this.show_git_blame_inline = true;
6497 cx.notify();
6498 })
6499 .log_err();
6500 }));
6501 }
6502 }
6503
6504 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6505 let snapshot = self.snapshot(window, cx);
6506 let cursor = self
6507 .selections
6508 .newest::<Point>(&snapshot.display_snapshot)
6509 .head();
6510 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6511 else {
6512 return;
6513 };
6514
6515 let Some(blame) = self.blame.as_ref() else {
6516 return;
6517 };
6518
6519 let row_info = RowInfo {
6520 buffer_id: Some(buffer.remote_id()),
6521 buffer_row: Some(point.row),
6522 ..Default::default()
6523 };
6524 let Some((buffer, blame_entry)) = blame
6525 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6526 .flatten()
6527 else {
6528 return;
6529 };
6530
6531 let anchor = self.selections.newest_anchor().head();
6532 let position = self.to_pixel_point(anchor, &snapshot, window);
6533 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6534 self.show_blame_popover(
6535 buffer,
6536 &blame_entry,
6537 position + last_bounds.origin,
6538 true,
6539 cx,
6540 );
6541 };
6542 }
6543
6544 fn show_blame_popover(
6545 &mut self,
6546 buffer: BufferId,
6547 blame_entry: &BlameEntry,
6548 position: gpui::Point<Pixels>,
6549 ignore_timeout: bool,
6550 cx: &mut Context<Self>,
6551 ) {
6552 if let Some(state) = &mut self.inline_blame_popover {
6553 state.hide_task.take();
6554 } else {
6555 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6556 let blame_entry = blame_entry.clone();
6557 let show_task = cx.spawn(async move |editor, cx| {
6558 if !ignore_timeout {
6559 cx.background_executor()
6560 .timer(std::time::Duration::from_millis(blame_popover_delay))
6561 .await;
6562 }
6563 editor
6564 .update(cx, |editor, cx| {
6565 editor.inline_blame_popover_show_task.take();
6566 let Some(blame) = editor.blame.as_ref() else {
6567 return;
6568 };
6569 let blame = blame.read(cx);
6570 let details = blame.details_for_entry(buffer, &blame_entry);
6571 let markdown = cx.new(|cx| {
6572 Markdown::new(
6573 details
6574 .as_ref()
6575 .map(|message| message.message.clone())
6576 .unwrap_or_default(),
6577 None,
6578 None,
6579 cx,
6580 )
6581 });
6582 editor.inline_blame_popover = Some(InlineBlamePopover {
6583 position,
6584 hide_task: None,
6585 popover_bounds: None,
6586 popover_state: InlineBlamePopoverState {
6587 scroll_handle: ScrollHandle::new(),
6588 commit_message: details,
6589 markdown,
6590 },
6591 keyboard_grace: ignore_timeout,
6592 });
6593 cx.notify();
6594 })
6595 .ok();
6596 });
6597 self.inline_blame_popover_show_task = Some(show_task);
6598 }
6599 }
6600
6601 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6602 self.inline_blame_popover_show_task.take();
6603 if let Some(state) = &mut self.inline_blame_popover {
6604 let hide_task = cx.spawn(async move |editor, cx| {
6605 if !ignore_timeout {
6606 cx.background_executor()
6607 .timer(std::time::Duration::from_millis(100))
6608 .await;
6609 }
6610 editor
6611 .update(cx, |editor, cx| {
6612 editor.inline_blame_popover.take();
6613 cx.notify();
6614 })
6615 .ok();
6616 });
6617 state.hide_task = Some(hide_task);
6618 true
6619 } else {
6620 false
6621 }
6622 }
6623
6624 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6625 if self.pending_rename.is_some() {
6626 return None;
6627 }
6628
6629 let provider = self.semantics_provider.clone()?;
6630 let buffer = self.buffer.read(cx);
6631 let newest_selection = self.selections.newest_anchor().clone();
6632 let cursor_position = newest_selection.head();
6633 let (cursor_buffer, cursor_buffer_position) =
6634 buffer.text_anchor_for_position(cursor_position, cx)?;
6635 let (tail_buffer, tail_buffer_position) =
6636 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6637 if cursor_buffer != tail_buffer {
6638 return None;
6639 }
6640
6641 let snapshot = cursor_buffer.read(cx).snapshot();
6642 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6643 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6644 if start_word_range != end_word_range {
6645 self.document_highlights_task.take();
6646 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6647 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6648 return None;
6649 }
6650
6651 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6652 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6653 cx.background_executor()
6654 .timer(Duration::from_millis(debounce))
6655 .await;
6656
6657 let highlights = if let Some(highlights) = cx
6658 .update(|cx| {
6659 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6660 })
6661 .ok()
6662 .flatten()
6663 {
6664 highlights.await.log_err()
6665 } else {
6666 None
6667 };
6668
6669 if let Some(highlights) = highlights {
6670 this.update(cx, |this, cx| {
6671 if this.pending_rename.is_some() {
6672 return;
6673 }
6674
6675 let buffer = this.buffer.read(cx);
6676 if buffer
6677 .text_anchor_for_position(cursor_position, cx)
6678 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6679 {
6680 return;
6681 }
6682
6683 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6684 let mut write_ranges = Vec::new();
6685 let mut read_ranges = Vec::new();
6686 for highlight in highlights {
6687 let buffer_id = cursor_buffer.read(cx).remote_id();
6688 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6689 {
6690 let start = highlight
6691 .range
6692 .start
6693 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6694 let end = highlight
6695 .range
6696 .end
6697 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6698 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6699 continue;
6700 }
6701
6702 let range =
6703 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6704 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6705 write_ranges.push(range);
6706 } else {
6707 read_ranges.push(range);
6708 }
6709 }
6710 }
6711
6712 this.highlight_background::<DocumentHighlightRead>(
6713 &read_ranges,
6714 |theme| theme.colors().editor_document_highlight_read_background,
6715 cx,
6716 );
6717 this.highlight_background::<DocumentHighlightWrite>(
6718 &write_ranges,
6719 |theme| theme.colors().editor_document_highlight_write_background,
6720 cx,
6721 );
6722 cx.notify();
6723 })
6724 .log_err();
6725 }
6726 }));
6727 None
6728 }
6729
6730 fn prepare_highlight_query_from_selection(
6731 &mut self,
6732 window: &Window,
6733 cx: &mut Context<Editor>,
6734 ) -> Option<(String, Range<Anchor>)> {
6735 if matches!(self.mode, EditorMode::SingleLine) {
6736 return None;
6737 }
6738 if !EditorSettings::get_global(cx).selection_highlight {
6739 return None;
6740 }
6741 if self.selections.count() != 1 || self.selections.line_mode() {
6742 return None;
6743 }
6744 let snapshot = self.snapshot(window, cx);
6745 let selection = self.selections.newest::<Point>(&snapshot);
6746 // If the selection spans multiple rows OR it is empty
6747 if selection.start.row != selection.end.row
6748 || selection.start.column == selection.end.column
6749 {
6750 return None;
6751 }
6752 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6753 let query = snapshot
6754 .buffer_snapshot()
6755 .text_for_range(selection_anchor_range.clone())
6756 .collect::<String>();
6757 if query.trim().is_empty() {
6758 return None;
6759 }
6760 Some((query, selection_anchor_range))
6761 }
6762
6763 fn update_selection_occurrence_highlights(
6764 &mut self,
6765 query_text: String,
6766 query_range: Range<Anchor>,
6767 multi_buffer_range_to_query: Range<Point>,
6768 use_debounce: bool,
6769 window: &mut Window,
6770 cx: &mut Context<Editor>,
6771 ) -> Task<()> {
6772 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6773 cx.spawn_in(window, async move |editor, cx| {
6774 if use_debounce {
6775 cx.background_executor()
6776 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6777 .await;
6778 }
6779 let match_task = cx.background_spawn(async move {
6780 let buffer_ranges = multi_buffer_snapshot
6781 .range_to_buffer_ranges(multi_buffer_range_to_query)
6782 .into_iter()
6783 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6784 let mut match_ranges = Vec::new();
6785 let Ok(regex) = project::search::SearchQuery::text(
6786 query_text.clone(),
6787 false,
6788 false,
6789 false,
6790 Default::default(),
6791 Default::default(),
6792 false,
6793 None,
6794 ) else {
6795 return Vec::default();
6796 };
6797 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6798 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6799 match_ranges.extend(
6800 regex
6801 .search(buffer_snapshot, Some(search_range.clone()))
6802 .await
6803 .into_iter()
6804 .filter_map(|match_range| {
6805 let match_start = buffer_snapshot
6806 .anchor_after(search_range.start + match_range.start);
6807 let match_end = buffer_snapshot
6808 .anchor_before(search_range.start + match_range.end);
6809 let match_anchor_range = Anchor::range_in_buffer(
6810 excerpt_id,
6811 buffer_snapshot.remote_id(),
6812 match_start..match_end,
6813 );
6814 (match_anchor_range != query_range).then_some(match_anchor_range)
6815 }),
6816 );
6817 }
6818 match_ranges
6819 });
6820 let match_ranges = match_task.await;
6821 editor
6822 .update_in(cx, |editor, _, cx| {
6823 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6824 if !match_ranges.is_empty() {
6825 editor.highlight_background::<SelectedTextHighlight>(
6826 &match_ranges,
6827 |theme| theme.colors().editor_document_highlight_bracket_background,
6828 cx,
6829 )
6830 }
6831 })
6832 .log_err();
6833 })
6834 }
6835
6836 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6837 struct NewlineFold;
6838 let type_id = std::any::TypeId::of::<NewlineFold>();
6839 if !self.mode.is_single_line() {
6840 return;
6841 }
6842 let snapshot = self.snapshot(window, cx);
6843 if snapshot.buffer_snapshot().max_point().row == 0 {
6844 return;
6845 }
6846 let task = cx.background_spawn(async move {
6847 let new_newlines = snapshot
6848 .buffer_chars_at(0)
6849 .filter_map(|(c, i)| {
6850 if c == '\n' {
6851 Some(
6852 snapshot.buffer_snapshot().anchor_after(i)
6853 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6854 )
6855 } else {
6856 None
6857 }
6858 })
6859 .collect::<Vec<_>>();
6860 let existing_newlines = snapshot
6861 .folds_in_range(0..snapshot.buffer_snapshot().len())
6862 .filter_map(|fold| {
6863 if fold.placeholder.type_tag == Some(type_id) {
6864 Some(fold.range.start..fold.range.end)
6865 } else {
6866 None
6867 }
6868 })
6869 .collect::<Vec<_>>();
6870
6871 (new_newlines, existing_newlines)
6872 });
6873 self.folding_newlines = cx.spawn(async move |this, cx| {
6874 let (new_newlines, existing_newlines) = task.await;
6875 if new_newlines == existing_newlines {
6876 return;
6877 }
6878 let placeholder = FoldPlaceholder {
6879 render: Arc::new(move |_, _, cx| {
6880 div()
6881 .bg(cx.theme().status().hint_background)
6882 .border_b_1()
6883 .size_full()
6884 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6885 .border_color(cx.theme().status().hint)
6886 .child("\\n")
6887 .into_any()
6888 }),
6889 constrain_width: false,
6890 merge_adjacent: false,
6891 type_tag: Some(type_id),
6892 };
6893 let creases = new_newlines
6894 .into_iter()
6895 .map(|range| Crease::simple(range, placeholder.clone()))
6896 .collect();
6897 this.update(cx, |this, cx| {
6898 this.display_map.update(cx, |display_map, cx| {
6899 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6900 display_map.fold(creases, cx);
6901 });
6902 })
6903 .ok();
6904 });
6905 }
6906
6907 fn refresh_selected_text_highlights(
6908 &mut self,
6909 on_buffer_edit: bool,
6910 window: &mut Window,
6911 cx: &mut Context<Editor>,
6912 ) {
6913 let Some((query_text, query_range)) =
6914 self.prepare_highlight_query_from_selection(window, cx)
6915 else {
6916 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6917 self.quick_selection_highlight_task.take();
6918 self.debounced_selection_highlight_task.take();
6919 return;
6920 };
6921 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6922 if on_buffer_edit
6923 || self
6924 .quick_selection_highlight_task
6925 .as_ref()
6926 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6927 {
6928 let multi_buffer_visible_start = self
6929 .scroll_manager
6930 .anchor()
6931 .anchor
6932 .to_point(&multi_buffer_snapshot);
6933 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6934 multi_buffer_visible_start
6935 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6936 Bias::Left,
6937 );
6938 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6939 self.quick_selection_highlight_task = Some((
6940 query_range.clone(),
6941 self.update_selection_occurrence_highlights(
6942 query_text.clone(),
6943 query_range.clone(),
6944 multi_buffer_visible_range,
6945 false,
6946 window,
6947 cx,
6948 ),
6949 ));
6950 }
6951 if on_buffer_edit
6952 || self
6953 .debounced_selection_highlight_task
6954 .as_ref()
6955 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6956 {
6957 let multi_buffer_start = multi_buffer_snapshot
6958 .anchor_before(0)
6959 .to_point(&multi_buffer_snapshot);
6960 let multi_buffer_end = multi_buffer_snapshot
6961 .anchor_after(multi_buffer_snapshot.len())
6962 .to_point(&multi_buffer_snapshot);
6963 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6964 self.debounced_selection_highlight_task = Some((
6965 query_range.clone(),
6966 self.update_selection_occurrence_highlights(
6967 query_text,
6968 query_range,
6969 multi_buffer_full_range,
6970 true,
6971 window,
6972 cx,
6973 ),
6974 ));
6975 }
6976 }
6977
6978 pub fn refresh_edit_prediction(
6979 &mut self,
6980 debounce: bool,
6981 user_requested: bool,
6982 window: &mut Window,
6983 cx: &mut Context<Self>,
6984 ) -> Option<()> {
6985 if DisableAiSettings::get_global(cx).disable_ai {
6986 return None;
6987 }
6988
6989 let provider = self.edit_prediction_provider()?;
6990 let cursor = self.selections.newest_anchor().head();
6991 let (buffer, cursor_buffer_position) =
6992 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6993
6994 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6995 self.discard_edit_prediction(false, cx);
6996 return None;
6997 }
6998
6999 self.update_visible_edit_prediction(window, cx);
7000
7001 if !user_requested
7002 && (!self.should_show_edit_predictions()
7003 || !self.is_focused(window)
7004 || buffer.read(cx).is_empty())
7005 {
7006 self.discard_edit_prediction(false, cx);
7007 return None;
7008 }
7009
7010 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7011 Some(())
7012 }
7013
7014 fn show_edit_predictions_in_menu(&self) -> bool {
7015 match self.edit_prediction_settings {
7016 EditPredictionSettings::Disabled => false,
7017 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7018 }
7019 }
7020
7021 pub fn edit_predictions_enabled(&self) -> bool {
7022 match self.edit_prediction_settings {
7023 EditPredictionSettings::Disabled => false,
7024 EditPredictionSettings::Enabled { .. } => true,
7025 }
7026 }
7027
7028 fn edit_prediction_requires_modifier(&self) -> bool {
7029 match self.edit_prediction_settings {
7030 EditPredictionSettings::Disabled => false,
7031 EditPredictionSettings::Enabled {
7032 preview_requires_modifier,
7033 ..
7034 } => preview_requires_modifier,
7035 }
7036 }
7037
7038 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7039 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7040 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7041 self.discard_edit_prediction(false, cx);
7042 } else {
7043 let selection = self.selections.newest_anchor();
7044 let cursor = selection.head();
7045
7046 if let Some((buffer, cursor_buffer_position)) =
7047 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7048 {
7049 self.edit_prediction_settings =
7050 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7051 }
7052 }
7053 }
7054
7055 fn edit_prediction_settings_at_position(
7056 &self,
7057 buffer: &Entity<Buffer>,
7058 buffer_position: language::Anchor,
7059 cx: &App,
7060 ) -> EditPredictionSettings {
7061 if !self.mode.is_full()
7062 || !self.show_edit_predictions_override.unwrap_or(true)
7063 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7064 {
7065 return EditPredictionSettings::Disabled;
7066 }
7067
7068 let buffer = buffer.read(cx);
7069
7070 let file = buffer.file();
7071
7072 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7073 return EditPredictionSettings::Disabled;
7074 };
7075
7076 let by_provider = matches!(
7077 self.menu_edit_predictions_policy,
7078 MenuEditPredictionsPolicy::ByProvider
7079 );
7080
7081 let show_in_menu = by_provider
7082 && self
7083 .edit_prediction_provider
7084 .as_ref()
7085 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7086
7087 let preview_requires_modifier =
7088 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7089
7090 EditPredictionSettings::Enabled {
7091 show_in_menu,
7092 preview_requires_modifier,
7093 }
7094 }
7095
7096 fn should_show_edit_predictions(&self) -> bool {
7097 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7098 }
7099
7100 pub fn edit_prediction_preview_is_active(&self) -> bool {
7101 matches!(
7102 self.edit_prediction_preview,
7103 EditPredictionPreview::Active { .. }
7104 )
7105 }
7106
7107 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7108 let cursor = self.selections.newest_anchor().head();
7109 if let Some((buffer, cursor_position)) =
7110 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7111 {
7112 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7113 } else {
7114 false
7115 }
7116 }
7117
7118 pub fn supports_minimap(&self, cx: &App) -> bool {
7119 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7120 }
7121
7122 fn edit_predictions_enabled_in_buffer(
7123 &self,
7124 buffer: &Entity<Buffer>,
7125 buffer_position: language::Anchor,
7126 cx: &App,
7127 ) -> bool {
7128 maybe!({
7129 if self.read_only(cx) {
7130 return Some(false);
7131 }
7132 let provider = self.edit_prediction_provider()?;
7133 if !provider.is_enabled(buffer, buffer_position, cx) {
7134 return Some(false);
7135 }
7136 let buffer = buffer.read(cx);
7137 let Some(file) = buffer.file() else {
7138 return Some(true);
7139 };
7140 let settings = all_language_settings(Some(file), cx);
7141 Some(settings.edit_predictions_enabled_for_file(file, cx))
7142 })
7143 .unwrap_or(false)
7144 }
7145
7146 fn cycle_edit_prediction(
7147 &mut self,
7148 direction: Direction,
7149 window: &mut Window,
7150 cx: &mut Context<Self>,
7151 ) -> Option<()> {
7152 let provider = self.edit_prediction_provider()?;
7153 let cursor = self.selections.newest_anchor().head();
7154 let (buffer, cursor_buffer_position) =
7155 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7156 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7157 return None;
7158 }
7159
7160 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7161 self.update_visible_edit_prediction(window, cx);
7162
7163 Some(())
7164 }
7165
7166 pub fn show_edit_prediction(
7167 &mut self,
7168 _: &ShowEditPrediction,
7169 window: &mut Window,
7170 cx: &mut Context<Self>,
7171 ) {
7172 if !self.has_active_edit_prediction() {
7173 self.refresh_edit_prediction(false, true, window, cx);
7174 return;
7175 }
7176
7177 self.update_visible_edit_prediction(window, cx);
7178 }
7179
7180 pub fn display_cursor_names(
7181 &mut self,
7182 _: &DisplayCursorNames,
7183 window: &mut Window,
7184 cx: &mut Context<Self>,
7185 ) {
7186 self.show_cursor_names(window, cx);
7187 }
7188
7189 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7190 self.show_cursor_names = true;
7191 cx.notify();
7192 cx.spawn_in(window, async move |this, cx| {
7193 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7194 this.update(cx, |this, cx| {
7195 this.show_cursor_names = false;
7196 cx.notify()
7197 })
7198 .ok()
7199 })
7200 .detach();
7201 }
7202
7203 pub fn next_edit_prediction(
7204 &mut self,
7205 _: &NextEditPrediction,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 if self.has_active_edit_prediction() {
7210 self.cycle_edit_prediction(Direction::Next, window, cx);
7211 } else {
7212 let is_copilot_disabled = self
7213 .refresh_edit_prediction(false, true, window, cx)
7214 .is_none();
7215 if is_copilot_disabled {
7216 cx.propagate();
7217 }
7218 }
7219 }
7220
7221 pub fn previous_edit_prediction(
7222 &mut self,
7223 _: &PreviousEditPrediction,
7224 window: &mut Window,
7225 cx: &mut Context<Self>,
7226 ) {
7227 if self.has_active_edit_prediction() {
7228 self.cycle_edit_prediction(Direction::Prev, window, cx);
7229 } else {
7230 let is_copilot_disabled = self
7231 .refresh_edit_prediction(false, true, window, cx)
7232 .is_none();
7233 if is_copilot_disabled {
7234 cx.propagate();
7235 }
7236 }
7237 }
7238
7239 pub fn accept_edit_prediction(
7240 &mut self,
7241 _: &AcceptEditPrediction,
7242 window: &mut Window,
7243 cx: &mut Context<Self>,
7244 ) {
7245 if self.show_edit_predictions_in_menu() {
7246 self.hide_context_menu(window, cx);
7247 }
7248
7249 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7250 return;
7251 };
7252
7253 match &active_edit_prediction.completion {
7254 EditPrediction::MoveWithin { target, .. } => {
7255 let target = *target;
7256
7257 if let Some(position_map) = &self.last_position_map {
7258 if position_map
7259 .visible_row_range
7260 .contains(&target.to_display_point(&position_map.snapshot).row())
7261 || !self.edit_prediction_requires_modifier()
7262 {
7263 self.unfold_ranges(&[target..target], true, false, cx);
7264 // Note that this is also done in vim's handler of the Tab action.
7265 self.change_selections(
7266 SelectionEffects::scroll(Autoscroll::newest()),
7267 window,
7268 cx,
7269 |selections| {
7270 selections.select_anchor_ranges([target..target]);
7271 },
7272 );
7273 self.clear_row_highlights::<EditPredictionPreview>();
7274
7275 self.edit_prediction_preview
7276 .set_previous_scroll_position(None);
7277 } else {
7278 self.edit_prediction_preview
7279 .set_previous_scroll_position(Some(
7280 position_map.snapshot.scroll_anchor,
7281 ));
7282
7283 self.highlight_rows::<EditPredictionPreview>(
7284 target..target,
7285 cx.theme().colors().editor_highlighted_line_background,
7286 RowHighlightOptions {
7287 autoscroll: true,
7288 ..Default::default()
7289 },
7290 cx,
7291 );
7292 self.request_autoscroll(Autoscroll::fit(), cx);
7293 }
7294 }
7295 }
7296 EditPrediction::MoveOutside { snapshot, target } => {
7297 if let Some(workspace) = self.workspace() {
7298 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7299 .detach_and_log_err(cx);
7300 }
7301 }
7302 EditPrediction::Edit { edits, .. } => {
7303 self.report_edit_prediction_event(
7304 active_edit_prediction.completion_id.clone(),
7305 true,
7306 cx,
7307 );
7308
7309 if let Some(provider) = self.edit_prediction_provider() {
7310 provider.accept(cx);
7311 }
7312
7313 // Store the transaction ID and selections before applying the edit
7314 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7315
7316 let snapshot = self.buffer.read(cx).snapshot(cx);
7317 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7318
7319 self.buffer.update(cx, |buffer, cx| {
7320 buffer.edit(edits.iter().cloned(), None, cx)
7321 });
7322
7323 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7324 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7325 });
7326
7327 let selections = self.selections.disjoint_anchors_arc();
7328 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7329 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7330 if has_new_transaction {
7331 self.selection_history
7332 .insert_transaction(transaction_id_now, selections);
7333 }
7334 }
7335
7336 self.update_visible_edit_prediction(window, cx);
7337 if self.active_edit_prediction.is_none() {
7338 self.refresh_edit_prediction(true, true, window, cx);
7339 }
7340
7341 cx.notify();
7342 }
7343 }
7344
7345 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7346 }
7347
7348 pub fn accept_partial_edit_prediction(
7349 &mut self,
7350 _: &AcceptPartialEditPrediction,
7351 window: &mut Window,
7352 cx: &mut Context<Self>,
7353 ) {
7354 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7355 return;
7356 };
7357 if self.selections.count() != 1 {
7358 return;
7359 }
7360
7361 match &active_edit_prediction.completion {
7362 EditPrediction::MoveWithin { target, .. } => {
7363 let target = *target;
7364 self.change_selections(
7365 SelectionEffects::scroll(Autoscroll::newest()),
7366 window,
7367 cx,
7368 |selections| {
7369 selections.select_anchor_ranges([target..target]);
7370 },
7371 );
7372 }
7373 EditPrediction::MoveOutside { snapshot, target } => {
7374 if let Some(workspace) = self.workspace() {
7375 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7376 .detach_and_log_err(cx);
7377 }
7378 }
7379 EditPrediction::Edit { edits, .. } => {
7380 self.report_edit_prediction_event(
7381 active_edit_prediction.completion_id.clone(),
7382 true,
7383 cx,
7384 );
7385
7386 // Find an insertion that starts at the cursor position.
7387 let snapshot = self.buffer.read(cx).snapshot(cx);
7388 let cursor_offset = self
7389 .selections
7390 .newest::<usize>(&self.display_snapshot(cx))
7391 .head();
7392 let insertion = edits.iter().find_map(|(range, text)| {
7393 let range = range.to_offset(&snapshot);
7394 if range.is_empty() && range.start == cursor_offset {
7395 Some(text)
7396 } else {
7397 None
7398 }
7399 });
7400
7401 if let Some(text) = insertion {
7402 let mut partial_completion = text
7403 .chars()
7404 .by_ref()
7405 .take_while(|c| c.is_alphabetic())
7406 .collect::<String>();
7407 if partial_completion.is_empty() {
7408 partial_completion = text
7409 .chars()
7410 .by_ref()
7411 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7412 .collect::<String>();
7413 }
7414
7415 cx.emit(EditorEvent::InputHandled {
7416 utf16_range_to_replace: None,
7417 text: partial_completion.clone().into(),
7418 });
7419
7420 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7421
7422 self.refresh_edit_prediction(true, true, window, cx);
7423 cx.notify();
7424 } else {
7425 self.accept_edit_prediction(&Default::default(), window, cx);
7426 }
7427 }
7428 }
7429 }
7430
7431 fn discard_edit_prediction(
7432 &mut self,
7433 should_report_edit_prediction_event: bool,
7434 cx: &mut Context<Self>,
7435 ) -> bool {
7436 if should_report_edit_prediction_event {
7437 let completion_id = self
7438 .active_edit_prediction
7439 .as_ref()
7440 .and_then(|active_completion| active_completion.completion_id.clone());
7441
7442 self.report_edit_prediction_event(completion_id, false, cx);
7443 }
7444
7445 if let Some(provider) = self.edit_prediction_provider() {
7446 provider.discard(cx);
7447 }
7448
7449 self.take_active_edit_prediction(cx)
7450 }
7451
7452 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7453 let Some(provider) = self.edit_prediction_provider() else {
7454 return;
7455 };
7456
7457 let Some((_, buffer, _)) = self
7458 .buffer
7459 .read(cx)
7460 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7461 else {
7462 return;
7463 };
7464
7465 let extension = buffer
7466 .read(cx)
7467 .file()
7468 .and_then(|file| Some(file.path().extension()?.to_string()));
7469
7470 let event_type = match accepted {
7471 true => "Edit Prediction Accepted",
7472 false => "Edit Prediction Discarded",
7473 };
7474 telemetry::event!(
7475 event_type,
7476 provider = provider.name(),
7477 prediction_id = id,
7478 suggestion_accepted = accepted,
7479 file_extension = extension,
7480 );
7481 }
7482
7483 fn open_editor_at_anchor(
7484 snapshot: &language::BufferSnapshot,
7485 target: language::Anchor,
7486 workspace: &Entity<Workspace>,
7487 window: &mut Window,
7488 cx: &mut App,
7489 ) -> Task<Result<()>> {
7490 workspace.update(cx, |workspace, cx| {
7491 let path = snapshot.file().map(|file| file.full_path(cx));
7492 let Some(path) =
7493 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7494 else {
7495 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7496 };
7497 let target = text::ToPoint::to_point(&target, snapshot);
7498 let item = workspace.open_path(path, None, true, window, cx);
7499 window.spawn(cx, async move |cx| {
7500 let Some(editor) = item.await?.downcast::<Editor>() else {
7501 return Ok(());
7502 };
7503 editor
7504 .update_in(cx, |editor, window, cx| {
7505 editor.go_to_singleton_buffer_point(target, window, cx);
7506 })
7507 .ok();
7508 anyhow::Ok(())
7509 })
7510 })
7511 }
7512
7513 pub fn has_active_edit_prediction(&self) -> bool {
7514 self.active_edit_prediction.is_some()
7515 }
7516
7517 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7518 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7519 return false;
7520 };
7521
7522 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7523 self.clear_highlights::<EditPredictionHighlight>(cx);
7524 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7525 true
7526 }
7527
7528 /// Returns true when we're displaying the edit prediction popover below the cursor
7529 /// like we are not previewing and the LSP autocomplete menu is visible
7530 /// or we are in `when_holding_modifier` mode.
7531 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7532 if self.edit_prediction_preview_is_active()
7533 || !self.show_edit_predictions_in_menu()
7534 || !self.edit_predictions_enabled()
7535 {
7536 return false;
7537 }
7538
7539 if self.has_visible_completions_menu() {
7540 return true;
7541 }
7542
7543 has_completion && self.edit_prediction_requires_modifier()
7544 }
7545
7546 fn handle_modifiers_changed(
7547 &mut self,
7548 modifiers: Modifiers,
7549 position_map: &PositionMap,
7550 window: &mut Window,
7551 cx: &mut Context<Self>,
7552 ) {
7553 if self.show_edit_predictions_in_menu() {
7554 self.update_edit_prediction_preview(&modifiers, window, cx);
7555 }
7556
7557 self.update_selection_mode(&modifiers, position_map, window, cx);
7558
7559 let mouse_position = window.mouse_position();
7560 if !position_map.text_hitbox.is_hovered(window) {
7561 return;
7562 }
7563
7564 self.update_hovered_link(
7565 position_map.point_for_position(mouse_position),
7566 &position_map.snapshot,
7567 modifiers,
7568 window,
7569 cx,
7570 )
7571 }
7572
7573 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7574 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7575 if invert {
7576 match multi_cursor_setting {
7577 MultiCursorModifier::Alt => modifiers.alt,
7578 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7579 }
7580 } else {
7581 match multi_cursor_setting {
7582 MultiCursorModifier::Alt => modifiers.secondary(),
7583 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7584 }
7585 }
7586 }
7587
7588 fn columnar_selection_mode(
7589 modifiers: &Modifiers,
7590 cx: &mut Context<Self>,
7591 ) -> Option<ColumnarMode> {
7592 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7593 if Self::multi_cursor_modifier(false, modifiers, cx) {
7594 Some(ColumnarMode::FromMouse)
7595 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7596 Some(ColumnarMode::FromSelection)
7597 } else {
7598 None
7599 }
7600 } else {
7601 None
7602 }
7603 }
7604
7605 fn update_selection_mode(
7606 &mut self,
7607 modifiers: &Modifiers,
7608 position_map: &PositionMap,
7609 window: &mut Window,
7610 cx: &mut Context<Self>,
7611 ) {
7612 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7613 return;
7614 };
7615 if self.selections.pending_anchor().is_none() {
7616 return;
7617 }
7618
7619 let mouse_position = window.mouse_position();
7620 let point_for_position = position_map.point_for_position(mouse_position);
7621 let position = point_for_position.previous_valid;
7622
7623 self.select(
7624 SelectPhase::BeginColumnar {
7625 position,
7626 reset: false,
7627 mode,
7628 goal_column: point_for_position.exact_unclipped.column(),
7629 },
7630 window,
7631 cx,
7632 );
7633 }
7634
7635 fn update_edit_prediction_preview(
7636 &mut self,
7637 modifiers: &Modifiers,
7638 window: &mut Window,
7639 cx: &mut Context<Self>,
7640 ) {
7641 let mut modifiers_held = false;
7642 if let Some(accept_keystroke) = self
7643 .accept_edit_prediction_keybind(false, window, cx)
7644 .keystroke()
7645 {
7646 modifiers_held = modifiers_held
7647 || (accept_keystroke.modifiers() == modifiers
7648 && accept_keystroke.modifiers().modified());
7649 };
7650 if let Some(accept_partial_keystroke) = self
7651 .accept_edit_prediction_keybind(true, window, cx)
7652 .keystroke()
7653 {
7654 modifiers_held = modifiers_held
7655 || (accept_partial_keystroke.modifiers() == modifiers
7656 && accept_partial_keystroke.modifiers().modified());
7657 }
7658
7659 if modifiers_held {
7660 if matches!(
7661 self.edit_prediction_preview,
7662 EditPredictionPreview::Inactive { .. }
7663 ) {
7664 self.edit_prediction_preview = EditPredictionPreview::Active {
7665 previous_scroll_position: None,
7666 since: Instant::now(),
7667 };
7668
7669 self.update_visible_edit_prediction(window, cx);
7670 cx.notify();
7671 }
7672 } else if let EditPredictionPreview::Active {
7673 previous_scroll_position,
7674 since,
7675 } = self.edit_prediction_preview
7676 {
7677 if let (Some(previous_scroll_position), Some(position_map)) =
7678 (previous_scroll_position, self.last_position_map.as_ref())
7679 {
7680 self.set_scroll_position(
7681 previous_scroll_position
7682 .scroll_position(&position_map.snapshot.display_snapshot),
7683 window,
7684 cx,
7685 );
7686 }
7687
7688 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7689 released_too_fast: since.elapsed() < Duration::from_millis(200),
7690 };
7691 self.clear_row_highlights::<EditPredictionPreview>();
7692 self.update_visible_edit_prediction(window, cx);
7693 cx.notify();
7694 }
7695 }
7696
7697 fn update_visible_edit_prediction(
7698 &mut self,
7699 _window: &mut Window,
7700 cx: &mut Context<Self>,
7701 ) -> Option<()> {
7702 if DisableAiSettings::get_global(cx).disable_ai {
7703 return None;
7704 }
7705
7706 if self.ime_transaction.is_some() {
7707 self.discard_edit_prediction(false, cx);
7708 return None;
7709 }
7710
7711 let selection = self.selections.newest_anchor();
7712 let cursor = selection.head();
7713 let multibuffer = self.buffer.read(cx).snapshot(cx);
7714 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7715 let excerpt_id = cursor.excerpt_id;
7716
7717 let show_in_menu = self.show_edit_predictions_in_menu();
7718 let completions_menu_has_precedence = !show_in_menu
7719 && (self.context_menu.borrow().is_some()
7720 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7721
7722 if completions_menu_has_precedence
7723 || !offset_selection.is_empty()
7724 || self
7725 .active_edit_prediction
7726 .as_ref()
7727 .is_some_and(|completion| {
7728 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7729 return false;
7730 };
7731 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7732 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7733 !invalidation_range.contains(&offset_selection.head())
7734 })
7735 {
7736 self.discard_edit_prediction(false, cx);
7737 return None;
7738 }
7739
7740 self.take_active_edit_prediction(cx);
7741 let Some(provider) = self.edit_prediction_provider() else {
7742 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7743 return None;
7744 };
7745
7746 let (buffer, cursor_buffer_position) =
7747 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7748
7749 self.edit_prediction_settings =
7750 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7751
7752 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7753
7754 if self.edit_prediction_indent_conflict {
7755 let cursor_point = cursor.to_point(&multibuffer);
7756
7757 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7758
7759 if let Some((_, indent)) = indents.iter().next()
7760 && indent.len == cursor_point.column
7761 {
7762 self.edit_prediction_indent_conflict = false;
7763 }
7764 }
7765
7766 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7767
7768 let (completion_id, edits, edit_preview) = match edit_prediction {
7769 edit_prediction::EditPrediction::Local {
7770 id,
7771 edits,
7772 edit_preview,
7773 } => (id, edits, edit_preview),
7774 edit_prediction::EditPrediction::Jump {
7775 id,
7776 snapshot,
7777 target,
7778 } => {
7779 self.stale_edit_prediction_in_menu = None;
7780 self.active_edit_prediction = Some(EditPredictionState {
7781 inlay_ids: vec![],
7782 completion: EditPrediction::MoveOutside { snapshot, target },
7783 completion_id: id,
7784 invalidation_range: None,
7785 });
7786 cx.notify();
7787 return Some(());
7788 }
7789 };
7790
7791 let edits = edits
7792 .into_iter()
7793 .flat_map(|(range, new_text)| {
7794 Some((
7795 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7796 new_text,
7797 ))
7798 })
7799 .collect::<Vec<_>>();
7800 if edits.is_empty() {
7801 return None;
7802 }
7803
7804 let first_edit_start = edits.first().unwrap().0.start;
7805 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7806 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7807
7808 let last_edit_end = edits.last().unwrap().0.end;
7809 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7810 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7811
7812 let cursor_row = cursor.to_point(&multibuffer).row;
7813
7814 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7815
7816 let mut inlay_ids = Vec::new();
7817 let invalidation_row_range;
7818 let move_invalidation_row_range = if cursor_row < edit_start_row {
7819 Some(cursor_row..edit_end_row)
7820 } else if cursor_row > edit_end_row {
7821 Some(edit_start_row..cursor_row)
7822 } else {
7823 None
7824 };
7825 let supports_jump = self
7826 .edit_prediction_provider
7827 .as_ref()
7828 .map(|provider| provider.provider.supports_jump_to_edit())
7829 .unwrap_or(true);
7830
7831 let is_move = supports_jump
7832 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7833 let completion = if is_move {
7834 invalidation_row_range =
7835 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7836 let target = first_edit_start;
7837 EditPrediction::MoveWithin { target, snapshot }
7838 } else {
7839 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7840 && !self.edit_predictions_hidden_for_vim_mode;
7841
7842 if show_completions_in_buffer {
7843 if edits
7844 .iter()
7845 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7846 {
7847 let mut inlays = Vec::new();
7848 for (range, new_text) in &edits {
7849 let inlay = Inlay::edit_prediction(
7850 post_inc(&mut self.next_inlay_id),
7851 range.start,
7852 Rope::from_str_small(new_text.as_str()),
7853 );
7854 inlay_ids.push(inlay.id);
7855 inlays.push(inlay);
7856 }
7857
7858 self.splice_inlays(&[], inlays, cx);
7859 } else {
7860 let background_color = cx.theme().status().deleted_background;
7861 self.highlight_text::<EditPredictionHighlight>(
7862 edits.iter().map(|(range, _)| range.clone()).collect(),
7863 HighlightStyle {
7864 background_color: Some(background_color),
7865 ..Default::default()
7866 },
7867 cx,
7868 );
7869 }
7870 }
7871
7872 invalidation_row_range = edit_start_row..edit_end_row;
7873
7874 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7875 if provider.show_tab_accept_marker() {
7876 EditDisplayMode::TabAccept
7877 } else {
7878 EditDisplayMode::Inline
7879 }
7880 } else {
7881 EditDisplayMode::DiffPopover
7882 };
7883
7884 EditPrediction::Edit {
7885 edits,
7886 edit_preview,
7887 display_mode,
7888 snapshot,
7889 }
7890 };
7891
7892 let invalidation_range = multibuffer
7893 .anchor_before(Point::new(invalidation_row_range.start, 0))
7894 ..multibuffer.anchor_after(Point::new(
7895 invalidation_row_range.end,
7896 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7897 ));
7898
7899 self.stale_edit_prediction_in_menu = None;
7900 self.active_edit_prediction = Some(EditPredictionState {
7901 inlay_ids,
7902 completion,
7903 completion_id,
7904 invalidation_range: Some(invalidation_range),
7905 });
7906
7907 cx.notify();
7908
7909 Some(())
7910 }
7911
7912 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7913 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7914 }
7915
7916 fn clear_tasks(&mut self) {
7917 self.tasks.clear()
7918 }
7919
7920 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7921 if self.tasks.insert(key, value).is_some() {
7922 // This case should hopefully be rare, but just in case...
7923 log::error!(
7924 "multiple different run targets found on a single line, only the last target will be rendered"
7925 )
7926 }
7927 }
7928
7929 /// Get all display points of breakpoints that will be rendered within editor
7930 ///
7931 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7932 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7933 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7934 fn active_breakpoints(
7935 &self,
7936 range: Range<DisplayRow>,
7937 window: &mut Window,
7938 cx: &mut Context<Self>,
7939 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7940 let mut breakpoint_display_points = HashMap::default();
7941
7942 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7943 return breakpoint_display_points;
7944 };
7945
7946 let snapshot = self.snapshot(window, cx);
7947
7948 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7949 let Some(project) = self.project() else {
7950 return breakpoint_display_points;
7951 };
7952
7953 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7954 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7955
7956 for (buffer_snapshot, range, excerpt_id) in
7957 multi_buffer_snapshot.range_to_buffer_ranges(range)
7958 {
7959 let Some(buffer) = project
7960 .read(cx)
7961 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7962 else {
7963 continue;
7964 };
7965 let breakpoints = breakpoint_store.read(cx).breakpoints(
7966 &buffer,
7967 Some(
7968 buffer_snapshot.anchor_before(range.start)
7969 ..buffer_snapshot.anchor_after(range.end),
7970 ),
7971 buffer_snapshot,
7972 cx,
7973 );
7974 for (breakpoint, state) in breakpoints {
7975 let multi_buffer_anchor =
7976 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7977 let position = multi_buffer_anchor
7978 .to_point(&multi_buffer_snapshot)
7979 .to_display_point(&snapshot);
7980
7981 breakpoint_display_points.insert(
7982 position.row(),
7983 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7984 );
7985 }
7986 }
7987
7988 breakpoint_display_points
7989 }
7990
7991 fn breakpoint_context_menu(
7992 &self,
7993 anchor: Anchor,
7994 window: &mut Window,
7995 cx: &mut Context<Self>,
7996 ) -> Entity<ui::ContextMenu> {
7997 let weak_editor = cx.weak_entity();
7998 let focus_handle = self.focus_handle(cx);
7999
8000 let row = self
8001 .buffer
8002 .read(cx)
8003 .snapshot(cx)
8004 .summary_for_anchor::<Point>(&anchor)
8005 .row;
8006
8007 let breakpoint = self
8008 .breakpoint_at_row(row, window, cx)
8009 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8010
8011 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8012 "Edit Log Breakpoint"
8013 } else {
8014 "Set Log Breakpoint"
8015 };
8016
8017 let condition_breakpoint_msg = if breakpoint
8018 .as_ref()
8019 .is_some_and(|bp| bp.1.condition.is_some())
8020 {
8021 "Edit Condition Breakpoint"
8022 } else {
8023 "Set Condition Breakpoint"
8024 };
8025
8026 let hit_condition_breakpoint_msg = if breakpoint
8027 .as_ref()
8028 .is_some_and(|bp| bp.1.hit_condition.is_some())
8029 {
8030 "Edit Hit Condition Breakpoint"
8031 } else {
8032 "Set Hit Condition Breakpoint"
8033 };
8034
8035 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8036 "Unset Breakpoint"
8037 } else {
8038 "Set Breakpoint"
8039 };
8040
8041 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8042
8043 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8044 BreakpointState::Enabled => Some("Disable"),
8045 BreakpointState::Disabled => Some("Enable"),
8046 });
8047
8048 let (anchor, breakpoint) =
8049 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8050
8051 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8052 menu.on_blur_subscription(Subscription::new(|| {}))
8053 .context(focus_handle)
8054 .when(run_to_cursor, |this| {
8055 let weak_editor = weak_editor.clone();
8056 this.entry("Run to cursor", None, move |window, cx| {
8057 weak_editor
8058 .update(cx, |editor, cx| {
8059 editor.change_selections(
8060 SelectionEffects::no_scroll(),
8061 window,
8062 cx,
8063 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8064 );
8065 })
8066 .ok();
8067
8068 window.dispatch_action(Box::new(RunToCursor), cx);
8069 })
8070 .separator()
8071 })
8072 .when_some(toggle_state_msg, |this, msg| {
8073 this.entry(msg, None, {
8074 let weak_editor = weak_editor.clone();
8075 let breakpoint = breakpoint.clone();
8076 move |_window, cx| {
8077 weak_editor
8078 .update(cx, |this, cx| {
8079 this.edit_breakpoint_at_anchor(
8080 anchor,
8081 breakpoint.as_ref().clone(),
8082 BreakpointEditAction::InvertState,
8083 cx,
8084 );
8085 })
8086 .log_err();
8087 }
8088 })
8089 })
8090 .entry(set_breakpoint_msg, None, {
8091 let weak_editor = weak_editor.clone();
8092 let breakpoint = breakpoint.clone();
8093 move |_window, cx| {
8094 weak_editor
8095 .update(cx, |this, cx| {
8096 this.edit_breakpoint_at_anchor(
8097 anchor,
8098 breakpoint.as_ref().clone(),
8099 BreakpointEditAction::Toggle,
8100 cx,
8101 );
8102 })
8103 .log_err();
8104 }
8105 })
8106 .entry(log_breakpoint_msg, None, {
8107 let breakpoint = breakpoint.clone();
8108 let weak_editor = weak_editor.clone();
8109 move |window, cx| {
8110 weak_editor
8111 .update(cx, |this, cx| {
8112 this.add_edit_breakpoint_block(
8113 anchor,
8114 breakpoint.as_ref(),
8115 BreakpointPromptEditAction::Log,
8116 window,
8117 cx,
8118 );
8119 })
8120 .log_err();
8121 }
8122 })
8123 .entry(condition_breakpoint_msg, None, {
8124 let breakpoint = breakpoint.clone();
8125 let weak_editor = weak_editor.clone();
8126 move |window, cx| {
8127 weak_editor
8128 .update(cx, |this, cx| {
8129 this.add_edit_breakpoint_block(
8130 anchor,
8131 breakpoint.as_ref(),
8132 BreakpointPromptEditAction::Condition,
8133 window,
8134 cx,
8135 );
8136 })
8137 .log_err();
8138 }
8139 })
8140 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8141 weak_editor
8142 .update(cx, |this, cx| {
8143 this.add_edit_breakpoint_block(
8144 anchor,
8145 breakpoint.as_ref(),
8146 BreakpointPromptEditAction::HitCondition,
8147 window,
8148 cx,
8149 );
8150 })
8151 .log_err();
8152 })
8153 })
8154 }
8155
8156 fn render_breakpoint(
8157 &self,
8158 position: Anchor,
8159 row: DisplayRow,
8160 breakpoint: &Breakpoint,
8161 state: Option<BreakpointSessionState>,
8162 cx: &mut Context<Self>,
8163 ) -> IconButton {
8164 let is_rejected = state.is_some_and(|s| !s.verified);
8165 // Is it a breakpoint that shows up when hovering over gutter?
8166 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8167 (false, false),
8168 |PhantomBreakpointIndicator {
8169 is_active,
8170 display_row,
8171 collides_with_existing_breakpoint,
8172 }| {
8173 (
8174 is_active && display_row == row,
8175 collides_with_existing_breakpoint,
8176 )
8177 },
8178 );
8179
8180 let (color, icon) = {
8181 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8182 (false, false) => ui::IconName::DebugBreakpoint,
8183 (true, false) => ui::IconName::DebugLogBreakpoint,
8184 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8185 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8186 };
8187
8188 let color = if is_phantom {
8189 Color::Hint
8190 } else if is_rejected {
8191 Color::Disabled
8192 } else {
8193 Color::Debugger
8194 };
8195
8196 (color, icon)
8197 };
8198
8199 let breakpoint = Arc::from(breakpoint.clone());
8200
8201 let alt_as_text = gpui::Keystroke {
8202 modifiers: Modifiers::secondary_key(),
8203 ..Default::default()
8204 };
8205 let primary_action_text = if breakpoint.is_disabled() {
8206 "Enable breakpoint"
8207 } else if is_phantom && !collides_with_existing {
8208 "Set breakpoint"
8209 } else {
8210 "Unset breakpoint"
8211 };
8212 let focus_handle = self.focus_handle.clone();
8213
8214 let meta = if is_rejected {
8215 SharedString::from("No executable code is associated with this line.")
8216 } else if collides_with_existing && !breakpoint.is_disabled() {
8217 SharedString::from(format!(
8218 "{alt_as_text}-click to disable,\nright-click for more options."
8219 ))
8220 } else {
8221 SharedString::from("Right-click for more options.")
8222 };
8223 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8224 .icon_size(IconSize::XSmall)
8225 .size(ui::ButtonSize::None)
8226 .when(is_rejected, |this| {
8227 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8228 })
8229 .icon_color(color)
8230 .style(ButtonStyle::Transparent)
8231 .on_click(cx.listener({
8232 move |editor, event: &ClickEvent, window, cx| {
8233 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8234 BreakpointEditAction::InvertState
8235 } else {
8236 BreakpointEditAction::Toggle
8237 };
8238
8239 window.focus(&editor.focus_handle(cx));
8240 editor.edit_breakpoint_at_anchor(
8241 position,
8242 breakpoint.as_ref().clone(),
8243 edit_action,
8244 cx,
8245 );
8246 }
8247 }))
8248 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8249 editor.set_breakpoint_context_menu(
8250 row,
8251 Some(position),
8252 event.position(),
8253 window,
8254 cx,
8255 );
8256 }))
8257 .tooltip(move |_window, cx| {
8258 Tooltip::with_meta_in(
8259 primary_action_text,
8260 Some(&ToggleBreakpoint),
8261 meta.clone(),
8262 &focus_handle,
8263 cx,
8264 )
8265 })
8266 }
8267
8268 fn build_tasks_context(
8269 project: &Entity<Project>,
8270 buffer: &Entity<Buffer>,
8271 buffer_row: u32,
8272 tasks: &Arc<RunnableTasks>,
8273 cx: &mut Context<Self>,
8274 ) -> Task<Option<task::TaskContext>> {
8275 let position = Point::new(buffer_row, tasks.column);
8276 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8277 let location = Location {
8278 buffer: buffer.clone(),
8279 range: range_start..range_start,
8280 };
8281 // Fill in the environmental variables from the tree-sitter captures
8282 let mut captured_task_variables = TaskVariables::default();
8283 for (capture_name, value) in tasks.extra_variables.clone() {
8284 captured_task_variables.insert(
8285 task::VariableName::Custom(capture_name.into()),
8286 value.clone(),
8287 );
8288 }
8289 project.update(cx, |project, cx| {
8290 project.task_store().update(cx, |task_store, cx| {
8291 task_store.task_context_for_location(captured_task_variables, location, cx)
8292 })
8293 })
8294 }
8295
8296 pub fn spawn_nearest_task(
8297 &mut self,
8298 action: &SpawnNearestTask,
8299 window: &mut Window,
8300 cx: &mut Context<Self>,
8301 ) {
8302 let Some((workspace, _)) = self.workspace.clone() else {
8303 return;
8304 };
8305 let Some(project) = self.project.clone() else {
8306 return;
8307 };
8308
8309 // Try to find a closest, enclosing node using tree-sitter that has a task
8310 let Some((buffer, buffer_row, tasks)) = self
8311 .find_enclosing_node_task(cx)
8312 // Or find the task that's closest in row-distance.
8313 .or_else(|| self.find_closest_task(cx))
8314 else {
8315 return;
8316 };
8317
8318 let reveal_strategy = action.reveal;
8319 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8320 cx.spawn_in(window, async move |_, cx| {
8321 let context = task_context.await?;
8322 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8323
8324 let resolved = &mut resolved_task.resolved;
8325 resolved.reveal = reveal_strategy;
8326
8327 workspace
8328 .update_in(cx, |workspace, window, cx| {
8329 workspace.schedule_resolved_task(
8330 task_source_kind,
8331 resolved_task,
8332 false,
8333 window,
8334 cx,
8335 );
8336 })
8337 .ok()
8338 })
8339 .detach();
8340 }
8341
8342 fn find_closest_task(
8343 &mut self,
8344 cx: &mut Context<Self>,
8345 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8346 let cursor_row = self
8347 .selections
8348 .newest_adjusted(&self.display_snapshot(cx))
8349 .head()
8350 .row;
8351
8352 let ((buffer_id, row), tasks) = self
8353 .tasks
8354 .iter()
8355 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8356
8357 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8358 let tasks = Arc::new(tasks.to_owned());
8359 Some((buffer, *row, tasks))
8360 }
8361
8362 fn find_enclosing_node_task(
8363 &mut self,
8364 cx: &mut Context<Self>,
8365 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8366 let snapshot = self.buffer.read(cx).snapshot(cx);
8367 let offset = self
8368 .selections
8369 .newest::<usize>(&self.display_snapshot(cx))
8370 .head();
8371 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8372 let buffer_id = excerpt.buffer().remote_id();
8373
8374 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8375 let mut cursor = layer.node().walk();
8376
8377 while cursor.goto_first_child_for_byte(offset).is_some() {
8378 if cursor.node().end_byte() == offset {
8379 cursor.goto_next_sibling();
8380 }
8381 }
8382
8383 // Ascend to the smallest ancestor that contains the range and has a task.
8384 loop {
8385 let node = cursor.node();
8386 let node_range = node.byte_range();
8387 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8388
8389 // Check if this node contains our offset
8390 if node_range.start <= offset && node_range.end >= offset {
8391 // If it contains offset, check for task
8392 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8393 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8394 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8395 }
8396 }
8397
8398 if !cursor.goto_parent() {
8399 break;
8400 }
8401 }
8402 None
8403 }
8404
8405 fn render_run_indicator(
8406 &self,
8407 _style: &EditorStyle,
8408 is_active: bool,
8409 row: DisplayRow,
8410 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8411 cx: &mut Context<Self>,
8412 ) -> IconButton {
8413 let color = Color::Muted;
8414 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8415
8416 IconButton::new(
8417 ("run_indicator", row.0 as usize),
8418 ui::IconName::PlayOutlined,
8419 )
8420 .shape(ui::IconButtonShape::Square)
8421 .icon_size(IconSize::XSmall)
8422 .icon_color(color)
8423 .toggle_state(is_active)
8424 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8425 let quick_launch = match e {
8426 ClickEvent::Keyboard(_) => true,
8427 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8428 };
8429
8430 window.focus(&editor.focus_handle(cx));
8431 editor.toggle_code_actions(
8432 &ToggleCodeActions {
8433 deployed_from: Some(CodeActionSource::RunMenu(row)),
8434 quick_launch,
8435 },
8436 window,
8437 cx,
8438 );
8439 }))
8440 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8441 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8442 }))
8443 }
8444
8445 pub fn context_menu_visible(&self) -> bool {
8446 !self.edit_prediction_preview_is_active()
8447 && self
8448 .context_menu
8449 .borrow()
8450 .as_ref()
8451 .is_some_and(|menu| menu.visible())
8452 }
8453
8454 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8455 self.context_menu
8456 .borrow()
8457 .as_ref()
8458 .map(|menu| menu.origin())
8459 }
8460
8461 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8462 self.context_menu_options = Some(options);
8463 }
8464
8465 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8466 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8467
8468 fn render_edit_prediction_popover(
8469 &mut self,
8470 text_bounds: &Bounds<Pixels>,
8471 content_origin: gpui::Point<Pixels>,
8472 right_margin: Pixels,
8473 editor_snapshot: &EditorSnapshot,
8474 visible_row_range: Range<DisplayRow>,
8475 scroll_top: ScrollOffset,
8476 scroll_bottom: ScrollOffset,
8477 line_layouts: &[LineWithInvisibles],
8478 line_height: Pixels,
8479 scroll_position: gpui::Point<ScrollOffset>,
8480 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8481 newest_selection_head: Option<DisplayPoint>,
8482 editor_width: Pixels,
8483 style: &EditorStyle,
8484 window: &mut Window,
8485 cx: &mut App,
8486 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8487 if self.mode().is_minimap() {
8488 return None;
8489 }
8490 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8491
8492 if self.edit_prediction_visible_in_cursor_popover(true) {
8493 return None;
8494 }
8495
8496 match &active_edit_prediction.completion {
8497 EditPrediction::MoveWithin { target, .. } => {
8498 let target_display_point = target.to_display_point(editor_snapshot);
8499
8500 if self.edit_prediction_requires_modifier() {
8501 if !self.edit_prediction_preview_is_active() {
8502 return None;
8503 }
8504
8505 self.render_edit_prediction_modifier_jump_popover(
8506 text_bounds,
8507 content_origin,
8508 visible_row_range,
8509 line_layouts,
8510 line_height,
8511 scroll_pixel_position,
8512 newest_selection_head,
8513 target_display_point,
8514 window,
8515 cx,
8516 )
8517 } else {
8518 self.render_edit_prediction_eager_jump_popover(
8519 text_bounds,
8520 content_origin,
8521 editor_snapshot,
8522 visible_row_range,
8523 scroll_top,
8524 scroll_bottom,
8525 line_height,
8526 scroll_pixel_position,
8527 target_display_point,
8528 editor_width,
8529 window,
8530 cx,
8531 )
8532 }
8533 }
8534 EditPrediction::Edit {
8535 display_mode: EditDisplayMode::Inline,
8536 ..
8537 } => None,
8538 EditPrediction::Edit {
8539 display_mode: EditDisplayMode::TabAccept,
8540 edits,
8541 ..
8542 } => {
8543 let range = &edits.first()?.0;
8544 let target_display_point = range.end.to_display_point(editor_snapshot);
8545
8546 self.render_edit_prediction_end_of_line_popover(
8547 "Accept",
8548 editor_snapshot,
8549 visible_row_range,
8550 target_display_point,
8551 line_height,
8552 scroll_pixel_position,
8553 content_origin,
8554 editor_width,
8555 window,
8556 cx,
8557 )
8558 }
8559 EditPrediction::Edit {
8560 edits,
8561 edit_preview,
8562 display_mode: EditDisplayMode::DiffPopover,
8563 snapshot,
8564 } => self.render_edit_prediction_diff_popover(
8565 text_bounds,
8566 content_origin,
8567 right_margin,
8568 editor_snapshot,
8569 visible_row_range,
8570 line_layouts,
8571 line_height,
8572 scroll_position,
8573 scroll_pixel_position,
8574 newest_selection_head,
8575 editor_width,
8576 style,
8577 edits,
8578 edit_preview,
8579 snapshot,
8580 window,
8581 cx,
8582 ),
8583 EditPrediction::MoveOutside { snapshot, .. } => {
8584 let file_name = snapshot
8585 .file()
8586 .map(|file| file.file_name(cx))
8587 .unwrap_or("untitled");
8588 let mut element = self
8589 .render_edit_prediction_line_popover(
8590 format!("Jump to {file_name}"),
8591 Some(IconName::ZedPredict),
8592 window,
8593 cx,
8594 )
8595 .into_any();
8596
8597 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8598 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8599 let origin_y = text_bounds.size.height - size.height - px(30.);
8600 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8601 element.prepaint_at(origin, window, cx);
8602
8603 Some((element, origin))
8604 }
8605 }
8606 }
8607
8608 fn render_edit_prediction_modifier_jump_popover(
8609 &mut self,
8610 text_bounds: &Bounds<Pixels>,
8611 content_origin: gpui::Point<Pixels>,
8612 visible_row_range: Range<DisplayRow>,
8613 line_layouts: &[LineWithInvisibles],
8614 line_height: Pixels,
8615 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8616 newest_selection_head: Option<DisplayPoint>,
8617 target_display_point: DisplayPoint,
8618 window: &mut Window,
8619 cx: &mut App,
8620 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8621 let scrolled_content_origin =
8622 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8623
8624 const SCROLL_PADDING_Y: Pixels = px(12.);
8625
8626 if target_display_point.row() < visible_row_range.start {
8627 return self.render_edit_prediction_scroll_popover(
8628 |_| SCROLL_PADDING_Y,
8629 IconName::ArrowUp,
8630 visible_row_range,
8631 line_layouts,
8632 newest_selection_head,
8633 scrolled_content_origin,
8634 window,
8635 cx,
8636 );
8637 } else if target_display_point.row() >= visible_row_range.end {
8638 return self.render_edit_prediction_scroll_popover(
8639 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8640 IconName::ArrowDown,
8641 visible_row_range,
8642 line_layouts,
8643 newest_selection_head,
8644 scrolled_content_origin,
8645 window,
8646 cx,
8647 );
8648 }
8649
8650 const POLE_WIDTH: Pixels = px(2.);
8651
8652 let line_layout =
8653 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8654 let target_column = target_display_point.column() as usize;
8655
8656 let target_x = line_layout.x_for_index(target_column);
8657 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8658 - scroll_pixel_position.y;
8659
8660 let flag_on_right = target_x < text_bounds.size.width / 2.;
8661
8662 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8663 border_color.l += 0.001;
8664
8665 let mut element = v_flex()
8666 .items_end()
8667 .when(flag_on_right, |el| el.items_start())
8668 .child(if flag_on_right {
8669 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8670 .rounded_bl(px(0.))
8671 .rounded_tl(px(0.))
8672 .border_l_2()
8673 .border_color(border_color)
8674 } else {
8675 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8676 .rounded_br(px(0.))
8677 .rounded_tr(px(0.))
8678 .border_r_2()
8679 .border_color(border_color)
8680 })
8681 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8682 .into_any();
8683
8684 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8685
8686 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8687 - point(
8688 if flag_on_right {
8689 POLE_WIDTH
8690 } else {
8691 size.width - POLE_WIDTH
8692 },
8693 size.height - line_height,
8694 );
8695
8696 origin.x = origin.x.max(content_origin.x);
8697
8698 element.prepaint_at(origin, window, cx);
8699
8700 Some((element, origin))
8701 }
8702
8703 fn render_edit_prediction_scroll_popover(
8704 &mut self,
8705 to_y: impl Fn(Size<Pixels>) -> Pixels,
8706 scroll_icon: IconName,
8707 visible_row_range: Range<DisplayRow>,
8708 line_layouts: &[LineWithInvisibles],
8709 newest_selection_head: Option<DisplayPoint>,
8710 scrolled_content_origin: gpui::Point<Pixels>,
8711 window: &mut Window,
8712 cx: &mut App,
8713 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8714 let mut element = self
8715 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8716 .into_any();
8717
8718 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8719
8720 let cursor = newest_selection_head?;
8721 let cursor_row_layout =
8722 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8723 let cursor_column = cursor.column() as usize;
8724
8725 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8726
8727 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8728
8729 element.prepaint_at(origin, window, cx);
8730 Some((element, origin))
8731 }
8732
8733 fn render_edit_prediction_eager_jump_popover(
8734 &mut self,
8735 text_bounds: &Bounds<Pixels>,
8736 content_origin: gpui::Point<Pixels>,
8737 editor_snapshot: &EditorSnapshot,
8738 visible_row_range: Range<DisplayRow>,
8739 scroll_top: ScrollOffset,
8740 scroll_bottom: ScrollOffset,
8741 line_height: Pixels,
8742 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8743 target_display_point: DisplayPoint,
8744 editor_width: Pixels,
8745 window: &mut Window,
8746 cx: &mut App,
8747 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8748 if target_display_point.row().as_f64() < scroll_top {
8749 let mut element = self
8750 .render_edit_prediction_line_popover(
8751 "Jump to Edit",
8752 Some(IconName::ArrowUp),
8753 window,
8754 cx,
8755 )
8756 .into_any();
8757
8758 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8759 let offset = point(
8760 (text_bounds.size.width - size.width) / 2.,
8761 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8762 );
8763
8764 let origin = text_bounds.origin + offset;
8765 element.prepaint_at(origin, window, cx);
8766 Some((element, origin))
8767 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8768 let mut element = self
8769 .render_edit_prediction_line_popover(
8770 "Jump to Edit",
8771 Some(IconName::ArrowDown),
8772 window,
8773 cx,
8774 )
8775 .into_any();
8776
8777 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8778 let offset = point(
8779 (text_bounds.size.width - size.width) / 2.,
8780 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8781 );
8782
8783 let origin = text_bounds.origin + offset;
8784 element.prepaint_at(origin, window, cx);
8785 Some((element, origin))
8786 } else {
8787 self.render_edit_prediction_end_of_line_popover(
8788 "Jump to Edit",
8789 editor_snapshot,
8790 visible_row_range,
8791 target_display_point,
8792 line_height,
8793 scroll_pixel_position,
8794 content_origin,
8795 editor_width,
8796 window,
8797 cx,
8798 )
8799 }
8800 }
8801
8802 fn render_edit_prediction_end_of_line_popover(
8803 self: &mut Editor,
8804 label: &'static str,
8805 editor_snapshot: &EditorSnapshot,
8806 visible_row_range: Range<DisplayRow>,
8807 target_display_point: DisplayPoint,
8808 line_height: Pixels,
8809 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8810 content_origin: gpui::Point<Pixels>,
8811 editor_width: Pixels,
8812 window: &mut Window,
8813 cx: &mut App,
8814 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8815 let target_line_end = DisplayPoint::new(
8816 target_display_point.row(),
8817 editor_snapshot.line_len(target_display_point.row()),
8818 );
8819
8820 let mut element = self
8821 .render_edit_prediction_line_popover(label, None, window, cx)
8822 .into_any();
8823
8824 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8825
8826 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8827
8828 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8829 let mut origin = start_point
8830 + line_origin
8831 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8832 origin.x = origin.x.max(content_origin.x);
8833
8834 let max_x = content_origin.x + editor_width - size.width;
8835
8836 if origin.x > max_x {
8837 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8838
8839 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8840 origin.y += offset;
8841 IconName::ArrowUp
8842 } else {
8843 origin.y -= offset;
8844 IconName::ArrowDown
8845 };
8846
8847 element = self
8848 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8849 .into_any();
8850
8851 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8852
8853 origin.x = content_origin.x + editor_width - size.width - px(2.);
8854 }
8855
8856 element.prepaint_at(origin, window, cx);
8857 Some((element, origin))
8858 }
8859
8860 fn render_edit_prediction_diff_popover(
8861 self: &Editor,
8862 text_bounds: &Bounds<Pixels>,
8863 content_origin: gpui::Point<Pixels>,
8864 right_margin: Pixels,
8865 editor_snapshot: &EditorSnapshot,
8866 visible_row_range: Range<DisplayRow>,
8867 line_layouts: &[LineWithInvisibles],
8868 line_height: Pixels,
8869 scroll_position: gpui::Point<ScrollOffset>,
8870 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8871 newest_selection_head: Option<DisplayPoint>,
8872 editor_width: Pixels,
8873 style: &EditorStyle,
8874 edits: &Vec<(Range<Anchor>, String)>,
8875 edit_preview: &Option<language::EditPreview>,
8876 snapshot: &language::BufferSnapshot,
8877 window: &mut Window,
8878 cx: &mut App,
8879 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8880 let edit_start = edits
8881 .first()
8882 .unwrap()
8883 .0
8884 .start
8885 .to_display_point(editor_snapshot);
8886 let edit_end = edits
8887 .last()
8888 .unwrap()
8889 .0
8890 .end
8891 .to_display_point(editor_snapshot);
8892
8893 let is_visible = visible_row_range.contains(&edit_start.row())
8894 || visible_row_range.contains(&edit_end.row());
8895 if !is_visible {
8896 return None;
8897 }
8898
8899 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8900 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8901 } else {
8902 // Fallback for providers without edit_preview
8903 crate::edit_prediction_fallback_text(edits, cx)
8904 };
8905
8906 let styled_text = highlighted_edits.to_styled_text(&style.text);
8907 let line_count = highlighted_edits.text.lines().count();
8908
8909 const BORDER_WIDTH: Pixels = px(1.);
8910
8911 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8912 let has_keybind = keybind.is_some();
8913
8914 let mut element = h_flex()
8915 .items_start()
8916 .child(
8917 h_flex()
8918 .bg(cx.theme().colors().editor_background)
8919 .border(BORDER_WIDTH)
8920 .shadow_xs()
8921 .border_color(cx.theme().colors().border)
8922 .rounded_l_lg()
8923 .when(line_count > 1, |el| el.rounded_br_lg())
8924 .pr_1()
8925 .child(styled_text),
8926 )
8927 .child(
8928 h_flex()
8929 .h(line_height + BORDER_WIDTH * 2.)
8930 .px_1p5()
8931 .gap_1()
8932 // Workaround: For some reason, there's a gap if we don't do this
8933 .ml(-BORDER_WIDTH)
8934 .shadow(vec![gpui::BoxShadow {
8935 color: gpui::black().opacity(0.05),
8936 offset: point(px(1.), px(1.)),
8937 blur_radius: px(2.),
8938 spread_radius: px(0.),
8939 }])
8940 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8941 .border(BORDER_WIDTH)
8942 .border_color(cx.theme().colors().border)
8943 .rounded_r_lg()
8944 .id("edit_prediction_diff_popover_keybind")
8945 .when(!has_keybind, |el| {
8946 let status_colors = cx.theme().status();
8947
8948 el.bg(status_colors.error_background)
8949 .border_color(status_colors.error.opacity(0.6))
8950 .child(Icon::new(IconName::Info).color(Color::Error))
8951 .cursor_default()
8952 .hoverable_tooltip(move |_window, cx| {
8953 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8954 })
8955 })
8956 .children(keybind),
8957 )
8958 .into_any();
8959
8960 let longest_row =
8961 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8962 let longest_line_width = if visible_row_range.contains(&longest_row) {
8963 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8964 } else {
8965 layout_line(
8966 longest_row,
8967 editor_snapshot,
8968 style,
8969 editor_width,
8970 |_| false,
8971 window,
8972 cx,
8973 )
8974 .width
8975 };
8976
8977 let viewport_bounds =
8978 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8979 right: -right_margin,
8980 ..Default::default()
8981 });
8982
8983 let x_after_longest = Pixels::from(
8984 ScrollPixelOffset::from(
8985 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
8986 ) - scroll_pixel_position.x,
8987 );
8988
8989 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8990
8991 // Fully visible if it can be displayed within the window (allow overlapping other
8992 // panes). However, this is only allowed if the popover starts within text_bounds.
8993 let can_position_to_the_right = x_after_longest < text_bounds.right()
8994 && x_after_longest + element_bounds.width < viewport_bounds.right();
8995
8996 let mut origin = if can_position_to_the_right {
8997 point(
8998 x_after_longest,
8999 text_bounds.origin.y
9000 + Pixels::from(
9001 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9002 - scroll_pixel_position.y,
9003 ),
9004 )
9005 } else {
9006 let cursor_row = newest_selection_head.map(|head| head.row());
9007 let above_edit = edit_start
9008 .row()
9009 .0
9010 .checked_sub(line_count as u32)
9011 .map(DisplayRow);
9012 let below_edit = Some(edit_end.row() + 1);
9013 let above_cursor =
9014 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9015 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9016
9017 // Place the edit popover adjacent to the edit if there is a location
9018 // available that is onscreen and does not obscure the cursor. Otherwise,
9019 // place it adjacent to the cursor.
9020 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9021 .into_iter()
9022 .flatten()
9023 .find(|&start_row| {
9024 let end_row = start_row + line_count as u32;
9025 visible_row_range.contains(&start_row)
9026 && visible_row_range.contains(&end_row)
9027 && cursor_row
9028 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9029 })?;
9030
9031 content_origin
9032 + point(
9033 Pixels::from(-scroll_pixel_position.x),
9034 Pixels::from(
9035 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9036 ),
9037 )
9038 };
9039
9040 origin.x -= BORDER_WIDTH;
9041
9042 window.defer_draw(element, origin, 1);
9043
9044 // Do not return an element, since it will already be drawn due to defer_draw.
9045 None
9046 }
9047
9048 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9049 px(30.)
9050 }
9051
9052 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9053 if self.read_only(cx) {
9054 cx.theme().players().read_only()
9055 } else {
9056 self.style.as_ref().unwrap().local_player
9057 }
9058 }
9059
9060 fn render_edit_prediction_accept_keybind(
9061 &self,
9062 window: &mut Window,
9063 cx: &mut App,
9064 ) -> Option<AnyElement> {
9065 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9066 let accept_keystroke = accept_binding.keystroke()?;
9067
9068 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9069
9070 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9071 Color::Accent
9072 } else {
9073 Color::Muted
9074 };
9075
9076 h_flex()
9077 .px_0p5()
9078 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9079 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9080 .text_size(TextSize::XSmall.rems(cx))
9081 .child(h_flex().children(ui::render_modifiers(
9082 accept_keystroke.modifiers(),
9083 PlatformStyle::platform(),
9084 Some(modifiers_color),
9085 Some(IconSize::XSmall.rems().into()),
9086 true,
9087 )))
9088 .when(is_platform_style_mac, |parent| {
9089 parent.child(accept_keystroke.key().to_string())
9090 })
9091 .when(!is_platform_style_mac, |parent| {
9092 parent.child(
9093 Key::new(
9094 util::capitalize(accept_keystroke.key()),
9095 Some(Color::Default),
9096 )
9097 .size(Some(IconSize::XSmall.rems().into())),
9098 )
9099 })
9100 .into_any()
9101 .into()
9102 }
9103
9104 fn render_edit_prediction_line_popover(
9105 &self,
9106 label: impl Into<SharedString>,
9107 icon: Option<IconName>,
9108 window: &mut Window,
9109 cx: &mut App,
9110 ) -> Stateful<Div> {
9111 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9112
9113 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9114 let has_keybind = keybind.is_some();
9115
9116 h_flex()
9117 .id("ep-line-popover")
9118 .py_0p5()
9119 .pl_1()
9120 .pr(padding_right)
9121 .gap_1()
9122 .rounded_md()
9123 .border_1()
9124 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9125 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9126 .shadow_xs()
9127 .when(!has_keybind, |el| {
9128 let status_colors = cx.theme().status();
9129
9130 el.bg(status_colors.error_background)
9131 .border_color(status_colors.error.opacity(0.6))
9132 .pl_2()
9133 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9134 .cursor_default()
9135 .hoverable_tooltip(move |_window, cx| {
9136 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9137 })
9138 })
9139 .children(keybind)
9140 .child(
9141 Label::new(label)
9142 .size(LabelSize::Small)
9143 .when(!has_keybind, |el| {
9144 el.color(cx.theme().status().error.into()).strikethrough()
9145 }),
9146 )
9147 .when(!has_keybind, |el| {
9148 el.child(
9149 h_flex().ml_1().child(
9150 Icon::new(IconName::Info)
9151 .size(IconSize::Small)
9152 .color(cx.theme().status().error.into()),
9153 ),
9154 )
9155 })
9156 .when_some(icon, |element, icon| {
9157 element.child(
9158 div()
9159 .mt(px(1.5))
9160 .child(Icon::new(icon).size(IconSize::Small)),
9161 )
9162 })
9163 }
9164
9165 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9166 let accent_color = cx.theme().colors().text_accent;
9167 let editor_bg_color = cx.theme().colors().editor_background;
9168 editor_bg_color.blend(accent_color.opacity(0.1))
9169 }
9170
9171 fn edit_prediction_callout_popover_border_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.6))
9175 }
9176 fn get_prediction_provider_icon_name(
9177 provider: &Option<RegisteredEditPredictionProvider>,
9178 ) -> IconName {
9179 match provider {
9180 Some(provider) => match provider.provider.name() {
9181 "copilot" => IconName::Copilot,
9182 "supermaven" => IconName::Supermaven,
9183 _ => IconName::ZedPredict,
9184 },
9185 None => IconName::ZedPredict,
9186 }
9187 }
9188
9189 fn render_edit_prediction_cursor_popover(
9190 &self,
9191 min_width: Pixels,
9192 max_width: Pixels,
9193 cursor_point: Point,
9194 style: &EditorStyle,
9195 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9196 _window: &Window,
9197 cx: &mut Context<Editor>,
9198 ) -> Option<AnyElement> {
9199 let provider = self.edit_prediction_provider.as_ref()?;
9200 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9201
9202 let is_refreshing = provider.provider.is_refreshing(cx);
9203
9204 fn pending_completion_container(icon: IconName) -> Div {
9205 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9206 }
9207
9208 let completion = match &self.active_edit_prediction {
9209 Some(prediction) => {
9210 if !self.has_visible_completions_menu() {
9211 const RADIUS: Pixels = px(6.);
9212 const BORDER_WIDTH: Pixels = px(1.);
9213
9214 return Some(
9215 h_flex()
9216 .elevation_2(cx)
9217 .border(BORDER_WIDTH)
9218 .border_color(cx.theme().colors().border)
9219 .when(accept_keystroke.is_none(), |el| {
9220 el.border_color(cx.theme().status().error)
9221 })
9222 .rounded(RADIUS)
9223 .rounded_tl(px(0.))
9224 .overflow_hidden()
9225 .child(div().px_1p5().child(match &prediction.completion {
9226 EditPrediction::MoveWithin { target, snapshot } => {
9227 use text::ToPoint as _;
9228 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9229 {
9230 Icon::new(IconName::ZedPredictDown)
9231 } else {
9232 Icon::new(IconName::ZedPredictUp)
9233 }
9234 }
9235 EditPrediction::MoveOutside { .. } => {
9236 // TODO [zeta2] custom icon for external jump?
9237 Icon::new(provider_icon)
9238 }
9239 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9240 }))
9241 .child(
9242 h_flex()
9243 .gap_1()
9244 .py_1()
9245 .px_2()
9246 .rounded_r(RADIUS - BORDER_WIDTH)
9247 .border_l_1()
9248 .border_color(cx.theme().colors().border)
9249 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9250 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9251 el.child(
9252 Label::new("Hold")
9253 .size(LabelSize::Small)
9254 .when(accept_keystroke.is_none(), |el| {
9255 el.strikethrough()
9256 })
9257 .line_height_style(LineHeightStyle::UiLabel),
9258 )
9259 })
9260 .id("edit_prediction_cursor_popover_keybind")
9261 .when(accept_keystroke.is_none(), |el| {
9262 let status_colors = cx.theme().status();
9263
9264 el.bg(status_colors.error_background)
9265 .border_color(status_colors.error.opacity(0.6))
9266 .child(Icon::new(IconName::Info).color(Color::Error))
9267 .cursor_default()
9268 .hoverable_tooltip(move |_window, cx| {
9269 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9270 .into()
9271 })
9272 })
9273 .when_some(
9274 accept_keystroke.as_ref(),
9275 |el, accept_keystroke| {
9276 el.child(h_flex().children(ui::render_modifiers(
9277 accept_keystroke.modifiers(),
9278 PlatformStyle::platform(),
9279 Some(Color::Default),
9280 Some(IconSize::XSmall.rems().into()),
9281 false,
9282 )))
9283 },
9284 ),
9285 )
9286 .into_any(),
9287 );
9288 }
9289
9290 self.render_edit_prediction_cursor_popover_preview(
9291 prediction,
9292 cursor_point,
9293 style,
9294 cx,
9295 )?
9296 }
9297
9298 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9299 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9300 stale_completion,
9301 cursor_point,
9302 style,
9303 cx,
9304 )?,
9305
9306 None => pending_completion_container(provider_icon)
9307 .child(Label::new("...").size(LabelSize::Small)),
9308 },
9309
9310 None => pending_completion_container(provider_icon)
9311 .child(Label::new("...").size(LabelSize::Small)),
9312 };
9313
9314 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9315 completion
9316 .with_animation(
9317 "loading-completion",
9318 Animation::new(Duration::from_secs(2))
9319 .repeat()
9320 .with_easing(pulsating_between(0.4, 0.8)),
9321 |label, delta| label.opacity(delta),
9322 )
9323 .into_any_element()
9324 } else {
9325 completion.into_any_element()
9326 };
9327
9328 let has_completion = self.active_edit_prediction.is_some();
9329
9330 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9331 Some(
9332 h_flex()
9333 .min_w(min_width)
9334 .max_w(max_width)
9335 .flex_1()
9336 .elevation_2(cx)
9337 .border_color(cx.theme().colors().border)
9338 .child(
9339 div()
9340 .flex_1()
9341 .py_1()
9342 .px_2()
9343 .overflow_hidden()
9344 .child(completion),
9345 )
9346 .when_some(accept_keystroke, |el, accept_keystroke| {
9347 if !accept_keystroke.modifiers().modified() {
9348 return el;
9349 }
9350
9351 el.child(
9352 h_flex()
9353 .h_full()
9354 .border_l_1()
9355 .rounded_r_lg()
9356 .border_color(cx.theme().colors().border)
9357 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9358 .gap_1()
9359 .py_1()
9360 .px_2()
9361 .child(
9362 h_flex()
9363 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9364 .when(is_platform_style_mac, |parent| parent.gap_1())
9365 .child(h_flex().children(ui::render_modifiers(
9366 accept_keystroke.modifiers(),
9367 PlatformStyle::platform(),
9368 Some(if !has_completion {
9369 Color::Muted
9370 } else {
9371 Color::Default
9372 }),
9373 None,
9374 false,
9375 ))),
9376 )
9377 .child(Label::new("Preview").into_any_element())
9378 .opacity(if has_completion { 1.0 } else { 0.4 }),
9379 )
9380 })
9381 .into_any(),
9382 )
9383 }
9384
9385 fn render_edit_prediction_cursor_popover_preview(
9386 &self,
9387 completion: &EditPredictionState,
9388 cursor_point: Point,
9389 style: &EditorStyle,
9390 cx: &mut Context<Editor>,
9391 ) -> Option<Div> {
9392 use text::ToPoint as _;
9393
9394 fn render_relative_row_jump(
9395 prefix: impl Into<String>,
9396 current_row: u32,
9397 target_row: u32,
9398 ) -> Div {
9399 let (row_diff, arrow) = if target_row < current_row {
9400 (current_row - target_row, IconName::ArrowUp)
9401 } else {
9402 (target_row - current_row, IconName::ArrowDown)
9403 };
9404
9405 h_flex()
9406 .child(
9407 Label::new(format!("{}{}", prefix.into(), row_diff))
9408 .color(Color::Muted)
9409 .size(LabelSize::Small),
9410 )
9411 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9412 }
9413
9414 let supports_jump = self
9415 .edit_prediction_provider
9416 .as_ref()
9417 .map(|provider| provider.provider.supports_jump_to_edit())
9418 .unwrap_or(true);
9419
9420 match &completion.completion {
9421 EditPrediction::MoveWithin {
9422 target, snapshot, ..
9423 } => {
9424 if !supports_jump {
9425 return None;
9426 }
9427
9428 Some(
9429 h_flex()
9430 .px_2()
9431 .gap_2()
9432 .flex_1()
9433 .child(
9434 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9435 Icon::new(IconName::ZedPredictDown)
9436 } else {
9437 Icon::new(IconName::ZedPredictUp)
9438 },
9439 )
9440 .child(Label::new("Jump to Edit")),
9441 )
9442 }
9443 EditPrediction::MoveOutside { snapshot, .. } => {
9444 let file_name = snapshot
9445 .file()
9446 .map(|file| file.file_name(cx))
9447 .unwrap_or("untitled");
9448 Some(
9449 h_flex()
9450 .px_2()
9451 .gap_2()
9452 .flex_1()
9453 .child(Icon::new(IconName::ZedPredict))
9454 .child(Label::new(format!("Jump to {file_name}"))),
9455 )
9456 }
9457 EditPrediction::Edit {
9458 edits,
9459 edit_preview,
9460 snapshot,
9461 display_mode: _,
9462 } => {
9463 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9464
9465 let (highlighted_edits, has_more_lines) =
9466 if let Some(edit_preview) = edit_preview.as_ref() {
9467 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9468 .first_line_preview()
9469 } else {
9470 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9471 };
9472
9473 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9474 .with_default_highlights(&style.text, highlighted_edits.highlights);
9475
9476 let preview = h_flex()
9477 .gap_1()
9478 .min_w_16()
9479 .child(styled_text)
9480 .when(has_more_lines, |parent| parent.child("…"));
9481
9482 let left = if supports_jump && first_edit_row != cursor_point.row {
9483 render_relative_row_jump("", cursor_point.row, first_edit_row)
9484 .into_any_element()
9485 } else {
9486 let icon_name =
9487 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9488 Icon::new(icon_name).into_any_element()
9489 };
9490
9491 Some(
9492 h_flex()
9493 .h_full()
9494 .flex_1()
9495 .gap_2()
9496 .pr_1()
9497 .overflow_x_hidden()
9498 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9499 .child(left)
9500 .child(preview),
9501 )
9502 }
9503 }
9504 }
9505
9506 pub fn render_context_menu(
9507 &self,
9508 style: &EditorStyle,
9509 max_height_in_lines: u32,
9510 window: &mut Window,
9511 cx: &mut Context<Editor>,
9512 ) -> Option<AnyElement> {
9513 let menu = self.context_menu.borrow();
9514 let menu = menu.as_ref()?;
9515 if !menu.visible() {
9516 return None;
9517 };
9518 Some(menu.render(style, max_height_in_lines, window, cx))
9519 }
9520
9521 fn render_context_menu_aside(
9522 &mut self,
9523 max_size: Size<Pixels>,
9524 window: &mut Window,
9525 cx: &mut Context<Editor>,
9526 ) -> Option<AnyElement> {
9527 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9528 if menu.visible() {
9529 menu.render_aside(max_size, window, cx)
9530 } else {
9531 None
9532 }
9533 })
9534 }
9535
9536 fn hide_context_menu(
9537 &mut self,
9538 window: &mut Window,
9539 cx: &mut Context<Self>,
9540 ) -> Option<CodeContextMenu> {
9541 cx.notify();
9542 self.completion_tasks.clear();
9543 let context_menu = self.context_menu.borrow_mut().take();
9544 self.stale_edit_prediction_in_menu.take();
9545 self.update_visible_edit_prediction(window, cx);
9546 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9547 && let Some(completion_provider) = &self.completion_provider
9548 {
9549 completion_provider.selection_changed(None, window, cx);
9550 }
9551 context_menu
9552 }
9553
9554 fn show_snippet_choices(
9555 &mut self,
9556 choices: &Vec<String>,
9557 selection: Range<Anchor>,
9558 cx: &mut Context<Self>,
9559 ) {
9560 let Some((_, buffer, _)) = self
9561 .buffer()
9562 .read(cx)
9563 .excerpt_containing(selection.start, cx)
9564 else {
9565 return;
9566 };
9567 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9568 else {
9569 return;
9570 };
9571 if buffer != end_buffer {
9572 log::error!("expected anchor range to have matching buffer IDs");
9573 return;
9574 }
9575
9576 let id = post_inc(&mut self.next_completion_id);
9577 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9578 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9579 CompletionsMenu::new_snippet_choices(
9580 id,
9581 true,
9582 choices,
9583 selection,
9584 buffer,
9585 snippet_sort_order,
9586 ),
9587 ));
9588 }
9589
9590 pub fn insert_snippet(
9591 &mut self,
9592 insertion_ranges: &[Range<usize>],
9593 snippet: Snippet,
9594 window: &mut Window,
9595 cx: &mut Context<Self>,
9596 ) -> Result<()> {
9597 struct Tabstop<T> {
9598 is_end_tabstop: bool,
9599 ranges: Vec<Range<T>>,
9600 choices: Option<Vec<String>>,
9601 }
9602
9603 let tabstops = self.buffer.update(cx, |buffer, cx| {
9604 let snippet_text: Arc<str> = snippet.text.clone().into();
9605 let edits = insertion_ranges
9606 .iter()
9607 .cloned()
9608 .map(|range| (range, snippet_text.clone()));
9609 let autoindent_mode = AutoindentMode::Block {
9610 original_indent_columns: Vec::new(),
9611 };
9612 buffer.edit(edits, Some(autoindent_mode), cx);
9613
9614 let snapshot = &*buffer.read(cx);
9615 let snippet = &snippet;
9616 snippet
9617 .tabstops
9618 .iter()
9619 .map(|tabstop| {
9620 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9621 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9622 });
9623 let mut tabstop_ranges = tabstop
9624 .ranges
9625 .iter()
9626 .flat_map(|tabstop_range| {
9627 let mut delta = 0_isize;
9628 insertion_ranges.iter().map(move |insertion_range| {
9629 let insertion_start = insertion_range.start as isize + delta;
9630 delta +=
9631 snippet.text.len() as isize - insertion_range.len() as isize;
9632
9633 let start = ((insertion_start + tabstop_range.start) as usize)
9634 .min(snapshot.len());
9635 let end = ((insertion_start + tabstop_range.end) as usize)
9636 .min(snapshot.len());
9637 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9638 })
9639 })
9640 .collect::<Vec<_>>();
9641 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9642
9643 Tabstop {
9644 is_end_tabstop,
9645 ranges: tabstop_ranges,
9646 choices: tabstop.choices.clone(),
9647 }
9648 })
9649 .collect::<Vec<_>>()
9650 });
9651 if let Some(tabstop) = tabstops.first() {
9652 self.change_selections(Default::default(), window, cx, |s| {
9653 // Reverse order so that the first range is the newest created selection.
9654 // Completions will use it and autoscroll will prioritize it.
9655 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9656 });
9657
9658 if let Some(choices) = &tabstop.choices
9659 && let Some(selection) = tabstop.ranges.first()
9660 {
9661 self.show_snippet_choices(choices, selection.clone(), cx)
9662 }
9663
9664 // If we're already at the last tabstop and it's at the end of the snippet,
9665 // we're done, we don't need to keep the state around.
9666 if !tabstop.is_end_tabstop {
9667 let choices = tabstops
9668 .iter()
9669 .map(|tabstop| tabstop.choices.clone())
9670 .collect();
9671
9672 let ranges = tabstops
9673 .into_iter()
9674 .map(|tabstop| tabstop.ranges)
9675 .collect::<Vec<_>>();
9676
9677 self.snippet_stack.push(SnippetState {
9678 active_index: 0,
9679 ranges,
9680 choices,
9681 });
9682 }
9683
9684 // Check whether the just-entered snippet ends with an auto-closable bracket.
9685 if self.autoclose_regions.is_empty() {
9686 let snapshot = self.buffer.read(cx).snapshot(cx);
9687 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9688 let selection_head = selection.head();
9689 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9690 continue;
9691 };
9692
9693 let mut bracket_pair = None;
9694 let max_lookup_length = scope
9695 .brackets()
9696 .map(|(pair, _)| {
9697 pair.start
9698 .as_str()
9699 .chars()
9700 .count()
9701 .max(pair.end.as_str().chars().count())
9702 })
9703 .max();
9704 if let Some(max_lookup_length) = max_lookup_length {
9705 let next_text = snapshot
9706 .chars_at(selection_head)
9707 .take(max_lookup_length)
9708 .collect::<String>();
9709 let prev_text = snapshot
9710 .reversed_chars_at(selection_head)
9711 .take(max_lookup_length)
9712 .collect::<String>();
9713
9714 for (pair, enabled) in scope.brackets() {
9715 if enabled
9716 && pair.close
9717 && prev_text.starts_with(pair.start.as_str())
9718 && next_text.starts_with(pair.end.as_str())
9719 {
9720 bracket_pair = Some(pair.clone());
9721 break;
9722 }
9723 }
9724 }
9725
9726 if let Some(pair) = bracket_pair {
9727 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9728 let autoclose_enabled =
9729 self.use_autoclose && snapshot_settings.use_autoclose;
9730 if autoclose_enabled {
9731 let start = snapshot.anchor_after(selection_head);
9732 let end = snapshot.anchor_after(selection_head);
9733 self.autoclose_regions.push(AutocloseRegion {
9734 selection_id: selection.id,
9735 range: start..end,
9736 pair,
9737 });
9738 }
9739 }
9740 }
9741 }
9742 }
9743 Ok(())
9744 }
9745
9746 pub fn move_to_next_snippet_tabstop(
9747 &mut self,
9748 window: &mut Window,
9749 cx: &mut Context<Self>,
9750 ) -> bool {
9751 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9752 }
9753
9754 pub fn move_to_prev_snippet_tabstop(
9755 &mut self,
9756 window: &mut Window,
9757 cx: &mut Context<Self>,
9758 ) -> bool {
9759 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9760 }
9761
9762 pub fn move_to_snippet_tabstop(
9763 &mut self,
9764 bias: Bias,
9765 window: &mut Window,
9766 cx: &mut Context<Self>,
9767 ) -> bool {
9768 if let Some(mut snippet) = self.snippet_stack.pop() {
9769 match bias {
9770 Bias::Left => {
9771 if snippet.active_index > 0 {
9772 snippet.active_index -= 1;
9773 } else {
9774 self.snippet_stack.push(snippet);
9775 return false;
9776 }
9777 }
9778 Bias::Right => {
9779 if snippet.active_index + 1 < snippet.ranges.len() {
9780 snippet.active_index += 1;
9781 } else {
9782 self.snippet_stack.push(snippet);
9783 return false;
9784 }
9785 }
9786 }
9787 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9788 self.change_selections(Default::default(), window, cx, |s| {
9789 // Reverse order so that the first range is the newest created selection.
9790 // Completions will use it and autoscroll will prioritize it.
9791 s.select_ranges(current_ranges.iter().rev().cloned())
9792 });
9793
9794 if let Some(choices) = &snippet.choices[snippet.active_index]
9795 && let Some(selection) = current_ranges.first()
9796 {
9797 self.show_snippet_choices(choices, selection.clone(), cx);
9798 }
9799
9800 // If snippet state is not at the last tabstop, push it back on the stack
9801 if snippet.active_index + 1 < snippet.ranges.len() {
9802 self.snippet_stack.push(snippet);
9803 }
9804 return true;
9805 }
9806 }
9807
9808 false
9809 }
9810
9811 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9812 self.transact(window, cx, |this, window, cx| {
9813 this.select_all(&SelectAll, window, cx);
9814 this.insert("", window, cx);
9815 });
9816 }
9817
9818 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9819 if self.read_only(cx) {
9820 return;
9821 }
9822 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9823 self.transact(window, cx, |this, window, cx| {
9824 this.select_autoclose_pair(window, cx);
9825
9826 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9827
9828 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9829 if !this.linked_edit_ranges.is_empty() {
9830 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9831 let snapshot = this.buffer.read(cx).snapshot(cx);
9832
9833 for selection in selections.iter() {
9834 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9835 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9836 if selection_start.buffer_id != selection_end.buffer_id {
9837 continue;
9838 }
9839 if let Some(ranges) =
9840 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9841 {
9842 for (buffer, entries) in ranges {
9843 linked_ranges.entry(buffer).or_default().extend(entries);
9844 }
9845 }
9846 }
9847 }
9848
9849 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9850 for selection in &mut selections {
9851 if selection.is_empty() {
9852 let old_head = selection.head();
9853 let mut new_head =
9854 movement::left(&display_map, old_head.to_display_point(&display_map))
9855 .to_point(&display_map);
9856 if let Some((buffer, line_buffer_range)) = display_map
9857 .buffer_snapshot()
9858 .buffer_line_for_row(MultiBufferRow(old_head.row))
9859 {
9860 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9861 let indent_len = match indent_size.kind {
9862 IndentKind::Space => {
9863 buffer.settings_at(line_buffer_range.start, cx).tab_size
9864 }
9865 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9866 };
9867 if old_head.column <= indent_size.len && old_head.column > 0 {
9868 let indent_len = indent_len.get();
9869 new_head = cmp::min(
9870 new_head,
9871 MultiBufferPoint::new(
9872 old_head.row,
9873 ((old_head.column - 1) / indent_len) * indent_len,
9874 ),
9875 );
9876 }
9877 }
9878
9879 selection.set_head(new_head, SelectionGoal::None);
9880 }
9881 }
9882
9883 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9884 this.insert("", window, cx);
9885 let empty_str: Arc<str> = Arc::from("");
9886 for (buffer, edits) in linked_ranges {
9887 let snapshot = buffer.read(cx).snapshot();
9888 use text::ToPoint as TP;
9889
9890 let edits = edits
9891 .into_iter()
9892 .map(|range| {
9893 let end_point = TP::to_point(&range.end, &snapshot);
9894 let mut start_point = TP::to_point(&range.start, &snapshot);
9895
9896 if end_point == start_point {
9897 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9898 .saturating_sub(1);
9899 start_point =
9900 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9901 };
9902
9903 (start_point..end_point, empty_str.clone())
9904 })
9905 .sorted_by_key(|(range, _)| range.start)
9906 .collect::<Vec<_>>();
9907 buffer.update(cx, |this, cx| {
9908 this.edit(edits, None, cx);
9909 })
9910 }
9911 this.refresh_edit_prediction(true, false, window, cx);
9912 refresh_linked_ranges(this, window, cx);
9913 });
9914 }
9915
9916 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9917 if self.read_only(cx) {
9918 return;
9919 }
9920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9921 self.transact(window, cx, |this, window, cx| {
9922 this.change_selections(Default::default(), window, cx, |s| {
9923 s.move_with(|map, selection| {
9924 if selection.is_empty() {
9925 let cursor = movement::right(map, selection.head());
9926 selection.end = cursor;
9927 selection.reversed = true;
9928 selection.goal = SelectionGoal::None;
9929 }
9930 })
9931 });
9932 this.insert("", window, cx);
9933 this.refresh_edit_prediction(true, false, window, cx);
9934 });
9935 }
9936
9937 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9938 if self.mode.is_single_line() {
9939 cx.propagate();
9940 return;
9941 }
9942
9943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9944 if self.move_to_prev_snippet_tabstop(window, cx) {
9945 return;
9946 }
9947 self.outdent(&Outdent, window, cx);
9948 }
9949
9950 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9951 if self.mode.is_single_line() {
9952 cx.propagate();
9953 return;
9954 }
9955
9956 if self.move_to_next_snippet_tabstop(window, cx) {
9957 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9958 return;
9959 }
9960 if self.read_only(cx) {
9961 return;
9962 }
9963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9964 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
9965 let buffer = self.buffer.read(cx);
9966 let snapshot = buffer.snapshot(cx);
9967 let rows_iter = selections.iter().map(|s| s.head().row);
9968 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9969
9970 let has_some_cursor_in_whitespace = selections
9971 .iter()
9972 .filter(|selection| selection.is_empty())
9973 .any(|selection| {
9974 let cursor = selection.head();
9975 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9976 cursor.column < current_indent.len
9977 });
9978
9979 let mut edits = Vec::new();
9980 let mut prev_edited_row = 0;
9981 let mut row_delta = 0;
9982 for selection in &mut selections {
9983 if selection.start.row != prev_edited_row {
9984 row_delta = 0;
9985 }
9986 prev_edited_row = selection.end.row;
9987
9988 // If the selection is non-empty, then increase the indentation of the selected lines.
9989 if !selection.is_empty() {
9990 row_delta =
9991 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9992 continue;
9993 }
9994
9995 let cursor = selection.head();
9996 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9997 if let Some(suggested_indent) =
9998 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9999 {
10000 // Don't do anything if already at suggested indent
10001 // and there is any other cursor which is not
10002 if has_some_cursor_in_whitespace
10003 && cursor.column == current_indent.len
10004 && current_indent.len == suggested_indent.len
10005 {
10006 continue;
10007 }
10008
10009 // Adjust line and move cursor to suggested indent
10010 // if cursor is not at suggested indent
10011 if cursor.column < suggested_indent.len
10012 && cursor.column <= current_indent.len
10013 && current_indent.len <= suggested_indent.len
10014 {
10015 selection.start = Point::new(cursor.row, suggested_indent.len);
10016 selection.end = selection.start;
10017 if row_delta == 0 {
10018 edits.extend(Buffer::edit_for_indent_size_adjustment(
10019 cursor.row,
10020 current_indent,
10021 suggested_indent,
10022 ));
10023 row_delta = suggested_indent.len - current_indent.len;
10024 }
10025 continue;
10026 }
10027
10028 // If current indent is more than suggested indent
10029 // only move cursor to current indent and skip indent
10030 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10031 selection.start = Point::new(cursor.row, current_indent.len);
10032 selection.end = selection.start;
10033 continue;
10034 }
10035 }
10036
10037 // Otherwise, insert a hard or soft tab.
10038 let settings = buffer.language_settings_at(cursor, cx);
10039 let tab_size = if settings.hard_tabs {
10040 IndentSize::tab()
10041 } else {
10042 let tab_size = settings.tab_size.get();
10043 let indent_remainder = snapshot
10044 .text_for_range(Point::new(cursor.row, 0)..cursor)
10045 .flat_map(str::chars)
10046 .fold(row_delta % tab_size, |counter: u32, c| {
10047 if c == '\t' {
10048 0
10049 } else {
10050 (counter + 1) % tab_size
10051 }
10052 });
10053
10054 let chars_to_next_tab_stop = tab_size - indent_remainder;
10055 IndentSize::spaces(chars_to_next_tab_stop)
10056 };
10057 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10058 selection.end = selection.start;
10059 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10060 row_delta += tab_size.len;
10061 }
10062
10063 self.transact(window, cx, |this, window, cx| {
10064 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10065 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10066 this.refresh_edit_prediction(true, false, window, cx);
10067 });
10068 }
10069
10070 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10071 if self.read_only(cx) {
10072 return;
10073 }
10074 if self.mode.is_single_line() {
10075 cx.propagate();
10076 return;
10077 }
10078
10079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10080 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10081 let mut prev_edited_row = 0;
10082 let mut row_delta = 0;
10083 let mut edits = Vec::new();
10084 let buffer = self.buffer.read(cx);
10085 let snapshot = buffer.snapshot(cx);
10086 for selection in &mut selections {
10087 if selection.start.row != prev_edited_row {
10088 row_delta = 0;
10089 }
10090 prev_edited_row = selection.end.row;
10091
10092 row_delta =
10093 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10094 }
10095
10096 self.transact(window, cx, |this, window, cx| {
10097 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10098 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10099 });
10100 }
10101
10102 fn indent_selection(
10103 buffer: &MultiBuffer,
10104 snapshot: &MultiBufferSnapshot,
10105 selection: &mut Selection<Point>,
10106 edits: &mut Vec<(Range<Point>, String)>,
10107 delta_for_start_row: u32,
10108 cx: &App,
10109 ) -> u32 {
10110 let settings = buffer.language_settings_at(selection.start, cx);
10111 let tab_size = settings.tab_size.get();
10112 let indent_kind = if settings.hard_tabs {
10113 IndentKind::Tab
10114 } else {
10115 IndentKind::Space
10116 };
10117 let mut start_row = selection.start.row;
10118 let mut end_row = selection.end.row + 1;
10119
10120 // If a selection ends at the beginning of a line, don't indent
10121 // that last line.
10122 if selection.end.column == 0 && selection.end.row > selection.start.row {
10123 end_row -= 1;
10124 }
10125
10126 // Avoid re-indenting a row that has already been indented by a
10127 // previous selection, but still update this selection's column
10128 // to reflect that indentation.
10129 if delta_for_start_row > 0 {
10130 start_row += 1;
10131 selection.start.column += delta_for_start_row;
10132 if selection.end.row == selection.start.row {
10133 selection.end.column += delta_for_start_row;
10134 }
10135 }
10136
10137 let mut delta_for_end_row = 0;
10138 let has_multiple_rows = start_row + 1 != end_row;
10139 for row in start_row..end_row {
10140 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10141 let indent_delta = match (current_indent.kind, indent_kind) {
10142 (IndentKind::Space, IndentKind::Space) => {
10143 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10144 IndentSize::spaces(columns_to_next_tab_stop)
10145 }
10146 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10147 (_, IndentKind::Tab) => IndentSize::tab(),
10148 };
10149
10150 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10151 0
10152 } else {
10153 selection.start.column
10154 };
10155 let row_start = Point::new(row, start);
10156 edits.push((
10157 row_start..row_start,
10158 indent_delta.chars().collect::<String>(),
10159 ));
10160
10161 // Update this selection's endpoints to reflect the indentation.
10162 if row == selection.start.row {
10163 selection.start.column += indent_delta.len;
10164 }
10165 if row == selection.end.row {
10166 selection.end.column += indent_delta.len;
10167 delta_for_end_row = indent_delta.len;
10168 }
10169 }
10170
10171 if selection.start.row == selection.end.row {
10172 delta_for_start_row + delta_for_end_row
10173 } else {
10174 delta_for_end_row
10175 }
10176 }
10177
10178 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10179 if self.read_only(cx) {
10180 return;
10181 }
10182 if self.mode.is_single_line() {
10183 cx.propagate();
10184 return;
10185 }
10186
10187 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10189 let selections = self.selections.all::<Point>(&display_map);
10190 let mut deletion_ranges = Vec::new();
10191 let mut last_outdent = None;
10192 {
10193 let buffer = self.buffer.read(cx);
10194 let snapshot = buffer.snapshot(cx);
10195 for selection in &selections {
10196 let settings = buffer.language_settings_at(selection.start, cx);
10197 let tab_size = settings.tab_size.get();
10198 let mut rows = selection.spanned_rows(false, &display_map);
10199
10200 // Avoid re-outdenting a row that has already been outdented by a
10201 // previous selection.
10202 if let Some(last_row) = last_outdent
10203 && last_row == rows.start
10204 {
10205 rows.start = rows.start.next_row();
10206 }
10207 let has_multiple_rows = rows.len() > 1;
10208 for row in rows.iter_rows() {
10209 let indent_size = snapshot.indent_size_for_line(row);
10210 if indent_size.len > 0 {
10211 let deletion_len = match indent_size.kind {
10212 IndentKind::Space => {
10213 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10214 if columns_to_prev_tab_stop == 0 {
10215 tab_size
10216 } else {
10217 columns_to_prev_tab_stop
10218 }
10219 }
10220 IndentKind::Tab => 1,
10221 };
10222 let start = if has_multiple_rows
10223 || deletion_len > selection.start.column
10224 || indent_size.len < selection.start.column
10225 {
10226 0
10227 } else {
10228 selection.start.column - deletion_len
10229 };
10230 deletion_ranges.push(
10231 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10232 );
10233 last_outdent = Some(row);
10234 }
10235 }
10236 }
10237 }
10238
10239 self.transact(window, cx, |this, window, cx| {
10240 this.buffer.update(cx, |buffer, cx| {
10241 let empty_str: Arc<str> = Arc::default();
10242 buffer.edit(
10243 deletion_ranges
10244 .into_iter()
10245 .map(|range| (range, empty_str.clone())),
10246 None,
10247 cx,
10248 );
10249 });
10250 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10251 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10252 });
10253 }
10254
10255 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10256 if self.read_only(cx) {
10257 return;
10258 }
10259 if self.mode.is_single_line() {
10260 cx.propagate();
10261 return;
10262 }
10263
10264 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10265 let selections = self
10266 .selections
10267 .all::<usize>(&self.display_snapshot(cx))
10268 .into_iter()
10269 .map(|s| s.range());
10270
10271 self.transact(window, cx, |this, window, cx| {
10272 this.buffer.update(cx, |buffer, cx| {
10273 buffer.autoindent_ranges(selections, cx);
10274 });
10275 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10276 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10277 });
10278 }
10279
10280 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10283 let selections = self.selections.all::<Point>(&display_map);
10284
10285 let mut new_cursors = Vec::new();
10286 let mut edit_ranges = Vec::new();
10287 let mut selections = selections.iter().peekable();
10288 while let Some(selection) = selections.next() {
10289 let mut rows = selection.spanned_rows(false, &display_map);
10290
10291 // Accumulate contiguous regions of rows that we want to delete.
10292 while let Some(next_selection) = selections.peek() {
10293 let next_rows = next_selection.spanned_rows(false, &display_map);
10294 if next_rows.start <= rows.end {
10295 rows.end = next_rows.end;
10296 selections.next().unwrap();
10297 } else {
10298 break;
10299 }
10300 }
10301
10302 let buffer = display_map.buffer_snapshot();
10303 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10304 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10305 // If there's a line after the range, delete the \n from the end of the row range
10306 (
10307 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10308 rows.end,
10309 )
10310 } else {
10311 // If there isn't a line after the range, delete the \n from the line before the
10312 // start of the row range
10313 edit_start = edit_start.saturating_sub(1);
10314 (buffer.len(), rows.start.previous_row())
10315 };
10316
10317 let text_layout_details = self.text_layout_details(window);
10318 let x = display_map.x_for_display_point(
10319 selection.head().to_display_point(&display_map),
10320 &text_layout_details,
10321 );
10322 let row = Point::new(target_row.0, 0)
10323 .to_display_point(&display_map)
10324 .row();
10325 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10326
10327 new_cursors.push((
10328 selection.id,
10329 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10330 SelectionGoal::None,
10331 ));
10332 edit_ranges.push(edit_start..edit_end);
10333 }
10334
10335 self.transact(window, cx, |this, window, cx| {
10336 let buffer = this.buffer.update(cx, |buffer, cx| {
10337 let empty_str: Arc<str> = Arc::default();
10338 buffer.edit(
10339 edit_ranges
10340 .into_iter()
10341 .map(|range| (range, empty_str.clone())),
10342 None,
10343 cx,
10344 );
10345 buffer.snapshot(cx)
10346 });
10347 let new_selections = new_cursors
10348 .into_iter()
10349 .map(|(id, cursor, goal)| {
10350 let cursor = cursor.to_point(&buffer);
10351 Selection {
10352 id,
10353 start: cursor,
10354 end: cursor,
10355 reversed: false,
10356 goal,
10357 }
10358 })
10359 .collect();
10360
10361 this.change_selections(Default::default(), window, cx, |s| {
10362 s.select(new_selections);
10363 });
10364 });
10365 }
10366
10367 pub fn join_lines_impl(
10368 &mut self,
10369 insert_whitespace: bool,
10370 window: &mut Window,
10371 cx: &mut Context<Self>,
10372 ) {
10373 if self.read_only(cx) {
10374 return;
10375 }
10376 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10377 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10378 let start = MultiBufferRow(selection.start.row);
10379 // Treat single line selections as if they include the next line. Otherwise this action
10380 // would do nothing for single line selections individual cursors.
10381 let end = if selection.start.row == selection.end.row {
10382 MultiBufferRow(selection.start.row + 1)
10383 } else {
10384 MultiBufferRow(selection.end.row)
10385 };
10386
10387 if let Some(last_row_range) = row_ranges.last_mut()
10388 && start <= last_row_range.end
10389 {
10390 last_row_range.end = end;
10391 continue;
10392 }
10393 row_ranges.push(start..end);
10394 }
10395
10396 let snapshot = self.buffer.read(cx).snapshot(cx);
10397 let mut cursor_positions = Vec::new();
10398 for row_range in &row_ranges {
10399 let anchor = snapshot.anchor_before(Point::new(
10400 row_range.end.previous_row().0,
10401 snapshot.line_len(row_range.end.previous_row()),
10402 ));
10403 cursor_positions.push(anchor..anchor);
10404 }
10405
10406 self.transact(window, cx, |this, window, cx| {
10407 for row_range in row_ranges.into_iter().rev() {
10408 for row in row_range.iter_rows().rev() {
10409 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10410 let next_line_row = row.next_row();
10411 let indent = snapshot.indent_size_for_line(next_line_row);
10412 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10413
10414 let replace =
10415 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10416 " "
10417 } else {
10418 ""
10419 };
10420
10421 this.buffer.update(cx, |buffer, cx| {
10422 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10423 });
10424 }
10425 }
10426
10427 this.change_selections(Default::default(), window, cx, |s| {
10428 s.select_anchor_ranges(cursor_positions)
10429 });
10430 });
10431 }
10432
10433 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10435 self.join_lines_impl(true, window, cx);
10436 }
10437
10438 pub fn sort_lines_case_sensitive(
10439 &mut self,
10440 _: &SortLinesCaseSensitive,
10441 window: &mut Window,
10442 cx: &mut Context<Self>,
10443 ) {
10444 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10445 }
10446
10447 pub fn sort_lines_by_length(
10448 &mut self,
10449 _: &SortLinesByLength,
10450 window: &mut Window,
10451 cx: &mut Context<Self>,
10452 ) {
10453 self.manipulate_immutable_lines(window, cx, |lines| {
10454 lines.sort_by_key(|&line| line.chars().count())
10455 })
10456 }
10457
10458 pub fn sort_lines_case_insensitive(
10459 &mut self,
10460 _: &SortLinesCaseInsensitive,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.manipulate_immutable_lines(window, cx, |lines| {
10465 lines.sort_by_key(|line| line.to_lowercase())
10466 })
10467 }
10468
10469 pub fn unique_lines_case_insensitive(
10470 &mut self,
10471 _: &UniqueLinesCaseInsensitive,
10472 window: &mut Window,
10473 cx: &mut Context<Self>,
10474 ) {
10475 self.manipulate_immutable_lines(window, cx, |lines| {
10476 let mut seen = HashSet::default();
10477 lines.retain(|line| seen.insert(line.to_lowercase()));
10478 })
10479 }
10480
10481 pub fn unique_lines_case_sensitive(
10482 &mut self,
10483 _: &UniqueLinesCaseSensitive,
10484 window: &mut Window,
10485 cx: &mut Context<Self>,
10486 ) {
10487 self.manipulate_immutable_lines(window, cx, |lines| {
10488 let mut seen = HashSet::default();
10489 lines.retain(|line| seen.insert(*line));
10490 })
10491 }
10492
10493 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10494 let snapshot = self.buffer.read(cx).snapshot(cx);
10495 for selection in self.selections.disjoint_anchors_arc().iter() {
10496 if snapshot
10497 .language_at(selection.start)
10498 .and_then(|lang| lang.config().wrap_characters.as_ref())
10499 .is_some()
10500 {
10501 return true;
10502 }
10503 }
10504 false
10505 }
10506
10507 fn wrap_selections_in_tag(
10508 &mut self,
10509 _: &WrapSelectionsInTag,
10510 window: &mut Window,
10511 cx: &mut Context<Self>,
10512 ) {
10513 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10514
10515 let snapshot = self.buffer.read(cx).snapshot(cx);
10516
10517 let mut edits = Vec::new();
10518 let mut boundaries = Vec::new();
10519
10520 for selection in self
10521 .selections
10522 .all_adjusted(&self.display_snapshot(cx))
10523 .iter()
10524 {
10525 let Some(wrap_config) = snapshot
10526 .language_at(selection.start)
10527 .and_then(|lang| lang.config().wrap_characters.clone())
10528 else {
10529 continue;
10530 };
10531
10532 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10533 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10534
10535 let start_before = snapshot.anchor_before(selection.start);
10536 let end_after = snapshot.anchor_after(selection.end);
10537
10538 edits.push((start_before..start_before, open_tag));
10539 edits.push((end_after..end_after, close_tag));
10540
10541 boundaries.push((
10542 start_before,
10543 end_after,
10544 wrap_config.start_prefix.len(),
10545 wrap_config.end_suffix.len(),
10546 ));
10547 }
10548
10549 if edits.is_empty() {
10550 return;
10551 }
10552
10553 self.transact(window, cx, |this, window, cx| {
10554 let buffer = this.buffer.update(cx, |buffer, cx| {
10555 buffer.edit(edits, None, cx);
10556 buffer.snapshot(cx)
10557 });
10558
10559 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10560 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10561 boundaries.into_iter()
10562 {
10563 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10564 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10565 new_selections.push(open_offset..open_offset);
10566 new_selections.push(close_offset..close_offset);
10567 }
10568
10569 this.change_selections(Default::default(), window, cx, |s| {
10570 s.select_ranges(new_selections);
10571 });
10572
10573 this.request_autoscroll(Autoscroll::fit(), cx);
10574 });
10575 }
10576
10577 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10578 let Some(project) = self.project.clone() else {
10579 return;
10580 };
10581 self.reload(project, window, cx)
10582 .detach_and_notify_err(window, cx);
10583 }
10584
10585 pub fn restore_file(
10586 &mut self,
10587 _: &::git::RestoreFile,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) {
10591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10592 let mut buffer_ids = HashSet::default();
10593 let snapshot = self.buffer().read(cx).snapshot(cx);
10594 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10595 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10596 }
10597
10598 let buffer = self.buffer().read(cx);
10599 let ranges = buffer_ids
10600 .into_iter()
10601 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10602 .collect::<Vec<_>>();
10603
10604 self.restore_hunks_in_ranges(ranges, window, cx);
10605 }
10606
10607 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10609 let selections = self
10610 .selections
10611 .all(&self.display_snapshot(cx))
10612 .into_iter()
10613 .map(|s| s.range())
10614 .collect();
10615 self.restore_hunks_in_ranges(selections, window, cx);
10616 }
10617
10618 pub fn restore_hunks_in_ranges(
10619 &mut self,
10620 ranges: Vec<Range<Point>>,
10621 window: &mut Window,
10622 cx: &mut Context<Editor>,
10623 ) {
10624 let mut revert_changes = HashMap::default();
10625 let chunk_by = self
10626 .snapshot(window, cx)
10627 .hunks_for_ranges(ranges)
10628 .into_iter()
10629 .chunk_by(|hunk| hunk.buffer_id);
10630 for (buffer_id, hunks) in &chunk_by {
10631 let hunks = hunks.collect::<Vec<_>>();
10632 for hunk in &hunks {
10633 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10634 }
10635 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10636 }
10637 drop(chunk_by);
10638 if !revert_changes.is_empty() {
10639 self.transact(window, cx, |editor, window, cx| {
10640 editor.restore(revert_changes, window, cx);
10641 });
10642 }
10643 }
10644
10645 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10646 if let Some(status) = self
10647 .addons
10648 .iter()
10649 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10650 {
10651 return Some(status);
10652 }
10653 self.project
10654 .as_ref()?
10655 .read(cx)
10656 .status_for_buffer_id(buffer_id, cx)
10657 }
10658
10659 pub fn open_active_item_in_terminal(
10660 &mut self,
10661 _: &OpenInTerminal,
10662 window: &mut Window,
10663 cx: &mut Context<Self>,
10664 ) {
10665 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10666 let project_path = buffer.read(cx).project_path(cx)?;
10667 let project = self.project()?.read(cx);
10668 let entry = project.entry_for_path(&project_path, cx)?;
10669 let parent = match &entry.canonical_path {
10670 Some(canonical_path) => canonical_path.to_path_buf(),
10671 None => project.absolute_path(&project_path, cx)?,
10672 }
10673 .parent()?
10674 .to_path_buf();
10675 Some(parent)
10676 }) {
10677 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10678 }
10679 }
10680
10681 fn set_breakpoint_context_menu(
10682 &mut self,
10683 display_row: DisplayRow,
10684 position: Option<Anchor>,
10685 clicked_point: gpui::Point<Pixels>,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 let source = self
10690 .buffer
10691 .read(cx)
10692 .snapshot(cx)
10693 .anchor_before(Point::new(display_row.0, 0u32));
10694
10695 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10696
10697 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10698 self,
10699 source,
10700 clicked_point,
10701 context_menu,
10702 window,
10703 cx,
10704 );
10705 }
10706
10707 fn add_edit_breakpoint_block(
10708 &mut self,
10709 anchor: Anchor,
10710 breakpoint: &Breakpoint,
10711 edit_action: BreakpointPromptEditAction,
10712 window: &mut Window,
10713 cx: &mut Context<Self>,
10714 ) {
10715 let weak_editor = cx.weak_entity();
10716 let bp_prompt = cx.new(|cx| {
10717 BreakpointPromptEditor::new(
10718 weak_editor,
10719 anchor,
10720 breakpoint.clone(),
10721 edit_action,
10722 window,
10723 cx,
10724 )
10725 });
10726
10727 let height = bp_prompt.update(cx, |this, cx| {
10728 this.prompt
10729 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10730 });
10731 let cloned_prompt = bp_prompt.clone();
10732 let blocks = vec![BlockProperties {
10733 style: BlockStyle::Sticky,
10734 placement: BlockPlacement::Above(anchor),
10735 height: Some(height),
10736 render: Arc::new(move |cx| {
10737 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10738 cloned_prompt.clone().into_any_element()
10739 }),
10740 priority: 0,
10741 }];
10742
10743 let focus_handle = bp_prompt.focus_handle(cx);
10744 window.focus(&focus_handle);
10745
10746 let block_ids = self.insert_blocks(blocks, None, cx);
10747 bp_prompt.update(cx, |prompt, _| {
10748 prompt.add_block_ids(block_ids);
10749 });
10750 }
10751
10752 pub(crate) fn breakpoint_at_row(
10753 &self,
10754 row: u32,
10755 window: &mut Window,
10756 cx: &mut Context<Self>,
10757 ) -> Option<(Anchor, Breakpoint)> {
10758 let snapshot = self.snapshot(window, cx);
10759 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10760
10761 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10762 }
10763
10764 pub(crate) fn breakpoint_at_anchor(
10765 &self,
10766 breakpoint_position: Anchor,
10767 snapshot: &EditorSnapshot,
10768 cx: &mut Context<Self>,
10769 ) -> Option<(Anchor, Breakpoint)> {
10770 let buffer = self
10771 .buffer
10772 .read(cx)
10773 .buffer_for_anchor(breakpoint_position, cx)?;
10774
10775 let enclosing_excerpt = breakpoint_position.excerpt_id;
10776 let buffer_snapshot = buffer.read(cx).snapshot();
10777
10778 let row = buffer_snapshot
10779 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10780 .row;
10781
10782 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10783 let anchor_end = snapshot
10784 .buffer_snapshot()
10785 .anchor_after(Point::new(row, line_len));
10786
10787 self.breakpoint_store
10788 .as_ref()?
10789 .read_with(cx, |breakpoint_store, cx| {
10790 breakpoint_store
10791 .breakpoints(
10792 &buffer,
10793 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10794 &buffer_snapshot,
10795 cx,
10796 )
10797 .next()
10798 .and_then(|(bp, _)| {
10799 let breakpoint_row = buffer_snapshot
10800 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10801 .row;
10802
10803 if breakpoint_row == row {
10804 snapshot
10805 .buffer_snapshot()
10806 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10807 .map(|position| (position, bp.bp.clone()))
10808 } else {
10809 None
10810 }
10811 })
10812 })
10813 }
10814
10815 pub fn edit_log_breakpoint(
10816 &mut self,
10817 _: &EditLogBreakpoint,
10818 window: &mut Window,
10819 cx: &mut Context<Self>,
10820 ) {
10821 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10822 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10823 message: None,
10824 state: BreakpointState::Enabled,
10825 condition: None,
10826 hit_condition: None,
10827 });
10828
10829 self.add_edit_breakpoint_block(
10830 anchor,
10831 &breakpoint,
10832 BreakpointPromptEditAction::Log,
10833 window,
10834 cx,
10835 );
10836 }
10837 }
10838
10839 fn breakpoints_at_cursors(
10840 &self,
10841 window: &mut Window,
10842 cx: &mut Context<Self>,
10843 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10844 let snapshot = self.snapshot(window, cx);
10845 let cursors = self
10846 .selections
10847 .disjoint_anchors_arc()
10848 .iter()
10849 .map(|selection| {
10850 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10851
10852 let breakpoint_position = self
10853 .breakpoint_at_row(cursor_position.row, window, cx)
10854 .map(|bp| bp.0)
10855 .unwrap_or_else(|| {
10856 snapshot
10857 .display_snapshot
10858 .buffer_snapshot()
10859 .anchor_after(Point::new(cursor_position.row, 0))
10860 });
10861
10862 let breakpoint = self
10863 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10864 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10865
10866 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10867 })
10868 // 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.
10869 .collect::<HashMap<Anchor, _>>();
10870
10871 cursors.into_iter().collect()
10872 }
10873
10874 pub fn enable_breakpoint(
10875 &mut self,
10876 _: &crate::actions::EnableBreakpoint,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10881 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10882 continue;
10883 };
10884 self.edit_breakpoint_at_anchor(
10885 anchor,
10886 breakpoint,
10887 BreakpointEditAction::InvertState,
10888 cx,
10889 );
10890 }
10891 }
10892
10893 pub fn disable_breakpoint(
10894 &mut self,
10895 _: &crate::actions::DisableBreakpoint,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10900 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10901 continue;
10902 };
10903 self.edit_breakpoint_at_anchor(
10904 anchor,
10905 breakpoint,
10906 BreakpointEditAction::InvertState,
10907 cx,
10908 );
10909 }
10910 }
10911
10912 pub fn toggle_breakpoint(
10913 &mut self,
10914 _: &crate::actions::ToggleBreakpoint,
10915 window: &mut Window,
10916 cx: &mut Context<Self>,
10917 ) {
10918 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10919 if let Some(breakpoint) = breakpoint {
10920 self.edit_breakpoint_at_anchor(
10921 anchor,
10922 breakpoint,
10923 BreakpointEditAction::Toggle,
10924 cx,
10925 );
10926 } else {
10927 self.edit_breakpoint_at_anchor(
10928 anchor,
10929 Breakpoint::new_standard(),
10930 BreakpointEditAction::Toggle,
10931 cx,
10932 );
10933 }
10934 }
10935 }
10936
10937 pub fn edit_breakpoint_at_anchor(
10938 &mut self,
10939 breakpoint_position: Anchor,
10940 breakpoint: Breakpoint,
10941 edit_action: BreakpointEditAction,
10942 cx: &mut Context<Self>,
10943 ) {
10944 let Some(breakpoint_store) = &self.breakpoint_store else {
10945 return;
10946 };
10947
10948 let Some(buffer) = self
10949 .buffer
10950 .read(cx)
10951 .buffer_for_anchor(breakpoint_position, cx)
10952 else {
10953 return;
10954 };
10955
10956 breakpoint_store.update(cx, |breakpoint_store, cx| {
10957 breakpoint_store.toggle_breakpoint(
10958 buffer,
10959 BreakpointWithPosition {
10960 position: breakpoint_position.text_anchor,
10961 bp: breakpoint,
10962 },
10963 edit_action,
10964 cx,
10965 );
10966 });
10967
10968 cx.notify();
10969 }
10970
10971 #[cfg(any(test, feature = "test-support"))]
10972 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10973 self.breakpoint_store.clone()
10974 }
10975
10976 pub fn prepare_restore_change(
10977 &self,
10978 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10979 hunk: &MultiBufferDiffHunk,
10980 cx: &mut App,
10981 ) -> Option<()> {
10982 if hunk.is_created_file() {
10983 return None;
10984 }
10985 let buffer = self.buffer.read(cx);
10986 let diff = buffer.diff_for(hunk.buffer_id)?;
10987 let buffer = buffer.buffer(hunk.buffer_id)?;
10988 let buffer = buffer.read(cx);
10989 let original_text = diff
10990 .read(cx)
10991 .base_text()
10992 .as_rope()
10993 .slice(hunk.diff_base_byte_range.clone());
10994 let buffer_snapshot = buffer.snapshot();
10995 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10996 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10997 probe
10998 .0
10999 .start
11000 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11001 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11002 }) {
11003 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11004 Some(())
11005 } else {
11006 None
11007 }
11008 }
11009
11010 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11011 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11012 }
11013
11014 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11015 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11016 }
11017
11018 fn manipulate_lines<M>(
11019 &mut self,
11020 window: &mut Window,
11021 cx: &mut Context<Self>,
11022 mut manipulate: M,
11023 ) where
11024 M: FnMut(&str) -> LineManipulationResult,
11025 {
11026 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11027
11028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11029 let buffer = self.buffer.read(cx).snapshot(cx);
11030
11031 let mut edits = Vec::new();
11032
11033 let selections = self.selections.all::<Point>(&display_map);
11034 let mut selections = selections.iter().peekable();
11035 let mut contiguous_row_selections = Vec::new();
11036 let mut new_selections = Vec::new();
11037 let mut added_lines = 0;
11038 let mut removed_lines = 0;
11039
11040 while let Some(selection) = selections.next() {
11041 let (start_row, end_row) = consume_contiguous_rows(
11042 &mut contiguous_row_selections,
11043 selection,
11044 &display_map,
11045 &mut selections,
11046 );
11047
11048 let start_point = Point::new(start_row.0, 0);
11049 let end_point = Point::new(
11050 end_row.previous_row().0,
11051 buffer.line_len(end_row.previous_row()),
11052 );
11053 let text = buffer
11054 .text_for_range(start_point..end_point)
11055 .collect::<String>();
11056
11057 let LineManipulationResult {
11058 new_text,
11059 line_count_before,
11060 line_count_after,
11061 } = manipulate(&text);
11062
11063 edits.push((start_point..end_point, new_text));
11064
11065 // Selections must change based on added and removed line count
11066 let start_row =
11067 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11068 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11069 new_selections.push(Selection {
11070 id: selection.id,
11071 start: start_row,
11072 end: end_row,
11073 goal: SelectionGoal::None,
11074 reversed: selection.reversed,
11075 });
11076
11077 if line_count_after > line_count_before {
11078 added_lines += line_count_after - line_count_before;
11079 } else if line_count_before > line_count_after {
11080 removed_lines += line_count_before - line_count_after;
11081 }
11082 }
11083
11084 self.transact(window, cx, |this, window, cx| {
11085 let buffer = this.buffer.update(cx, |buffer, cx| {
11086 buffer.edit(edits, None, cx);
11087 buffer.snapshot(cx)
11088 });
11089
11090 // Recalculate offsets on newly edited buffer
11091 let new_selections = new_selections
11092 .iter()
11093 .map(|s| {
11094 let start_point = Point::new(s.start.0, 0);
11095 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11096 Selection {
11097 id: s.id,
11098 start: buffer.point_to_offset(start_point),
11099 end: buffer.point_to_offset(end_point),
11100 goal: s.goal,
11101 reversed: s.reversed,
11102 }
11103 })
11104 .collect();
11105
11106 this.change_selections(Default::default(), window, cx, |s| {
11107 s.select(new_selections);
11108 });
11109
11110 this.request_autoscroll(Autoscroll::fit(), cx);
11111 });
11112 }
11113
11114 fn manipulate_immutable_lines<Fn>(
11115 &mut self,
11116 window: &mut Window,
11117 cx: &mut Context<Self>,
11118 mut callback: Fn,
11119 ) where
11120 Fn: FnMut(&mut Vec<&str>),
11121 {
11122 self.manipulate_lines(window, cx, |text| {
11123 let mut lines: Vec<&str> = text.split('\n').collect();
11124 let line_count_before = lines.len();
11125
11126 callback(&mut lines);
11127
11128 LineManipulationResult {
11129 new_text: lines.join("\n"),
11130 line_count_before,
11131 line_count_after: lines.len(),
11132 }
11133 });
11134 }
11135
11136 fn manipulate_mutable_lines<Fn>(
11137 &mut self,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 mut callback: Fn,
11141 ) where
11142 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11143 {
11144 self.manipulate_lines(window, cx, |text| {
11145 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11146 let line_count_before = lines.len();
11147
11148 callback(&mut lines);
11149
11150 LineManipulationResult {
11151 new_text: lines.join("\n"),
11152 line_count_before,
11153 line_count_after: lines.len(),
11154 }
11155 });
11156 }
11157
11158 pub fn convert_indentation_to_spaces(
11159 &mut self,
11160 _: &ConvertIndentationToSpaces,
11161 window: &mut Window,
11162 cx: &mut Context<Self>,
11163 ) {
11164 let settings = self.buffer.read(cx).language_settings(cx);
11165 let tab_size = settings.tab_size.get() as usize;
11166
11167 self.manipulate_mutable_lines(window, cx, |lines| {
11168 // Allocates a reasonably sized scratch buffer once for the whole loop
11169 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11170 // Avoids recomputing spaces that could be inserted many times
11171 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11172 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11173 .collect();
11174
11175 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11176 let mut chars = line.as_ref().chars();
11177 let mut col = 0;
11178 let mut changed = false;
11179
11180 for ch in chars.by_ref() {
11181 match ch {
11182 ' ' => {
11183 reindented_line.push(' ');
11184 col += 1;
11185 }
11186 '\t' => {
11187 // \t are converted to spaces depending on the current column
11188 let spaces_len = tab_size - (col % tab_size);
11189 reindented_line.extend(&space_cache[spaces_len - 1]);
11190 col += spaces_len;
11191 changed = true;
11192 }
11193 _ => {
11194 // If we dont append before break, the character is consumed
11195 reindented_line.push(ch);
11196 break;
11197 }
11198 }
11199 }
11200
11201 if !changed {
11202 reindented_line.clear();
11203 continue;
11204 }
11205 // Append the rest of the line and replace old reference with new one
11206 reindented_line.extend(chars);
11207 *line = Cow::Owned(reindented_line.clone());
11208 reindented_line.clear();
11209 }
11210 });
11211 }
11212
11213 pub fn convert_indentation_to_tabs(
11214 &mut self,
11215 _: &ConvertIndentationToTabs,
11216 window: &mut Window,
11217 cx: &mut Context<Self>,
11218 ) {
11219 let settings = self.buffer.read(cx).language_settings(cx);
11220 let tab_size = settings.tab_size.get() as usize;
11221
11222 self.manipulate_mutable_lines(window, cx, |lines| {
11223 // Allocates a reasonably sized buffer once for the whole loop
11224 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11225 // Avoids recomputing spaces that could be inserted many times
11226 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11227 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11228 .collect();
11229
11230 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11231 let mut chars = line.chars();
11232 let mut spaces_count = 0;
11233 let mut first_non_indent_char = None;
11234 let mut changed = false;
11235
11236 for ch in chars.by_ref() {
11237 match ch {
11238 ' ' => {
11239 // Keep track of spaces. Append \t when we reach tab_size
11240 spaces_count += 1;
11241 changed = true;
11242 if spaces_count == tab_size {
11243 reindented_line.push('\t');
11244 spaces_count = 0;
11245 }
11246 }
11247 '\t' => {
11248 reindented_line.push('\t');
11249 spaces_count = 0;
11250 }
11251 _ => {
11252 // Dont append it yet, we might have remaining spaces
11253 first_non_indent_char = Some(ch);
11254 break;
11255 }
11256 }
11257 }
11258
11259 if !changed {
11260 reindented_line.clear();
11261 continue;
11262 }
11263 // Remaining spaces that didn't make a full tab stop
11264 if spaces_count > 0 {
11265 reindented_line.extend(&space_cache[spaces_count - 1]);
11266 }
11267 // If we consume an extra character that was not indentation, add it back
11268 if let Some(extra_char) = first_non_indent_char {
11269 reindented_line.push(extra_char);
11270 }
11271 // Append the rest of the line and replace old reference with new one
11272 reindented_line.extend(chars);
11273 *line = Cow::Owned(reindented_line.clone());
11274 reindented_line.clear();
11275 }
11276 });
11277 }
11278
11279 pub fn convert_to_upper_case(
11280 &mut self,
11281 _: &ConvertToUpperCase,
11282 window: &mut Window,
11283 cx: &mut Context<Self>,
11284 ) {
11285 self.manipulate_text(window, cx, |text| text.to_uppercase())
11286 }
11287
11288 pub fn convert_to_lower_case(
11289 &mut self,
11290 _: &ConvertToLowerCase,
11291 window: &mut Window,
11292 cx: &mut Context<Self>,
11293 ) {
11294 self.manipulate_text(window, cx, |text| text.to_lowercase())
11295 }
11296
11297 pub fn convert_to_title_case(
11298 &mut self,
11299 _: &ConvertToTitleCase,
11300 window: &mut Window,
11301 cx: &mut Context<Self>,
11302 ) {
11303 self.manipulate_text(window, cx, |text| {
11304 text.split('\n')
11305 .map(|line| line.to_case(Case::Title))
11306 .join("\n")
11307 })
11308 }
11309
11310 pub fn convert_to_snake_case(
11311 &mut self,
11312 _: &ConvertToSnakeCase,
11313 window: &mut Window,
11314 cx: &mut Context<Self>,
11315 ) {
11316 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11317 }
11318
11319 pub fn convert_to_kebab_case(
11320 &mut self,
11321 _: &ConvertToKebabCase,
11322 window: &mut Window,
11323 cx: &mut Context<Self>,
11324 ) {
11325 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11326 }
11327
11328 pub fn convert_to_upper_camel_case(
11329 &mut self,
11330 _: &ConvertToUpperCamelCase,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 ) {
11334 self.manipulate_text(window, cx, |text| {
11335 text.split('\n')
11336 .map(|line| line.to_case(Case::UpperCamel))
11337 .join("\n")
11338 })
11339 }
11340
11341 pub fn convert_to_lower_camel_case(
11342 &mut self,
11343 _: &ConvertToLowerCamelCase,
11344 window: &mut Window,
11345 cx: &mut Context<Self>,
11346 ) {
11347 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11348 }
11349
11350 pub fn convert_to_opposite_case(
11351 &mut self,
11352 _: &ConvertToOppositeCase,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 self.manipulate_text(window, cx, |text| {
11357 text.chars()
11358 .fold(String::with_capacity(text.len()), |mut t, c| {
11359 if c.is_uppercase() {
11360 t.extend(c.to_lowercase());
11361 } else {
11362 t.extend(c.to_uppercase());
11363 }
11364 t
11365 })
11366 })
11367 }
11368
11369 pub fn convert_to_sentence_case(
11370 &mut self,
11371 _: &ConvertToSentenceCase,
11372 window: &mut Window,
11373 cx: &mut Context<Self>,
11374 ) {
11375 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11376 }
11377
11378 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11379 self.manipulate_text(window, cx, |text| {
11380 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11381 if has_upper_case_characters {
11382 text.to_lowercase()
11383 } else {
11384 text.to_uppercase()
11385 }
11386 })
11387 }
11388
11389 pub fn convert_to_rot13(
11390 &mut self,
11391 _: &ConvertToRot13,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 self.manipulate_text(window, cx, |text| {
11396 text.chars()
11397 .map(|c| match c {
11398 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11399 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11400 _ => c,
11401 })
11402 .collect()
11403 })
11404 }
11405
11406 pub fn convert_to_rot47(
11407 &mut self,
11408 _: &ConvertToRot47,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 ) {
11412 self.manipulate_text(window, cx, |text| {
11413 text.chars()
11414 .map(|c| {
11415 let code_point = c as u32;
11416 if code_point >= 33 && code_point <= 126 {
11417 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11418 }
11419 c
11420 })
11421 .collect()
11422 })
11423 }
11424
11425 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11426 where
11427 Fn: FnMut(&str) -> String,
11428 {
11429 let buffer = self.buffer.read(cx).snapshot(cx);
11430
11431 let mut new_selections = Vec::new();
11432 let mut edits = Vec::new();
11433 let mut selection_adjustment = 0i32;
11434
11435 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11436 let selection_is_empty = selection.is_empty();
11437
11438 let (start, end) = if selection_is_empty {
11439 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11440 (word_range.start, word_range.end)
11441 } else {
11442 (
11443 buffer.point_to_offset(selection.start),
11444 buffer.point_to_offset(selection.end),
11445 )
11446 };
11447
11448 let text = buffer.text_for_range(start..end).collect::<String>();
11449 let old_length = text.len() as i32;
11450 let text = callback(&text);
11451
11452 new_selections.push(Selection {
11453 start: (start as i32 - selection_adjustment) as usize,
11454 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11455 goal: SelectionGoal::None,
11456 id: selection.id,
11457 reversed: selection.reversed,
11458 });
11459
11460 selection_adjustment += old_length - text.len() as i32;
11461
11462 edits.push((start..end, text));
11463 }
11464
11465 self.transact(window, cx, |this, window, cx| {
11466 this.buffer.update(cx, |buffer, cx| {
11467 buffer.edit(edits, None, cx);
11468 });
11469
11470 this.change_selections(Default::default(), window, cx, |s| {
11471 s.select(new_selections);
11472 });
11473
11474 this.request_autoscroll(Autoscroll::fit(), cx);
11475 });
11476 }
11477
11478 pub fn move_selection_on_drop(
11479 &mut self,
11480 selection: &Selection<Anchor>,
11481 target: DisplayPoint,
11482 is_cut: bool,
11483 window: &mut Window,
11484 cx: &mut Context<Self>,
11485 ) {
11486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11487 let buffer = display_map.buffer_snapshot();
11488 let mut edits = Vec::new();
11489 let insert_point = display_map
11490 .clip_point(target, Bias::Left)
11491 .to_point(&display_map);
11492 let text = buffer
11493 .text_for_range(selection.start..selection.end)
11494 .collect::<String>();
11495 if is_cut {
11496 edits.push(((selection.start..selection.end), String::new()));
11497 }
11498 let insert_anchor = buffer.anchor_before(insert_point);
11499 edits.push(((insert_anchor..insert_anchor), text));
11500 let last_edit_start = insert_anchor.bias_left(buffer);
11501 let last_edit_end = insert_anchor.bias_right(buffer);
11502 self.transact(window, cx, |this, window, cx| {
11503 this.buffer.update(cx, |buffer, cx| {
11504 buffer.edit(edits, None, cx);
11505 });
11506 this.change_selections(Default::default(), window, cx, |s| {
11507 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11508 });
11509 });
11510 }
11511
11512 pub fn clear_selection_drag_state(&mut self) {
11513 self.selection_drag_state = SelectionDragState::None;
11514 }
11515
11516 pub fn duplicate(
11517 &mut self,
11518 upwards: bool,
11519 whole_lines: bool,
11520 window: &mut Window,
11521 cx: &mut Context<Self>,
11522 ) {
11523 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11524
11525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11526 let buffer = display_map.buffer_snapshot();
11527 let selections = self.selections.all::<Point>(&display_map);
11528
11529 let mut edits = Vec::new();
11530 let mut selections_iter = selections.iter().peekable();
11531 while let Some(selection) = selections_iter.next() {
11532 let mut rows = selection.spanned_rows(false, &display_map);
11533 // duplicate line-wise
11534 if whole_lines || selection.start == selection.end {
11535 // Avoid duplicating the same lines twice.
11536 while let Some(next_selection) = selections_iter.peek() {
11537 let next_rows = next_selection.spanned_rows(false, &display_map);
11538 if next_rows.start < rows.end {
11539 rows.end = next_rows.end;
11540 selections_iter.next().unwrap();
11541 } else {
11542 break;
11543 }
11544 }
11545
11546 // Copy the text from the selected row region and splice it either at the start
11547 // or end of the region.
11548 let start = Point::new(rows.start.0, 0);
11549 let end = Point::new(
11550 rows.end.previous_row().0,
11551 buffer.line_len(rows.end.previous_row()),
11552 );
11553
11554 let mut text = buffer.text_for_range(start..end).collect::<String>();
11555
11556 let insert_location = if upwards {
11557 // When duplicating upward, we need to insert before the current line.
11558 // If we're on the last line and it doesn't end with a newline,
11559 // we need to add a newline before the duplicated content.
11560 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11561 && buffer.max_point().column > 0
11562 && !text.ends_with('\n');
11563
11564 if needs_leading_newline {
11565 text.insert(0, '\n');
11566 end
11567 } else {
11568 text.push('\n');
11569 Point::new(rows.start.0, 0)
11570 }
11571 } else {
11572 text.push('\n');
11573 start
11574 };
11575 edits.push((insert_location..insert_location, text));
11576 } else {
11577 // duplicate character-wise
11578 let start = selection.start;
11579 let end = selection.end;
11580 let text = buffer.text_for_range(start..end).collect::<String>();
11581 edits.push((selection.end..selection.end, text));
11582 }
11583 }
11584
11585 self.transact(window, cx, |this, window, cx| {
11586 this.buffer.update(cx, |buffer, cx| {
11587 buffer.edit(edits, None, cx);
11588 });
11589
11590 // When duplicating upward with whole lines, move the cursor to the duplicated line
11591 if upwards && whole_lines {
11592 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11593
11594 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11595 let mut new_ranges = Vec::new();
11596 let selections = s.all::<Point>(&display_map);
11597 let mut selections_iter = selections.iter().peekable();
11598
11599 while let Some(first_selection) = selections_iter.next() {
11600 // Group contiguous selections together to find the total row span
11601 let mut group_selections = vec![first_selection];
11602 let mut rows = first_selection.spanned_rows(false, &display_map);
11603
11604 while let Some(next_selection) = selections_iter.peek() {
11605 let next_rows = next_selection.spanned_rows(false, &display_map);
11606 if next_rows.start < rows.end {
11607 rows.end = next_rows.end;
11608 group_selections.push(selections_iter.next().unwrap());
11609 } else {
11610 break;
11611 }
11612 }
11613
11614 let row_count = rows.end.0 - rows.start.0;
11615
11616 // Move all selections in this group up by the total number of duplicated rows
11617 for selection in group_selections {
11618 let new_start = Point::new(
11619 selection.start.row.saturating_sub(row_count),
11620 selection.start.column,
11621 );
11622
11623 let new_end = Point::new(
11624 selection.end.row.saturating_sub(row_count),
11625 selection.end.column,
11626 );
11627
11628 new_ranges.push(new_start..new_end);
11629 }
11630 }
11631
11632 s.select_ranges(new_ranges);
11633 });
11634 }
11635
11636 this.request_autoscroll(Autoscroll::fit(), cx);
11637 });
11638 }
11639
11640 pub fn duplicate_line_up(
11641 &mut self,
11642 _: &DuplicateLineUp,
11643 window: &mut Window,
11644 cx: &mut Context<Self>,
11645 ) {
11646 self.duplicate(true, true, window, cx);
11647 }
11648
11649 pub fn duplicate_line_down(
11650 &mut self,
11651 _: &DuplicateLineDown,
11652 window: &mut Window,
11653 cx: &mut Context<Self>,
11654 ) {
11655 self.duplicate(false, true, window, cx);
11656 }
11657
11658 pub fn duplicate_selection(
11659 &mut self,
11660 _: &DuplicateSelection,
11661 window: &mut Window,
11662 cx: &mut Context<Self>,
11663 ) {
11664 self.duplicate(false, false, window, cx);
11665 }
11666
11667 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11669 if self.mode.is_single_line() {
11670 cx.propagate();
11671 return;
11672 }
11673
11674 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11675 let buffer = self.buffer.read(cx).snapshot(cx);
11676
11677 let mut edits = Vec::new();
11678 let mut unfold_ranges = Vec::new();
11679 let mut refold_creases = Vec::new();
11680
11681 let selections = self.selections.all::<Point>(&display_map);
11682 let mut selections = selections.iter().peekable();
11683 let mut contiguous_row_selections = Vec::new();
11684 let mut new_selections = Vec::new();
11685
11686 while let Some(selection) = selections.next() {
11687 // Find all the selections that span a contiguous row range
11688 let (start_row, end_row) = consume_contiguous_rows(
11689 &mut contiguous_row_selections,
11690 selection,
11691 &display_map,
11692 &mut selections,
11693 );
11694
11695 // Move the text spanned by the row range to be before the line preceding the row range
11696 if start_row.0 > 0 {
11697 let range_to_move = Point::new(
11698 start_row.previous_row().0,
11699 buffer.line_len(start_row.previous_row()),
11700 )
11701 ..Point::new(
11702 end_row.previous_row().0,
11703 buffer.line_len(end_row.previous_row()),
11704 );
11705 let insertion_point = display_map
11706 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11707 .0;
11708
11709 // Don't move lines across excerpts
11710 if buffer
11711 .excerpt_containing(insertion_point..range_to_move.end)
11712 .is_some()
11713 {
11714 let text = buffer
11715 .text_for_range(range_to_move.clone())
11716 .flat_map(|s| s.chars())
11717 .skip(1)
11718 .chain(['\n'])
11719 .collect::<String>();
11720
11721 edits.push((
11722 buffer.anchor_after(range_to_move.start)
11723 ..buffer.anchor_before(range_to_move.end),
11724 String::new(),
11725 ));
11726 let insertion_anchor = buffer.anchor_after(insertion_point);
11727 edits.push((insertion_anchor..insertion_anchor, text));
11728
11729 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11730
11731 // Move selections up
11732 new_selections.extend(contiguous_row_selections.drain(..).map(
11733 |mut selection| {
11734 selection.start.row -= row_delta;
11735 selection.end.row -= row_delta;
11736 selection
11737 },
11738 ));
11739
11740 // Move folds up
11741 unfold_ranges.push(range_to_move.clone());
11742 for fold in display_map.folds_in_range(
11743 buffer.anchor_before(range_to_move.start)
11744 ..buffer.anchor_after(range_to_move.end),
11745 ) {
11746 let mut start = fold.range.start.to_point(&buffer);
11747 let mut end = fold.range.end.to_point(&buffer);
11748 start.row -= row_delta;
11749 end.row -= row_delta;
11750 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11751 }
11752 }
11753 }
11754
11755 // If we didn't move line(s), preserve the existing selections
11756 new_selections.append(&mut contiguous_row_selections);
11757 }
11758
11759 self.transact(window, cx, |this, window, cx| {
11760 this.unfold_ranges(&unfold_ranges, true, true, cx);
11761 this.buffer.update(cx, |buffer, cx| {
11762 for (range, text) in edits {
11763 buffer.edit([(range, text)], None, cx);
11764 }
11765 });
11766 this.fold_creases(refold_creases, true, window, cx);
11767 this.change_selections(Default::default(), window, cx, |s| {
11768 s.select(new_selections);
11769 })
11770 });
11771 }
11772
11773 pub fn move_line_down(
11774 &mut self,
11775 _: &MoveLineDown,
11776 window: &mut Window,
11777 cx: &mut Context<Self>,
11778 ) {
11779 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11780 if self.mode.is_single_line() {
11781 cx.propagate();
11782 return;
11783 }
11784
11785 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11786 let buffer = self.buffer.read(cx).snapshot(cx);
11787
11788 let mut edits = Vec::new();
11789 let mut unfold_ranges = Vec::new();
11790 let mut refold_creases = Vec::new();
11791
11792 let selections = self.selections.all::<Point>(&display_map);
11793 let mut selections = selections.iter().peekable();
11794 let mut contiguous_row_selections = Vec::new();
11795 let mut new_selections = Vec::new();
11796
11797 while let Some(selection) = selections.next() {
11798 // Find all the selections that span a contiguous row range
11799 let (start_row, end_row) = consume_contiguous_rows(
11800 &mut contiguous_row_selections,
11801 selection,
11802 &display_map,
11803 &mut selections,
11804 );
11805
11806 // Move the text spanned by the row range to be after the last line of the row range
11807 if end_row.0 <= buffer.max_point().row {
11808 let range_to_move =
11809 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11810 let insertion_point = display_map
11811 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11812 .0;
11813
11814 // Don't move lines across excerpt boundaries
11815 if buffer
11816 .excerpt_containing(range_to_move.start..insertion_point)
11817 .is_some()
11818 {
11819 let mut text = String::from("\n");
11820 text.extend(buffer.text_for_range(range_to_move.clone()));
11821 text.pop(); // Drop trailing newline
11822 edits.push((
11823 buffer.anchor_after(range_to_move.start)
11824 ..buffer.anchor_before(range_to_move.end),
11825 String::new(),
11826 ));
11827 let insertion_anchor = buffer.anchor_after(insertion_point);
11828 edits.push((insertion_anchor..insertion_anchor, text));
11829
11830 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11831
11832 // Move selections down
11833 new_selections.extend(contiguous_row_selections.drain(..).map(
11834 |mut selection| {
11835 selection.start.row += row_delta;
11836 selection.end.row += row_delta;
11837 selection
11838 },
11839 ));
11840
11841 // Move folds down
11842 unfold_ranges.push(range_to_move.clone());
11843 for fold in display_map.folds_in_range(
11844 buffer.anchor_before(range_to_move.start)
11845 ..buffer.anchor_after(range_to_move.end),
11846 ) {
11847 let mut start = fold.range.start.to_point(&buffer);
11848 let mut end = fold.range.end.to_point(&buffer);
11849 start.row += row_delta;
11850 end.row += row_delta;
11851 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11852 }
11853 }
11854 }
11855
11856 // If we didn't move line(s), preserve the existing selections
11857 new_selections.append(&mut contiguous_row_selections);
11858 }
11859
11860 self.transact(window, cx, |this, window, cx| {
11861 this.unfold_ranges(&unfold_ranges, true, true, cx);
11862 this.buffer.update(cx, |buffer, cx| {
11863 for (range, text) in edits {
11864 buffer.edit([(range, text)], None, cx);
11865 }
11866 });
11867 this.fold_creases(refold_creases, true, window, cx);
11868 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11869 });
11870 }
11871
11872 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11874 let text_layout_details = &self.text_layout_details(window);
11875 self.transact(window, cx, |this, window, cx| {
11876 let edits = this.change_selections(Default::default(), window, cx, |s| {
11877 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11878 s.move_with(|display_map, selection| {
11879 if !selection.is_empty() {
11880 return;
11881 }
11882
11883 let mut head = selection.head();
11884 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11885 if head.column() == display_map.line_len(head.row()) {
11886 transpose_offset = display_map
11887 .buffer_snapshot()
11888 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11889 }
11890
11891 if transpose_offset == 0 {
11892 return;
11893 }
11894
11895 *head.column_mut() += 1;
11896 head = display_map.clip_point(head, Bias::Right);
11897 let goal = SelectionGoal::HorizontalPosition(
11898 display_map
11899 .x_for_display_point(head, text_layout_details)
11900 .into(),
11901 );
11902 selection.collapse_to(head, goal);
11903
11904 let transpose_start = display_map
11905 .buffer_snapshot()
11906 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11907 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11908 let transpose_end = display_map
11909 .buffer_snapshot()
11910 .clip_offset(transpose_offset + 1, Bias::Right);
11911 if let Some(ch) = display_map
11912 .buffer_snapshot()
11913 .chars_at(transpose_start)
11914 .next()
11915 {
11916 edits.push((transpose_start..transpose_offset, String::new()));
11917 edits.push((transpose_end..transpose_end, ch.to_string()));
11918 }
11919 }
11920 });
11921 edits
11922 });
11923 this.buffer
11924 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11925 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11926 this.change_selections(Default::default(), window, cx, |s| {
11927 s.select(selections);
11928 });
11929 });
11930 }
11931
11932 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11934 if self.mode.is_single_line() {
11935 cx.propagate();
11936 return;
11937 }
11938
11939 self.rewrap_impl(RewrapOptions::default(), cx)
11940 }
11941
11942 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11943 let buffer = self.buffer.read(cx).snapshot(cx);
11944 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11945
11946 #[derive(Clone, Debug, PartialEq)]
11947 enum CommentFormat {
11948 /// single line comment, with prefix for line
11949 Line(String),
11950 /// single line within a block comment, with prefix for line
11951 BlockLine(String),
11952 /// a single line of a block comment that includes the initial delimiter
11953 BlockCommentWithStart(BlockCommentConfig),
11954 /// a single line of a block comment that includes the ending delimiter
11955 BlockCommentWithEnd(BlockCommentConfig),
11956 }
11957
11958 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11959 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11960 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11961 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11962 .peekable();
11963
11964 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11965 row
11966 } else {
11967 return Vec::new();
11968 };
11969
11970 let language_settings = buffer.language_settings_at(selection.head(), cx);
11971 let language_scope = buffer.language_scope_at(selection.head());
11972
11973 let indent_and_prefix_for_row =
11974 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11975 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11976 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11977 &language_scope
11978 {
11979 let indent_end = Point::new(row, indent.len);
11980 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11981 let line_text_after_indent = buffer
11982 .text_for_range(indent_end..line_end)
11983 .collect::<String>();
11984
11985 let is_within_comment_override = buffer
11986 .language_scope_at(indent_end)
11987 .is_some_and(|scope| scope.override_name() == Some("comment"));
11988 let comment_delimiters = if is_within_comment_override {
11989 // we are within a comment syntax node, but we don't
11990 // yet know what kind of comment: block, doc or line
11991 match (
11992 language_scope.documentation_comment(),
11993 language_scope.block_comment(),
11994 ) {
11995 (Some(config), _) | (_, Some(config))
11996 if buffer.contains_str_at(indent_end, &config.start) =>
11997 {
11998 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11999 }
12000 (Some(config), _) | (_, Some(config))
12001 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12002 {
12003 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12004 }
12005 (Some(config), _) | (_, Some(config))
12006 if buffer.contains_str_at(indent_end, &config.prefix) =>
12007 {
12008 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12009 }
12010 (_, _) => language_scope
12011 .line_comment_prefixes()
12012 .iter()
12013 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12014 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12015 }
12016 } else {
12017 // we not in an overridden comment node, but we may
12018 // be within a non-overridden line comment node
12019 language_scope
12020 .line_comment_prefixes()
12021 .iter()
12022 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12023 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12024 };
12025
12026 let rewrap_prefix = language_scope
12027 .rewrap_prefixes()
12028 .iter()
12029 .find_map(|prefix_regex| {
12030 prefix_regex.find(&line_text_after_indent).map(|mat| {
12031 if mat.start() == 0 {
12032 Some(mat.as_str().to_string())
12033 } else {
12034 None
12035 }
12036 })
12037 })
12038 .flatten();
12039 (comment_delimiters, rewrap_prefix)
12040 } else {
12041 (None, None)
12042 };
12043 (indent, comment_prefix, rewrap_prefix)
12044 };
12045
12046 let mut ranges = Vec::new();
12047 let from_empty_selection = selection.is_empty();
12048
12049 let mut current_range_start = first_row;
12050 let mut prev_row = first_row;
12051 let (
12052 mut current_range_indent,
12053 mut current_range_comment_delimiters,
12054 mut current_range_rewrap_prefix,
12055 ) = indent_and_prefix_for_row(first_row);
12056
12057 for row in non_blank_rows_iter.skip(1) {
12058 let has_paragraph_break = row > prev_row + 1;
12059
12060 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12061 indent_and_prefix_for_row(row);
12062
12063 let has_indent_change = row_indent != current_range_indent;
12064 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12065
12066 let has_boundary_change = has_comment_change
12067 || row_rewrap_prefix.is_some()
12068 || (has_indent_change && current_range_comment_delimiters.is_some());
12069
12070 if has_paragraph_break || has_boundary_change {
12071 ranges.push((
12072 language_settings.clone(),
12073 Point::new(current_range_start, 0)
12074 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12075 current_range_indent,
12076 current_range_comment_delimiters.clone(),
12077 current_range_rewrap_prefix.clone(),
12078 from_empty_selection,
12079 ));
12080 current_range_start = row;
12081 current_range_indent = row_indent;
12082 current_range_comment_delimiters = row_comment_delimiters;
12083 current_range_rewrap_prefix = row_rewrap_prefix;
12084 }
12085 prev_row = row;
12086 }
12087
12088 ranges.push((
12089 language_settings.clone(),
12090 Point::new(current_range_start, 0)
12091 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12092 current_range_indent,
12093 current_range_comment_delimiters,
12094 current_range_rewrap_prefix,
12095 from_empty_selection,
12096 ));
12097
12098 ranges
12099 });
12100
12101 let mut edits = Vec::new();
12102 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12103
12104 for (
12105 language_settings,
12106 wrap_range,
12107 mut indent_size,
12108 comment_prefix,
12109 rewrap_prefix,
12110 from_empty_selection,
12111 ) in wrap_ranges
12112 {
12113 let mut start_row = wrap_range.start.row;
12114 let mut end_row = wrap_range.end.row;
12115
12116 // Skip selections that overlap with a range that has already been rewrapped.
12117 let selection_range = start_row..end_row;
12118 if rewrapped_row_ranges
12119 .iter()
12120 .any(|range| range.overlaps(&selection_range))
12121 {
12122 continue;
12123 }
12124
12125 let tab_size = language_settings.tab_size;
12126
12127 let (line_prefix, inside_comment) = match &comment_prefix {
12128 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12129 (Some(prefix.as_str()), true)
12130 }
12131 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12132 (Some(prefix.as_ref()), true)
12133 }
12134 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12135 start: _,
12136 end: _,
12137 prefix,
12138 tab_size,
12139 })) => {
12140 indent_size.len += tab_size;
12141 (Some(prefix.as_ref()), true)
12142 }
12143 None => (None, false),
12144 };
12145 let indent_prefix = indent_size.chars().collect::<String>();
12146 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12147
12148 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12149 RewrapBehavior::InComments => inside_comment,
12150 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12151 RewrapBehavior::Anywhere => true,
12152 };
12153
12154 let should_rewrap = options.override_language_settings
12155 || allow_rewrap_based_on_language
12156 || self.hard_wrap.is_some();
12157 if !should_rewrap {
12158 continue;
12159 }
12160
12161 if from_empty_selection {
12162 'expand_upwards: while start_row > 0 {
12163 let prev_row = start_row - 1;
12164 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12165 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12166 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12167 {
12168 start_row = prev_row;
12169 } else {
12170 break 'expand_upwards;
12171 }
12172 }
12173
12174 'expand_downwards: while end_row < buffer.max_point().row {
12175 let next_row = end_row + 1;
12176 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12177 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12178 && !buffer.is_line_blank(MultiBufferRow(next_row))
12179 {
12180 end_row = next_row;
12181 } else {
12182 break 'expand_downwards;
12183 }
12184 }
12185 }
12186
12187 let start = Point::new(start_row, 0);
12188 let start_offset = ToOffset::to_offset(&start, &buffer);
12189 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12190 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12191 let mut first_line_delimiter = None;
12192 let mut last_line_delimiter = None;
12193 let Some(lines_without_prefixes) = selection_text
12194 .lines()
12195 .enumerate()
12196 .map(|(ix, line)| {
12197 let line_trimmed = line.trim_start();
12198 if rewrap_prefix.is_some() && ix > 0 {
12199 Ok(line_trimmed)
12200 } else if let Some(
12201 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12202 start,
12203 prefix,
12204 end,
12205 tab_size,
12206 })
12207 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12208 start,
12209 prefix,
12210 end,
12211 tab_size,
12212 }),
12213 ) = &comment_prefix
12214 {
12215 let line_trimmed = line_trimmed
12216 .strip_prefix(start.as_ref())
12217 .map(|s| {
12218 let mut indent_size = indent_size;
12219 indent_size.len -= tab_size;
12220 let indent_prefix: String = indent_size.chars().collect();
12221 first_line_delimiter = Some((indent_prefix, start));
12222 s.trim_start()
12223 })
12224 .unwrap_or(line_trimmed);
12225 let line_trimmed = line_trimmed
12226 .strip_suffix(end.as_ref())
12227 .map(|s| {
12228 last_line_delimiter = Some(end);
12229 s.trim_end()
12230 })
12231 .unwrap_or(line_trimmed);
12232 let line_trimmed = line_trimmed
12233 .strip_prefix(prefix.as_ref())
12234 .unwrap_or(line_trimmed);
12235 Ok(line_trimmed)
12236 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12237 line_trimmed.strip_prefix(prefix).with_context(|| {
12238 format!("line did not start with prefix {prefix:?}: {line:?}")
12239 })
12240 } else {
12241 line_trimmed
12242 .strip_prefix(&line_prefix.trim_start())
12243 .with_context(|| {
12244 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12245 })
12246 }
12247 })
12248 .collect::<Result<Vec<_>, _>>()
12249 .log_err()
12250 else {
12251 continue;
12252 };
12253
12254 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12255 buffer
12256 .language_settings_at(Point::new(start_row, 0), cx)
12257 .preferred_line_length as usize
12258 });
12259
12260 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12261 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12262 } else {
12263 line_prefix.clone()
12264 };
12265
12266 let wrapped_text = {
12267 let mut wrapped_text = wrap_with_prefix(
12268 line_prefix,
12269 subsequent_lines_prefix,
12270 lines_without_prefixes.join("\n"),
12271 wrap_column,
12272 tab_size,
12273 options.preserve_existing_whitespace,
12274 );
12275
12276 if let Some((indent, delimiter)) = first_line_delimiter {
12277 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12278 }
12279 if let Some(last_line) = last_line_delimiter {
12280 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12281 }
12282
12283 wrapped_text
12284 };
12285
12286 // TODO: should always use char-based diff while still supporting cursor behavior that
12287 // matches vim.
12288 let mut diff_options = DiffOptions::default();
12289 if options.override_language_settings {
12290 diff_options.max_word_diff_len = 0;
12291 diff_options.max_word_diff_line_count = 0;
12292 } else {
12293 diff_options.max_word_diff_len = usize::MAX;
12294 diff_options.max_word_diff_line_count = usize::MAX;
12295 }
12296
12297 for (old_range, new_text) in
12298 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12299 {
12300 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12301 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12302 edits.push((edit_start..edit_end, new_text));
12303 }
12304
12305 rewrapped_row_ranges.push(start_row..=end_row);
12306 }
12307
12308 self.buffer
12309 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12310 }
12311
12312 pub fn cut_common(
12313 &mut self,
12314 cut_no_selection_line: bool,
12315 window: &mut Window,
12316 cx: &mut Context<Self>,
12317 ) -> ClipboardItem {
12318 let mut text = String::new();
12319 let buffer = self.buffer.read(cx).snapshot(cx);
12320 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12321 let mut clipboard_selections = Vec::with_capacity(selections.len());
12322 {
12323 let max_point = buffer.max_point();
12324 let mut is_first = true;
12325 for selection in &mut selections {
12326 let is_entire_line =
12327 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12328 if is_entire_line {
12329 selection.start = Point::new(selection.start.row, 0);
12330 if !selection.is_empty() && selection.end.column == 0 {
12331 selection.end = cmp::min(max_point, selection.end);
12332 } else {
12333 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12334 }
12335 selection.goal = SelectionGoal::None;
12336 }
12337 if is_first {
12338 is_first = false;
12339 } else {
12340 text += "\n";
12341 }
12342 let mut len = 0;
12343 for chunk in buffer.text_for_range(selection.start..selection.end) {
12344 text.push_str(chunk);
12345 len += chunk.len();
12346 }
12347 clipboard_selections.push(ClipboardSelection {
12348 len,
12349 is_entire_line,
12350 first_line_indent: buffer
12351 .indent_size_for_line(MultiBufferRow(selection.start.row))
12352 .len,
12353 });
12354 }
12355 }
12356
12357 self.transact(window, cx, |this, window, cx| {
12358 this.change_selections(Default::default(), window, cx, |s| {
12359 s.select(selections);
12360 });
12361 this.insert("", window, cx);
12362 });
12363 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12364 }
12365
12366 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12368 let item = self.cut_common(true, window, cx);
12369 cx.write_to_clipboard(item);
12370 }
12371
12372 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12374 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12375 s.move_with(|snapshot, sel| {
12376 if sel.is_empty() {
12377 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12378 }
12379 if sel.is_empty() {
12380 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12381 }
12382 });
12383 });
12384 let item = self.cut_common(false, window, cx);
12385 cx.set_global(KillRing(item))
12386 }
12387
12388 pub fn kill_ring_yank(
12389 &mut self,
12390 _: &KillRingYank,
12391 window: &mut Window,
12392 cx: &mut Context<Self>,
12393 ) {
12394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12395 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12396 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12397 (kill_ring.text().to_string(), kill_ring.metadata_json())
12398 } else {
12399 return;
12400 }
12401 } else {
12402 return;
12403 };
12404 self.do_paste(&text, metadata, false, window, cx);
12405 }
12406
12407 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12408 self.do_copy(true, cx);
12409 }
12410
12411 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12412 self.do_copy(false, cx);
12413 }
12414
12415 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12416 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12417 let buffer = self.buffer.read(cx).read(cx);
12418 let mut text = String::new();
12419
12420 let mut clipboard_selections = Vec::with_capacity(selections.len());
12421 {
12422 let max_point = buffer.max_point();
12423 let mut is_first = true;
12424 for selection in &selections {
12425 let mut start = selection.start;
12426 let mut end = selection.end;
12427 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12428 let mut add_trailing_newline = false;
12429 if is_entire_line {
12430 start = Point::new(start.row, 0);
12431 let next_line_start = Point::new(end.row + 1, 0);
12432 if next_line_start <= max_point {
12433 end = next_line_start;
12434 } else {
12435 // We're on the last line without a trailing newline.
12436 // Copy to the end of the line and add a newline afterwards.
12437 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12438 add_trailing_newline = true;
12439 }
12440 }
12441
12442 let mut trimmed_selections = Vec::new();
12443 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12444 let row = MultiBufferRow(start.row);
12445 let first_indent = buffer.indent_size_for_line(row);
12446 if first_indent.len == 0 || start.column > first_indent.len {
12447 trimmed_selections.push(start..end);
12448 } else {
12449 trimmed_selections.push(
12450 Point::new(row.0, first_indent.len)
12451 ..Point::new(row.0, buffer.line_len(row)),
12452 );
12453 for row in start.row + 1..=end.row {
12454 let mut line_len = buffer.line_len(MultiBufferRow(row));
12455 if row == end.row {
12456 line_len = end.column;
12457 }
12458 if line_len == 0 {
12459 trimmed_selections
12460 .push(Point::new(row, 0)..Point::new(row, line_len));
12461 continue;
12462 }
12463 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12464 if row_indent_size.len >= first_indent.len {
12465 trimmed_selections.push(
12466 Point::new(row, first_indent.len)..Point::new(row, line_len),
12467 );
12468 } else {
12469 trimmed_selections.clear();
12470 trimmed_selections.push(start..end);
12471 break;
12472 }
12473 }
12474 }
12475 } else {
12476 trimmed_selections.push(start..end);
12477 }
12478
12479 for trimmed_range in trimmed_selections {
12480 if is_first {
12481 is_first = false;
12482 } else {
12483 text += "\n";
12484 }
12485 let mut len = 0;
12486 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12487 text.push_str(chunk);
12488 len += chunk.len();
12489 }
12490 if add_trailing_newline {
12491 text.push('\n');
12492 len += 1;
12493 }
12494 clipboard_selections.push(ClipboardSelection {
12495 len,
12496 is_entire_line,
12497 first_line_indent: buffer
12498 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12499 .len,
12500 });
12501 }
12502 }
12503 }
12504
12505 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12506 text,
12507 clipboard_selections,
12508 ));
12509 }
12510
12511 pub fn do_paste(
12512 &mut self,
12513 text: &String,
12514 clipboard_selections: Option<Vec<ClipboardSelection>>,
12515 handle_entire_lines: bool,
12516 window: &mut Window,
12517 cx: &mut Context<Self>,
12518 ) {
12519 if self.read_only(cx) {
12520 return;
12521 }
12522
12523 let clipboard_text = Cow::Borrowed(text.as_str());
12524
12525 self.transact(window, cx, |this, window, cx| {
12526 let had_active_edit_prediction = this.has_active_edit_prediction();
12527 let display_map = this.display_snapshot(cx);
12528 let old_selections = this.selections.all::<usize>(&display_map);
12529 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12530
12531 if let Some(mut clipboard_selections) = clipboard_selections {
12532 let all_selections_were_entire_line =
12533 clipboard_selections.iter().all(|s| s.is_entire_line);
12534 let first_selection_indent_column =
12535 clipboard_selections.first().map(|s| s.first_line_indent);
12536 if clipboard_selections.len() != old_selections.len() {
12537 clipboard_selections.drain(..);
12538 }
12539 let mut auto_indent_on_paste = true;
12540
12541 this.buffer.update(cx, |buffer, cx| {
12542 let snapshot = buffer.read(cx);
12543 auto_indent_on_paste = snapshot
12544 .language_settings_at(cursor_offset, cx)
12545 .auto_indent_on_paste;
12546
12547 let mut start_offset = 0;
12548 let mut edits = Vec::new();
12549 let mut original_indent_columns = Vec::new();
12550 for (ix, selection) in old_selections.iter().enumerate() {
12551 let to_insert;
12552 let entire_line;
12553 let original_indent_column;
12554 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12555 let end_offset = start_offset + clipboard_selection.len;
12556 to_insert = &clipboard_text[start_offset..end_offset];
12557 entire_line = clipboard_selection.is_entire_line;
12558 start_offset = end_offset + 1;
12559 original_indent_column = Some(clipboard_selection.first_line_indent);
12560 } else {
12561 to_insert = &*clipboard_text;
12562 entire_line = all_selections_were_entire_line;
12563 original_indent_column = first_selection_indent_column
12564 }
12565
12566 let (range, to_insert) =
12567 if selection.is_empty() && handle_entire_lines && entire_line {
12568 // If the corresponding selection was empty when this slice of the
12569 // clipboard text was written, then the entire line containing the
12570 // selection was copied. If this selection is also currently empty,
12571 // then paste the line before the current line of the buffer.
12572 let column = selection.start.to_point(&snapshot).column as usize;
12573 let line_start = selection.start - column;
12574 (line_start..line_start, Cow::Borrowed(to_insert))
12575 } else {
12576 let language = snapshot.language_at(selection.head());
12577 let range = selection.range();
12578 if let Some(language) = language
12579 && language.name() == "Markdown".into()
12580 {
12581 edit_for_markdown_paste(
12582 &snapshot,
12583 range,
12584 to_insert,
12585 url::Url::parse(to_insert).ok(),
12586 )
12587 } else {
12588 (range, Cow::Borrowed(to_insert))
12589 }
12590 };
12591
12592 edits.push((range, to_insert));
12593 original_indent_columns.push(original_indent_column);
12594 }
12595 drop(snapshot);
12596
12597 buffer.edit(
12598 edits,
12599 if auto_indent_on_paste {
12600 Some(AutoindentMode::Block {
12601 original_indent_columns,
12602 })
12603 } else {
12604 None
12605 },
12606 cx,
12607 );
12608 });
12609
12610 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12611 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12612 } else {
12613 let url = url::Url::parse(&clipboard_text).ok();
12614
12615 let auto_indent_mode = if !clipboard_text.is_empty() {
12616 Some(AutoindentMode::Block {
12617 original_indent_columns: Vec::new(),
12618 })
12619 } else {
12620 None
12621 };
12622
12623 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12624 let snapshot = buffer.snapshot(cx);
12625
12626 let anchors = old_selections
12627 .iter()
12628 .map(|s| {
12629 let anchor = snapshot.anchor_after(s.head());
12630 s.map(|_| anchor)
12631 })
12632 .collect::<Vec<_>>();
12633
12634 let mut edits = Vec::new();
12635
12636 for selection in old_selections.iter() {
12637 let language = snapshot.language_at(selection.head());
12638 let range = selection.range();
12639
12640 let (edit_range, edit_text) = if let Some(language) = language
12641 && language.name() == "Markdown".into()
12642 {
12643 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12644 } else {
12645 (range, clipboard_text.clone())
12646 };
12647
12648 edits.push((edit_range, edit_text));
12649 }
12650
12651 drop(snapshot);
12652 buffer.edit(edits, auto_indent_mode, cx);
12653
12654 anchors
12655 });
12656
12657 this.change_selections(Default::default(), window, cx, |s| {
12658 s.select_anchors(selection_anchors);
12659 });
12660 }
12661
12662 let trigger_in_words =
12663 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12664
12665 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12666 });
12667 }
12668
12669 pub fn diff_clipboard_with_selection(
12670 &mut self,
12671 _: &DiffClipboardWithSelection,
12672 window: &mut Window,
12673 cx: &mut Context<Self>,
12674 ) {
12675 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12676
12677 if selections.is_empty() {
12678 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12679 return;
12680 };
12681
12682 let clipboard_text = match cx.read_from_clipboard() {
12683 Some(item) => match item.entries().first() {
12684 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12685 _ => None,
12686 },
12687 None => None,
12688 };
12689
12690 let Some(clipboard_text) = clipboard_text else {
12691 log::warn!("Clipboard doesn't contain text.");
12692 return;
12693 };
12694
12695 window.dispatch_action(
12696 Box::new(DiffClipboardWithSelectionData {
12697 clipboard_text,
12698 editor: cx.entity(),
12699 }),
12700 cx,
12701 );
12702 }
12703
12704 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12705 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12706 if let Some(item) = cx.read_from_clipboard() {
12707 let entries = item.entries();
12708
12709 match entries.first() {
12710 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12711 // of all the pasted entries.
12712 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12713 .do_paste(
12714 clipboard_string.text(),
12715 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12716 true,
12717 window,
12718 cx,
12719 ),
12720 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12721 }
12722 }
12723 }
12724
12725 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12726 if self.read_only(cx) {
12727 return;
12728 }
12729
12730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12731
12732 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12733 if let Some((selections, _)) =
12734 self.selection_history.transaction(transaction_id).cloned()
12735 {
12736 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12737 s.select_anchors(selections.to_vec());
12738 });
12739 } else {
12740 log::error!(
12741 "No entry in selection_history found for undo. \
12742 This may correspond to a bug where undo does not update the selection. \
12743 If this is occurring, please add details to \
12744 https://github.com/zed-industries/zed/issues/22692"
12745 );
12746 }
12747 self.request_autoscroll(Autoscroll::fit(), cx);
12748 self.unmark_text(window, cx);
12749 self.refresh_edit_prediction(true, false, window, cx);
12750 cx.emit(EditorEvent::Edited { transaction_id });
12751 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12752 }
12753 }
12754
12755 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12756 if self.read_only(cx) {
12757 return;
12758 }
12759
12760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12761
12762 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12763 if let Some((_, Some(selections))) =
12764 self.selection_history.transaction(transaction_id).cloned()
12765 {
12766 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12767 s.select_anchors(selections.to_vec());
12768 });
12769 } else {
12770 log::error!(
12771 "No entry in selection_history found for redo. \
12772 This may correspond to a bug where undo does not update the selection. \
12773 If this is occurring, please add details to \
12774 https://github.com/zed-industries/zed/issues/22692"
12775 );
12776 }
12777 self.request_autoscroll(Autoscroll::fit(), cx);
12778 self.unmark_text(window, cx);
12779 self.refresh_edit_prediction(true, false, window, cx);
12780 cx.emit(EditorEvent::Edited { transaction_id });
12781 }
12782 }
12783
12784 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12785 self.buffer
12786 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12787 }
12788
12789 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12790 self.buffer
12791 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12792 }
12793
12794 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12796 self.change_selections(Default::default(), window, cx, |s| {
12797 s.move_with(|map, selection| {
12798 let cursor = if selection.is_empty() {
12799 movement::left(map, selection.start)
12800 } else {
12801 selection.start
12802 };
12803 selection.collapse_to(cursor, SelectionGoal::None);
12804 });
12805 })
12806 }
12807
12808 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12810 self.change_selections(Default::default(), window, cx, |s| {
12811 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12812 })
12813 }
12814
12815 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12816 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12817 self.change_selections(Default::default(), window, cx, |s| {
12818 s.move_with(|map, selection| {
12819 let cursor = if selection.is_empty() {
12820 movement::right(map, selection.end)
12821 } else {
12822 selection.end
12823 };
12824 selection.collapse_to(cursor, SelectionGoal::None)
12825 });
12826 })
12827 }
12828
12829 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12831 self.change_selections(Default::default(), window, cx, |s| {
12832 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12833 });
12834 }
12835
12836 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12837 if self.take_rename(true, window, cx).is_some() {
12838 return;
12839 }
12840
12841 if self.mode.is_single_line() {
12842 cx.propagate();
12843 return;
12844 }
12845
12846 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12847
12848 let text_layout_details = &self.text_layout_details(window);
12849 let selection_count = self.selections.count();
12850 let first_selection = self.selections.first_anchor();
12851
12852 self.change_selections(Default::default(), window, cx, |s| {
12853 s.move_with(|map, selection| {
12854 if !selection.is_empty() {
12855 selection.goal = SelectionGoal::None;
12856 }
12857 let (cursor, goal) = movement::up(
12858 map,
12859 selection.start,
12860 selection.goal,
12861 false,
12862 text_layout_details,
12863 );
12864 selection.collapse_to(cursor, goal);
12865 });
12866 });
12867
12868 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12869 {
12870 cx.propagate();
12871 }
12872 }
12873
12874 pub fn move_up_by_lines(
12875 &mut self,
12876 action: &MoveUpByLines,
12877 window: &mut Window,
12878 cx: &mut Context<Self>,
12879 ) {
12880 if self.take_rename(true, window, cx).is_some() {
12881 return;
12882 }
12883
12884 if self.mode.is_single_line() {
12885 cx.propagate();
12886 return;
12887 }
12888
12889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12890
12891 let text_layout_details = &self.text_layout_details(window);
12892
12893 self.change_selections(Default::default(), window, cx, |s| {
12894 s.move_with(|map, selection| {
12895 if !selection.is_empty() {
12896 selection.goal = SelectionGoal::None;
12897 }
12898 let (cursor, goal) = movement::up_by_rows(
12899 map,
12900 selection.start,
12901 action.lines,
12902 selection.goal,
12903 false,
12904 text_layout_details,
12905 );
12906 selection.collapse_to(cursor, goal);
12907 });
12908 })
12909 }
12910
12911 pub fn move_down_by_lines(
12912 &mut self,
12913 action: &MoveDownByLines,
12914 window: &mut Window,
12915 cx: &mut Context<Self>,
12916 ) {
12917 if self.take_rename(true, window, cx).is_some() {
12918 return;
12919 }
12920
12921 if self.mode.is_single_line() {
12922 cx.propagate();
12923 return;
12924 }
12925
12926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12927
12928 let text_layout_details = &self.text_layout_details(window);
12929
12930 self.change_selections(Default::default(), window, cx, |s| {
12931 s.move_with(|map, selection| {
12932 if !selection.is_empty() {
12933 selection.goal = SelectionGoal::None;
12934 }
12935 let (cursor, goal) = movement::down_by_rows(
12936 map,
12937 selection.start,
12938 action.lines,
12939 selection.goal,
12940 false,
12941 text_layout_details,
12942 );
12943 selection.collapse_to(cursor, goal);
12944 });
12945 })
12946 }
12947
12948 pub fn select_down_by_lines(
12949 &mut self,
12950 action: &SelectDownByLines,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12955 let text_layout_details = &self.text_layout_details(window);
12956 self.change_selections(Default::default(), window, cx, |s| {
12957 s.move_heads_with(|map, head, goal| {
12958 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12959 })
12960 })
12961 }
12962
12963 pub fn select_up_by_lines(
12964 &mut self,
12965 action: &SelectUpByLines,
12966 window: &mut Window,
12967 cx: &mut Context<Self>,
12968 ) {
12969 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12970 let text_layout_details = &self.text_layout_details(window);
12971 self.change_selections(Default::default(), window, cx, |s| {
12972 s.move_heads_with(|map, head, goal| {
12973 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12974 })
12975 })
12976 }
12977
12978 pub fn select_page_up(
12979 &mut self,
12980 _: &SelectPageUp,
12981 window: &mut Window,
12982 cx: &mut Context<Self>,
12983 ) {
12984 let Some(row_count) = self.visible_row_count() else {
12985 return;
12986 };
12987
12988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12989
12990 let text_layout_details = &self.text_layout_details(window);
12991
12992 self.change_selections(Default::default(), window, cx, |s| {
12993 s.move_heads_with(|map, head, goal| {
12994 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12995 })
12996 })
12997 }
12998
12999 pub fn move_page_up(
13000 &mut self,
13001 action: &MovePageUp,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 if self.take_rename(true, window, cx).is_some() {
13006 return;
13007 }
13008
13009 if self
13010 .context_menu
13011 .borrow_mut()
13012 .as_mut()
13013 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13014 .unwrap_or(false)
13015 {
13016 return;
13017 }
13018
13019 if matches!(self.mode, EditorMode::SingleLine) {
13020 cx.propagate();
13021 return;
13022 }
13023
13024 let Some(row_count) = self.visible_row_count() else {
13025 return;
13026 };
13027
13028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13029
13030 let effects = if action.center_cursor {
13031 SelectionEffects::scroll(Autoscroll::center())
13032 } else {
13033 SelectionEffects::default()
13034 };
13035
13036 let text_layout_details = &self.text_layout_details(window);
13037
13038 self.change_selections(effects, window, cx, |s| {
13039 s.move_with(|map, selection| {
13040 if !selection.is_empty() {
13041 selection.goal = SelectionGoal::None;
13042 }
13043 let (cursor, goal) = movement::up_by_rows(
13044 map,
13045 selection.end,
13046 row_count,
13047 selection.goal,
13048 false,
13049 text_layout_details,
13050 );
13051 selection.collapse_to(cursor, goal);
13052 });
13053 });
13054 }
13055
13056 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13058 let text_layout_details = &self.text_layout_details(window);
13059 self.change_selections(Default::default(), window, cx, |s| {
13060 s.move_heads_with(|map, head, goal| {
13061 movement::up(map, head, goal, false, text_layout_details)
13062 })
13063 })
13064 }
13065
13066 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13067 self.take_rename(true, window, cx);
13068
13069 if self.mode.is_single_line() {
13070 cx.propagate();
13071 return;
13072 }
13073
13074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13075
13076 let text_layout_details = &self.text_layout_details(window);
13077 let selection_count = self.selections.count();
13078 let first_selection = self.selections.first_anchor();
13079
13080 self.change_selections(Default::default(), window, cx, |s| {
13081 s.move_with(|map, selection| {
13082 if !selection.is_empty() {
13083 selection.goal = SelectionGoal::None;
13084 }
13085 let (cursor, goal) = movement::down(
13086 map,
13087 selection.end,
13088 selection.goal,
13089 false,
13090 text_layout_details,
13091 );
13092 selection.collapse_to(cursor, goal);
13093 });
13094 });
13095
13096 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13097 {
13098 cx.propagate();
13099 }
13100 }
13101
13102 pub fn select_page_down(
13103 &mut self,
13104 _: &SelectPageDown,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 let Some(row_count) = self.visible_row_count() else {
13109 return;
13110 };
13111
13112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13113
13114 let text_layout_details = &self.text_layout_details(window);
13115
13116 self.change_selections(Default::default(), window, cx, |s| {
13117 s.move_heads_with(|map, head, goal| {
13118 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13119 })
13120 })
13121 }
13122
13123 pub fn move_page_down(
13124 &mut self,
13125 action: &MovePageDown,
13126 window: &mut Window,
13127 cx: &mut Context<Self>,
13128 ) {
13129 if self.take_rename(true, window, cx).is_some() {
13130 return;
13131 }
13132
13133 if self
13134 .context_menu
13135 .borrow_mut()
13136 .as_mut()
13137 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13138 .unwrap_or(false)
13139 {
13140 return;
13141 }
13142
13143 if matches!(self.mode, EditorMode::SingleLine) {
13144 cx.propagate();
13145 return;
13146 }
13147
13148 let Some(row_count) = self.visible_row_count() else {
13149 return;
13150 };
13151
13152 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13153
13154 let effects = if action.center_cursor {
13155 SelectionEffects::scroll(Autoscroll::center())
13156 } else {
13157 SelectionEffects::default()
13158 };
13159
13160 let text_layout_details = &self.text_layout_details(window);
13161 self.change_selections(effects, window, cx, |s| {
13162 s.move_with(|map, selection| {
13163 if !selection.is_empty() {
13164 selection.goal = SelectionGoal::None;
13165 }
13166 let (cursor, goal) = movement::down_by_rows(
13167 map,
13168 selection.end,
13169 row_count,
13170 selection.goal,
13171 false,
13172 text_layout_details,
13173 );
13174 selection.collapse_to(cursor, goal);
13175 });
13176 });
13177 }
13178
13179 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13181 let text_layout_details = &self.text_layout_details(window);
13182 self.change_selections(Default::default(), window, cx, |s| {
13183 s.move_heads_with(|map, head, goal| {
13184 movement::down(map, head, goal, false, text_layout_details)
13185 })
13186 });
13187 }
13188
13189 pub fn context_menu_first(
13190 &mut self,
13191 _: &ContextMenuFirst,
13192 window: &mut Window,
13193 cx: &mut Context<Self>,
13194 ) {
13195 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13196 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13197 }
13198 }
13199
13200 pub fn context_menu_prev(
13201 &mut self,
13202 _: &ContextMenuPrevious,
13203 window: &mut Window,
13204 cx: &mut Context<Self>,
13205 ) {
13206 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13207 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13208 }
13209 }
13210
13211 pub fn context_menu_next(
13212 &mut self,
13213 _: &ContextMenuNext,
13214 window: &mut Window,
13215 cx: &mut Context<Self>,
13216 ) {
13217 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13218 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13219 }
13220 }
13221
13222 pub fn context_menu_last(
13223 &mut self,
13224 _: &ContextMenuLast,
13225 window: &mut Window,
13226 cx: &mut Context<Self>,
13227 ) {
13228 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13229 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13230 }
13231 }
13232
13233 pub fn signature_help_prev(
13234 &mut self,
13235 _: &SignatureHelpPrevious,
13236 _: &mut Window,
13237 cx: &mut Context<Self>,
13238 ) {
13239 if let Some(popover) = self.signature_help_state.popover_mut() {
13240 if popover.current_signature == 0 {
13241 popover.current_signature = popover.signatures.len() - 1;
13242 } else {
13243 popover.current_signature -= 1;
13244 }
13245 cx.notify();
13246 }
13247 }
13248
13249 pub fn signature_help_next(
13250 &mut self,
13251 _: &SignatureHelpNext,
13252 _: &mut Window,
13253 cx: &mut Context<Self>,
13254 ) {
13255 if let Some(popover) = self.signature_help_state.popover_mut() {
13256 if popover.current_signature + 1 == popover.signatures.len() {
13257 popover.current_signature = 0;
13258 } else {
13259 popover.current_signature += 1;
13260 }
13261 cx.notify();
13262 }
13263 }
13264
13265 pub fn move_to_previous_word_start(
13266 &mut self,
13267 _: &MoveToPreviousWordStart,
13268 window: &mut Window,
13269 cx: &mut Context<Self>,
13270 ) {
13271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13272 self.change_selections(Default::default(), window, cx, |s| {
13273 s.move_cursors_with(|map, head, _| {
13274 (
13275 movement::previous_word_start(map, head),
13276 SelectionGoal::None,
13277 )
13278 });
13279 })
13280 }
13281
13282 pub fn move_to_previous_subword_start(
13283 &mut self,
13284 _: &MoveToPreviousSubwordStart,
13285 window: &mut Window,
13286 cx: &mut Context<Self>,
13287 ) {
13288 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13289 self.change_selections(Default::default(), window, cx, |s| {
13290 s.move_cursors_with(|map, head, _| {
13291 (
13292 movement::previous_subword_start(map, head),
13293 SelectionGoal::None,
13294 )
13295 });
13296 })
13297 }
13298
13299 pub fn select_to_previous_word_start(
13300 &mut self,
13301 _: &SelectToPreviousWordStart,
13302 window: &mut Window,
13303 cx: &mut Context<Self>,
13304 ) {
13305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13306 self.change_selections(Default::default(), window, cx, |s| {
13307 s.move_heads_with(|map, head, _| {
13308 (
13309 movement::previous_word_start(map, head),
13310 SelectionGoal::None,
13311 )
13312 });
13313 })
13314 }
13315
13316 pub fn select_to_previous_subword_start(
13317 &mut self,
13318 _: &SelectToPreviousSubwordStart,
13319 window: &mut Window,
13320 cx: &mut Context<Self>,
13321 ) {
13322 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13323 self.change_selections(Default::default(), window, cx, |s| {
13324 s.move_heads_with(|map, head, _| {
13325 (
13326 movement::previous_subword_start(map, head),
13327 SelectionGoal::None,
13328 )
13329 });
13330 })
13331 }
13332
13333 pub fn delete_to_previous_word_start(
13334 &mut self,
13335 action: &DeleteToPreviousWordStart,
13336 window: &mut Window,
13337 cx: &mut Context<Self>,
13338 ) {
13339 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13340 self.transact(window, cx, |this, window, cx| {
13341 this.select_autoclose_pair(window, cx);
13342 this.change_selections(Default::default(), window, cx, |s| {
13343 s.move_with(|map, selection| {
13344 if selection.is_empty() {
13345 let mut cursor = if action.ignore_newlines {
13346 movement::previous_word_start(map, selection.head())
13347 } else {
13348 movement::previous_word_start_or_newline(map, selection.head())
13349 };
13350 cursor = movement::adjust_greedy_deletion(
13351 map,
13352 selection.head(),
13353 cursor,
13354 action.ignore_brackets,
13355 );
13356 selection.set_head(cursor, SelectionGoal::None);
13357 }
13358 });
13359 });
13360 this.insert("", window, cx);
13361 });
13362 }
13363
13364 pub fn delete_to_previous_subword_start(
13365 &mut self,
13366 _: &DeleteToPreviousSubwordStart,
13367 window: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) {
13370 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13371 self.transact(window, cx, |this, window, cx| {
13372 this.select_autoclose_pair(window, cx);
13373 this.change_selections(Default::default(), window, cx, |s| {
13374 s.move_with(|map, selection| {
13375 if selection.is_empty() {
13376 let mut cursor = movement::previous_subword_start(map, selection.head());
13377 cursor =
13378 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13379 selection.set_head(cursor, SelectionGoal::None);
13380 }
13381 });
13382 });
13383 this.insert("", window, cx);
13384 });
13385 }
13386
13387 pub fn move_to_next_word_end(
13388 &mut self,
13389 _: &MoveToNextWordEnd,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13394 self.change_selections(Default::default(), window, cx, |s| {
13395 s.move_cursors_with(|map, head, _| {
13396 (movement::next_word_end(map, head), SelectionGoal::None)
13397 });
13398 })
13399 }
13400
13401 pub fn move_to_next_subword_end(
13402 &mut self,
13403 _: &MoveToNextSubwordEnd,
13404 window: &mut Window,
13405 cx: &mut Context<Self>,
13406 ) {
13407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13408 self.change_selections(Default::default(), window, cx, |s| {
13409 s.move_cursors_with(|map, head, _| {
13410 (movement::next_subword_end(map, head), SelectionGoal::None)
13411 });
13412 })
13413 }
13414
13415 pub fn select_to_next_word_end(
13416 &mut self,
13417 _: &SelectToNextWordEnd,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13422 self.change_selections(Default::default(), window, cx, |s| {
13423 s.move_heads_with(|map, head, _| {
13424 (movement::next_word_end(map, head), SelectionGoal::None)
13425 });
13426 })
13427 }
13428
13429 pub fn select_to_next_subword_end(
13430 &mut self,
13431 _: &SelectToNextSubwordEnd,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.move_heads_with(|map, head, _| {
13438 (movement::next_subword_end(map, head), SelectionGoal::None)
13439 });
13440 })
13441 }
13442
13443 pub fn delete_to_next_word_end(
13444 &mut self,
13445 action: &DeleteToNextWordEnd,
13446 window: &mut Window,
13447 cx: &mut Context<Self>,
13448 ) {
13449 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13450 self.transact(window, cx, |this, window, cx| {
13451 this.change_selections(Default::default(), window, cx, |s| {
13452 s.move_with(|map, selection| {
13453 if selection.is_empty() {
13454 let mut cursor = if action.ignore_newlines {
13455 movement::next_word_end(map, selection.head())
13456 } else {
13457 movement::next_word_end_or_newline(map, selection.head())
13458 };
13459 cursor = movement::adjust_greedy_deletion(
13460 map,
13461 selection.head(),
13462 cursor,
13463 action.ignore_brackets,
13464 );
13465 selection.set_head(cursor, SelectionGoal::None);
13466 }
13467 });
13468 });
13469 this.insert("", window, cx);
13470 });
13471 }
13472
13473 pub fn delete_to_next_subword_end(
13474 &mut self,
13475 _: &DeleteToNextSubwordEnd,
13476 window: &mut Window,
13477 cx: &mut Context<Self>,
13478 ) {
13479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13480 self.transact(window, cx, |this, window, cx| {
13481 this.change_selections(Default::default(), window, cx, |s| {
13482 s.move_with(|map, selection| {
13483 if selection.is_empty() {
13484 let mut cursor = movement::next_subword_end(map, selection.head());
13485 cursor =
13486 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13487 selection.set_head(cursor, SelectionGoal::None);
13488 }
13489 });
13490 });
13491 this.insert("", window, cx);
13492 });
13493 }
13494
13495 pub fn move_to_beginning_of_line(
13496 &mut self,
13497 action: &MoveToBeginningOfLine,
13498 window: &mut Window,
13499 cx: &mut Context<Self>,
13500 ) {
13501 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13502 self.change_selections(Default::default(), window, cx, |s| {
13503 s.move_cursors_with(|map, head, _| {
13504 (
13505 movement::indented_line_beginning(
13506 map,
13507 head,
13508 action.stop_at_soft_wraps,
13509 action.stop_at_indent,
13510 ),
13511 SelectionGoal::None,
13512 )
13513 });
13514 })
13515 }
13516
13517 pub fn select_to_beginning_of_line(
13518 &mut self,
13519 action: &SelectToBeginningOfLine,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13524 self.change_selections(Default::default(), window, cx, |s| {
13525 s.move_heads_with(|map, head, _| {
13526 (
13527 movement::indented_line_beginning(
13528 map,
13529 head,
13530 action.stop_at_soft_wraps,
13531 action.stop_at_indent,
13532 ),
13533 SelectionGoal::None,
13534 )
13535 });
13536 });
13537 }
13538
13539 pub fn delete_to_beginning_of_line(
13540 &mut self,
13541 action: &DeleteToBeginningOfLine,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) {
13545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13546 self.transact(window, cx, |this, window, cx| {
13547 this.change_selections(Default::default(), window, cx, |s| {
13548 s.move_with(|_, selection| {
13549 selection.reversed = true;
13550 });
13551 });
13552
13553 this.select_to_beginning_of_line(
13554 &SelectToBeginningOfLine {
13555 stop_at_soft_wraps: false,
13556 stop_at_indent: action.stop_at_indent,
13557 },
13558 window,
13559 cx,
13560 );
13561 this.backspace(&Backspace, window, cx);
13562 });
13563 }
13564
13565 pub fn move_to_end_of_line(
13566 &mut self,
13567 action: &MoveToEndOfLine,
13568 window: &mut Window,
13569 cx: &mut Context<Self>,
13570 ) {
13571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13572 self.change_selections(Default::default(), window, cx, |s| {
13573 s.move_cursors_with(|map, head, _| {
13574 (
13575 movement::line_end(map, head, action.stop_at_soft_wraps),
13576 SelectionGoal::None,
13577 )
13578 });
13579 })
13580 }
13581
13582 pub fn select_to_end_of_line(
13583 &mut self,
13584 action: &SelectToEndOfLine,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) {
13588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13589 self.change_selections(Default::default(), window, cx, |s| {
13590 s.move_heads_with(|map, head, _| {
13591 (
13592 movement::line_end(map, head, action.stop_at_soft_wraps),
13593 SelectionGoal::None,
13594 )
13595 });
13596 })
13597 }
13598
13599 pub fn delete_to_end_of_line(
13600 &mut self,
13601 _: &DeleteToEndOfLine,
13602 window: &mut Window,
13603 cx: &mut Context<Self>,
13604 ) {
13605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13606 self.transact(window, cx, |this, window, cx| {
13607 this.select_to_end_of_line(
13608 &SelectToEndOfLine {
13609 stop_at_soft_wraps: false,
13610 },
13611 window,
13612 cx,
13613 );
13614 this.delete(&Delete, window, cx);
13615 });
13616 }
13617
13618 pub fn cut_to_end_of_line(
13619 &mut self,
13620 action: &CutToEndOfLine,
13621 window: &mut Window,
13622 cx: &mut Context<Self>,
13623 ) {
13624 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13625 self.transact(window, cx, |this, window, cx| {
13626 this.select_to_end_of_line(
13627 &SelectToEndOfLine {
13628 stop_at_soft_wraps: false,
13629 },
13630 window,
13631 cx,
13632 );
13633 if !action.stop_at_newlines {
13634 this.change_selections(Default::default(), window, cx, |s| {
13635 s.move_with(|_, sel| {
13636 if sel.is_empty() {
13637 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13638 }
13639 });
13640 });
13641 }
13642 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13643 let item = this.cut_common(false, window, cx);
13644 cx.write_to_clipboard(item);
13645 });
13646 }
13647
13648 pub fn move_to_start_of_paragraph(
13649 &mut self,
13650 _: &MoveToStartOfParagraph,
13651 window: &mut Window,
13652 cx: &mut Context<Self>,
13653 ) {
13654 if matches!(self.mode, EditorMode::SingleLine) {
13655 cx.propagate();
13656 return;
13657 }
13658 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13659 self.change_selections(Default::default(), window, cx, |s| {
13660 s.move_with(|map, selection| {
13661 selection.collapse_to(
13662 movement::start_of_paragraph(map, selection.head(), 1),
13663 SelectionGoal::None,
13664 )
13665 });
13666 })
13667 }
13668
13669 pub fn move_to_end_of_paragraph(
13670 &mut self,
13671 _: &MoveToEndOfParagraph,
13672 window: &mut Window,
13673 cx: &mut Context<Self>,
13674 ) {
13675 if matches!(self.mode, EditorMode::SingleLine) {
13676 cx.propagate();
13677 return;
13678 }
13679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13680 self.change_selections(Default::default(), window, cx, |s| {
13681 s.move_with(|map, selection| {
13682 selection.collapse_to(
13683 movement::end_of_paragraph(map, selection.head(), 1),
13684 SelectionGoal::None,
13685 )
13686 });
13687 })
13688 }
13689
13690 pub fn select_to_start_of_paragraph(
13691 &mut self,
13692 _: &SelectToStartOfParagraph,
13693 window: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) {
13696 if matches!(self.mode, EditorMode::SingleLine) {
13697 cx.propagate();
13698 return;
13699 }
13700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13701 self.change_selections(Default::default(), window, cx, |s| {
13702 s.move_heads_with(|map, head, _| {
13703 (
13704 movement::start_of_paragraph(map, head, 1),
13705 SelectionGoal::None,
13706 )
13707 });
13708 })
13709 }
13710
13711 pub fn select_to_end_of_paragraph(
13712 &mut self,
13713 _: &SelectToEndOfParagraph,
13714 window: &mut Window,
13715 cx: &mut Context<Self>,
13716 ) {
13717 if matches!(self.mode, EditorMode::SingleLine) {
13718 cx.propagate();
13719 return;
13720 }
13721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13722 self.change_selections(Default::default(), window, cx, |s| {
13723 s.move_heads_with(|map, head, _| {
13724 (
13725 movement::end_of_paragraph(map, head, 1),
13726 SelectionGoal::None,
13727 )
13728 });
13729 })
13730 }
13731
13732 pub fn move_to_start_of_excerpt(
13733 &mut self,
13734 _: &MoveToStartOfExcerpt,
13735 window: &mut Window,
13736 cx: &mut Context<Self>,
13737 ) {
13738 if matches!(self.mode, EditorMode::SingleLine) {
13739 cx.propagate();
13740 return;
13741 }
13742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13743 self.change_selections(Default::default(), window, cx, |s| {
13744 s.move_with(|map, selection| {
13745 selection.collapse_to(
13746 movement::start_of_excerpt(
13747 map,
13748 selection.head(),
13749 workspace::searchable::Direction::Prev,
13750 ),
13751 SelectionGoal::None,
13752 )
13753 });
13754 })
13755 }
13756
13757 pub fn move_to_start_of_next_excerpt(
13758 &mut self,
13759 _: &MoveToStartOfNextExcerpt,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) {
13763 if matches!(self.mode, EditorMode::SingleLine) {
13764 cx.propagate();
13765 return;
13766 }
13767
13768 self.change_selections(Default::default(), window, cx, |s| {
13769 s.move_with(|map, selection| {
13770 selection.collapse_to(
13771 movement::start_of_excerpt(
13772 map,
13773 selection.head(),
13774 workspace::searchable::Direction::Next,
13775 ),
13776 SelectionGoal::None,
13777 )
13778 });
13779 })
13780 }
13781
13782 pub fn move_to_end_of_excerpt(
13783 &mut self,
13784 _: &MoveToEndOfExcerpt,
13785 window: &mut Window,
13786 cx: &mut Context<Self>,
13787 ) {
13788 if matches!(self.mode, EditorMode::SingleLine) {
13789 cx.propagate();
13790 return;
13791 }
13792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13793 self.change_selections(Default::default(), window, cx, |s| {
13794 s.move_with(|map, selection| {
13795 selection.collapse_to(
13796 movement::end_of_excerpt(
13797 map,
13798 selection.head(),
13799 workspace::searchable::Direction::Next,
13800 ),
13801 SelectionGoal::None,
13802 )
13803 });
13804 })
13805 }
13806
13807 pub fn move_to_end_of_previous_excerpt(
13808 &mut self,
13809 _: &MoveToEndOfPreviousExcerpt,
13810 window: &mut Window,
13811 cx: &mut Context<Self>,
13812 ) {
13813 if matches!(self.mode, EditorMode::SingleLine) {
13814 cx.propagate();
13815 return;
13816 }
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13818 self.change_selections(Default::default(), window, cx, |s| {
13819 s.move_with(|map, selection| {
13820 selection.collapse_to(
13821 movement::end_of_excerpt(
13822 map,
13823 selection.head(),
13824 workspace::searchable::Direction::Prev,
13825 ),
13826 SelectionGoal::None,
13827 )
13828 });
13829 })
13830 }
13831
13832 pub fn select_to_start_of_excerpt(
13833 &mut self,
13834 _: &SelectToStartOfExcerpt,
13835 window: &mut Window,
13836 cx: &mut Context<Self>,
13837 ) {
13838 if matches!(self.mode, EditorMode::SingleLine) {
13839 cx.propagate();
13840 return;
13841 }
13842 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13843 self.change_selections(Default::default(), window, cx, |s| {
13844 s.move_heads_with(|map, head, _| {
13845 (
13846 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13847 SelectionGoal::None,
13848 )
13849 });
13850 })
13851 }
13852
13853 pub fn select_to_start_of_next_excerpt(
13854 &mut self,
13855 _: &SelectToStartOfNextExcerpt,
13856 window: &mut Window,
13857 cx: &mut Context<Self>,
13858 ) {
13859 if matches!(self.mode, EditorMode::SingleLine) {
13860 cx.propagate();
13861 return;
13862 }
13863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13864 self.change_selections(Default::default(), window, cx, |s| {
13865 s.move_heads_with(|map, head, _| {
13866 (
13867 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13868 SelectionGoal::None,
13869 )
13870 });
13871 })
13872 }
13873
13874 pub fn select_to_end_of_excerpt(
13875 &mut self,
13876 _: &SelectToEndOfExcerpt,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 if matches!(self.mode, EditorMode::SingleLine) {
13881 cx.propagate();
13882 return;
13883 }
13884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13885 self.change_selections(Default::default(), window, cx, |s| {
13886 s.move_heads_with(|map, head, _| {
13887 (
13888 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13889 SelectionGoal::None,
13890 )
13891 });
13892 })
13893 }
13894
13895 pub fn select_to_end_of_previous_excerpt(
13896 &mut self,
13897 _: &SelectToEndOfPreviousExcerpt,
13898 window: &mut Window,
13899 cx: &mut Context<Self>,
13900 ) {
13901 if matches!(self.mode, EditorMode::SingleLine) {
13902 cx.propagate();
13903 return;
13904 }
13905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13906 self.change_selections(Default::default(), window, cx, |s| {
13907 s.move_heads_with(|map, head, _| {
13908 (
13909 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13910 SelectionGoal::None,
13911 )
13912 });
13913 })
13914 }
13915
13916 pub fn move_to_beginning(
13917 &mut self,
13918 _: &MoveToBeginning,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) {
13922 if matches!(self.mode, EditorMode::SingleLine) {
13923 cx.propagate();
13924 return;
13925 }
13926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13927 self.change_selections(Default::default(), window, cx, |s| {
13928 s.select_ranges(vec![0..0]);
13929 });
13930 }
13931
13932 pub fn select_to_beginning(
13933 &mut self,
13934 _: &SelectToBeginning,
13935 window: &mut Window,
13936 cx: &mut Context<Self>,
13937 ) {
13938 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13939 selection.set_head(Point::zero(), SelectionGoal::None);
13940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13941 self.change_selections(Default::default(), window, cx, |s| {
13942 s.select(vec![selection]);
13943 });
13944 }
13945
13946 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13947 if matches!(self.mode, EditorMode::SingleLine) {
13948 cx.propagate();
13949 return;
13950 }
13951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13952 let cursor = self.buffer.read(cx).read(cx).len();
13953 self.change_selections(Default::default(), window, cx, |s| {
13954 s.select_ranges(vec![cursor..cursor])
13955 });
13956 }
13957
13958 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13959 self.nav_history = nav_history;
13960 }
13961
13962 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13963 self.nav_history.as_ref()
13964 }
13965
13966 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13967 self.push_to_nav_history(
13968 self.selections.newest_anchor().head(),
13969 None,
13970 false,
13971 true,
13972 cx,
13973 );
13974 }
13975
13976 fn push_to_nav_history(
13977 &mut self,
13978 cursor_anchor: Anchor,
13979 new_position: Option<Point>,
13980 is_deactivate: bool,
13981 always: bool,
13982 cx: &mut Context<Self>,
13983 ) {
13984 if let Some(nav_history) = self.nav_history.as_mut() {
13985 let buffer = self.buffer.read(cx).read(cx);
13986 let cursor_position = cursor_anchor.to_point(&buffer);
13987 let scroll_state = self.scroll_manager.anchor();
13988 let scroll_top_row = scroll_state.top_row(&buffer);
13989 drop(buffer);
13990
13991 if let Some(new_position) = new_position {
13992 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13993 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13994 return;
13995 }
13996 }
13997
13998 nav_history.push(
13999 Some(NavigationData {
14000 cursor_anchor,
14001 cursor_position,
14002 scroll_anchor: scroll_state,
14003 scroll_top_row,
14004 }),
14005 cx,
14006 );
14007 cx.emit(EditorEvent::PushedToNavHistory {
14008 anchor: cursor_anchor,
14009 is_deactivate,
14010 })
14011 }
14012 }
14013
14014 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14016 let buffer = self.buffer.read(cx).snapshot(cx);
14017 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14018 selection.set_head(buffer.len(), SelectionGoal::None);
14019 self.change_selections(Default::default(), window, cx, |s| {
14020 s.select(vec![selection]);
14021 });
14022 }
14023
14024 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14026 let end = self.buffer.read(cx).read(cx).len();
14027 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14028 s.select_ranges(vec![0..end]);
14029 });
14030 }
14031
14032 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14033 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14034 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14035 let mut selections = self.selections.all::<Point>(&display_map);
14036 let max_point = display_map.buffer_snapshot().max_point();
14037 for selection in &mut selections {
14038 let rows = selection.spanned_rows(true, &display_map);
14039 selection.start = Point::new(rows.start.0, 0);
14040 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14041 selection.reversed = false;
14042 }
14043 self.change_selections(Default::default(), window, cx, |s| {
14044 s.select(selections);
14045 });
14046 }
14047
14048 pub fn split_selection_into_lines(
14049 &mut self,
14050 action: &SplitSelectionIntoLines,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) {
14054 let selections = self
14055 .selections
14056 .all::<Point>(&self.display_snapshot(cx))
14057 .into_iter()
14058 .map(|selection| selection.start..selection.end)
14059 .collect::<Vec<_>>();
14060 self.unfold_ranges(&selections, true, true, cx);
14061
14062 let mut new_selection_ranges = Vec::new();
14063 {
14064 let buffer = self.buffer.read(cx).read(cx);
14065 for selection in selections {
14066 for row in selection.start.row..selection.end.row {
14067 let line_start = Point::new(row, 0);
14068 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14069
14070 if action.keep_selections {
14071 // Keep the selection range for each line
14072 let selection_start = if row == selection.start.row {
14073 selection.start
14074 } else {
14075 line_start
14076 };
14077 new_selection_ranges.push(selection_start..line_end);
14078 } else {
14079 // Collapse to cursor at end of line
14080 new_selection_ranges.push(line_end..line_end);
14081 }
14082 }
14083
14084 let is_multiline_selection = selection.start.row != selection.end.row;
14085 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14086 // so this action feels more ergonomic when paired with other selection operations
14087 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14088 if !should_skip_last {
14089 if action.keep_selections {
14090 if is_multiline_selection {
14091 let line_start = Point::new(selection.end.row, 0);
14092 new_selection_ranges.push(line_start..selection.end);
14093 } else {
14094 new_selection_ranges.push(selection.start..selection.end);
14095 }
14096 } else {
14097 new_selection_ranges.push(selection.end..selection.end);
14098 }
14099 }
14100 }
14101 }
14102 self.change_selections(Default::default(), window, cx, |s| {
14103 s.select_ranges(new_selection_ranges);
14104 });
14105 }
14106
14107 pub fn add_selection_above(
14108 &mut self,
14109 action: &AddSelectionAbove,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) {
14113 self.add_selection(true, action.skip_soft_wrap, window, cx);
14114 }
14115
14116 pub fn add_selection_below(
14117 &mut self,
14118 action: &AddSelectionBelow,
14119 window: &mut Window,
14120 cx: &mut Context<Self>,
14121 ) {
14122 self.add_selection(false, action.skip_soft_wrap, window, cx);
14123 }
14124
14125 fn add_selection(
14126 &mut self,
14127 above: bool,
14128 skip_soft_wrap: bool,
14129 window: &mut Window,
14130 cx: &mut Context<Self>,
14131 ) {
14132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14133
14134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14135 let all_selections = self.selections.all::<Point>(&display_map);
14136 let text_layout_details = self.text_layout_details(window);
14137
14138 let (mut columnar_selections, new_selections_to_columnarize) = {
14139 if let Some(state) = self.add_selections_state.as_ref() {
14140 let columnar_selection_ids: HashSet<_> = state
14141 .groups
14142 .iter()
14143 .flat_map(|group| group.stack.iter())
14144 .copied()
14145 .collect();
14146
14147 all_selections
14148 .into_iter()
14149 .partition(|s| columnar_selection_ids.contains(&s.id))
14150 } else {
14151 (Vec::new(), all_selections)
14152 }
14153 };
14154
14155 let mut state = self
14156 .add_selections_state
14157 .take()
14158 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14159
14160 for selection in new_selections_to_columnarize {
14161 let range = selection.display_range(&display_map).sorted();
14162 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14163 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14164 let positions = start_x.min(end_x)..start_x.max(end_x);
14165 let mut stack = Vec::new();
14166 for row in range.start.row().0..=range.end.row().0 {
14167 if let Some(selection) = self.selections.build_columnar_selection(
14168 &display_map,
14169 DisplayRow(row),
14170 &positions,
14171 selection.reversed,
14172 &text_layout_details,
14173 ) {
14174 stack.push(selection.id);
14175 columnar_selections.push(selection);
14176 }
14177 }
14178 if !stack.is_empty() {
14179 if above {
14180 stack.reverse();
14181 }
14182 state.groups.push(AddSelectionsGroup { above, stack });
14183 }
14184 }
14185
14186 let mut final_selections = Vec::new();
14187 let end_row = if above {
14188 DisplayRow(0)
14189 } else {
14190 display_map.max_point().row()
14191 };
14192
14193 let mut last_added_item_per_group = HashMap::default();
14194 for group in state.groups.iter_mut() {
14195 if let Some(last_id) = group.stack.last() {
14196 last_added_item_per_group.insert(*last_id, group);
14197 }
14198 }
14199
14200 for selection in columnar_selections {
14201 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14202 if above == group.above {
14203 let range = selection.display_range(&display_map).sorted();
14204 debug_assert_eq!(range.start.row(), range.end.row());
14205 let mut row = range.start.row();
14206 let positions =
14207 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14208 Pixels::from(start)..Pixels::from(end)
14209 } else {
14210 let start_x =
14211 display_map.x_for_display_point(range.start, &text_layout_details);
14212 let end_x =
14213 display_map.x_for_display_point(range.end, &text_layout_details);
14214 start_x.min(end_x)..start_x.max(end_x)
14215 };
14216
14217 let mut maybe_new_selection = None;
14218 let direction = if above { -1 } else { 1 };
14219
14220 while row != end_row {
14221 if skip_soft_wrap {
14222 row = display_map
14223 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14224 .row();
14225 } else if above {
14226 row.0 -= 1;
14227 } else {
14228 row.0 += 1;
14229 }
14230
14231 if let Some(new_selection) = self.selections.build_columnar_selection(
14232 &display_map,
14233 row,
14234 &positions,
14235 selection.reversed,
14236 &text_layout_details,
14237 ) {
14238 maybe_new_selection = Some(new_selection);
14239 break;
14240 }
14241 }
14242
14243 if let Some(new_selection) = maybe_new_selection {
14244 group.stack.push(new_selection.id);
14245 if above {
14246 final_selections.push(new_selection);
14247 final_selections.push(selection);
14248 } else {
14249 final_selections.push(selection);
14250 final_selections.push(new_selection);
14251 }
14252 } else {
14253 final_selections.push(selection);
14254 }
14255 } else {
14256 group.stack.pop();
14257 }
14258 } else {
14259 final_selections.push(selection);
14260 }
14261 }
14262
14263 self.change_selections(Default::default(), window, cx, |s| {
14264 s.select(final_selections);
14265 });
14266
14267 let final_selection_ids: HashSet<_> = self
14268 .selections
14269 .all::<Point>(&display_map)
14270 .iter()
14271 .map(|s| s.id)
14272 .collect();
14273 state.groups.retain_mut(|group| {
14274 // selections might get merged above so we remove invalid items from stacks
14275 group.stack.retain(|id| final_selection_ids.contains(id));
14276
14277 // single selection in stack can be treated as initial state
14278 group.stack.len() > 1
14279 });
14280
14281 if !state.groups.is_empty() {
14282 self.add_selections_state = Some(state);
14283 }
14284 }
14285
14286 fn select_match_ranges(
14287 &mut self,
14288 range: Range<usize>,
14289 reversed: bool,
14290 replace_newest: bool,
14291 auto_scroll: Option<Autoscroll>,
14292 window: &mut Window,
14293 cx: &mut Context<Editor>,
14294 ) {
14295 self.unfold_ranges(
14296 std::slice::from_ref(&range),
14297 false,
14298 auto_scroll.is_some(),
14299 cx,
14300 );
14301 let effects = if let Some(scroll) = auto_scroll {
14302 SelectionEffects::scroll(scroll)
14303 } else {
14304 SelectionEffects::no_scroll()
14305 };
14306 self.change_selections(effects, window, cx, |s| {
14307 if replace_newest {
14308 s.delete(s.newest_anchor().id);
14309 }
14310 if reversed {
14311 s.insert_range(range.end..range.start);
14312 } else {
14313 s.insert_range(range);
14314 }
14315 });
14316 }
14317
14318 pub fn select_next_match_internal(
14319 &mut self,
14320 display_map: &DisplaySnapshot,
14321 replace_newest: bool,
14322 autoscroll: Option<Autoscroll>,
14323 window: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) -> Result<()> {
14326 let buffer = display_map.buffer_snapshot();
14327 let mut selections = self.selections.all::<usize>(&display_map);
14328 if let Some(mut select_next_state) = self.select_next_state.take() {
14329 let query = &select_next_state.query;
14330 if !select_next_state.done {
14331 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14332 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14333 let mut next_selected_range = None;
14334
14335 let bytes_after_last_selection =
14336 buffer.bytes_in_range(last_selection.end..buffer.len());
14337 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14338 let query_matches = query
14339 .stream_find_iter(bytes_after_last_selection)
14340 .map(|result| (last_selection.end, result))
14341 .chain(
14342 query
14343 .stream_find_iter(bytes_before_first_selection)
14344 .map(|result| (0, result)),
14345 );
14346
14347 for (start_offset, query_match) in query_matches {
14348 let query_match = query_match.unwrap(); // can only fail due to I/O
14349 let offset_range =
14350 start_offset + query_match.start()..start_offset + query_match.end();
14351
14352 if !select_next_state.wordwise
14353 || (!buffer.is_inside_word(offset_range.start, None)
14354 && !buffer.is_inside_word(offset_range.end, None))
14355 {
14356 let idx = selections
14357 .partition_point(|selection| selection.end <= offset_range.start);
14358 let overlaps = selections
14359 .get(idx)
14360 .map_or(false, |selection| selection.start < offset_range.end);
14361
14362 if !overlaps {
14363 next_selected_range = Some(offset_range);
14364 break;
14365 }
14366 }
14367 }
14368
14369 if let Some(next_selected_range) = next_selected_range {
14370 self.select_match_ranges(
14371 next_selected_range,
14372 last_selection.reversed,
14373 replace_newest,
14374 autoscroll,
14375 window,
14376 cx,
14377 );
14378 } else {
14379 select_next_state.done = true;
14380 }
14381 }
14382
14383 self.select_next_state = Some(select_next_state);
14384 } else {
14385 let mut only_carets = true;
14386 let mut same_text_selected = true;
14387 let mut selected_text = None;
14388
14389 let mut selections_iter = selections.iter().peekable();
14390 while let Some(selection) = selections_iter.next() {
14391 if selection.start != selection.end {
14392 only_carets = false;
14393 }
14394
14395 if same_text_selected {
14396 if selected_text.is_none() {
14397 selected_text =
14398 Some(buffer.text_for_range(selection.range()).collect::<String>());
14399 }
14400
14401 if let Some(next_selection) = selections_iter.peek() {
14402 if next_selection.range().len() == selection.range().len() {
14403 let next_selected_text = buffer
14404 .text_for_range(next_selection.range())
14405 .collect::<String>();
14406 if Some(next_selected_text) != selected_text {
14407 same_text_selected = false;
14408 selected_text = None;
14409 }
14410 } else {
14411 same_text_selected = false;
14412 selected_text = None;
14413 }
14414 }
14415 }
14416 }
14417
14418 if only_carets {
14419 for selection in &mut selections {
14420 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14421 selection.start = word_range.start;
14422 selection.end = word_range.end;
14423 selection.goal = SelectionGoal::None;
14424 selection.reversed = false;
14425 self.select_match_ranges(
14426 selection.start..selection.end,
14427 selection.reversed,
14428 replace_newest,
14429 autoscroll,
14430 window,
14431 cx,
14432 );
14433 }
14434
14435 if selections.len() == 1 {
14436 let selection = selections
14437 .last()
14438 .expect("ensured that there's only one selection");
14439 let query = buffer
14440 .text_for_range(selection.start..selection.end)
14441 .collect::<String>();
14442 let is_empty = query.is_empty();
14443 let select_state = SelectNextState {
14444 query: AhoCorasick::new(&[query])?,
14445 wordwise: true,
14446 done: is_empty,
14447 };
14448 self.select_next_state = Some(select_state);
14449 } else {
14450 self.select_next_state = None;
14451 }
14452 } else if let Some(selected_text) = selected_text {
14453 self.select_next_state = Some(SelectNextState {
14454 query: AhoCorasick::new(&[selected_text])?,
14455 wordwise: false,
14456 done: false,
14457 });
14458 self.select_next_match_internal(
14459 display_map,
14460 replace_newest,
14461 autoscroll,
14462 window,
14463 cx,
14464 )?;
14465 }
14466 }
14467 Ok(())
14468 }
14469
14470 pub fn select_all_matches(
14471 &mut self,
14472 _action: &SelectAllMatches,
14473 window: &mut Window,
14474 cx: &mut Context<Self>,
14475 ) -> Result<()> {
14476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14477
14478 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14479
14480 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14481 let Some(select_next_state) = self.select_next_state.as_mut() else {
14482 return Ok(());
14483 };
14484 if select_next_state.done {
14485 return Ok(());
14486 }
14487
14488 let mut new_selections = Vec::new();
14489
14490 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14491 let buffer = display_map.buffer_snapshot();
14492 let query_matches = select_next_state
14493 .query
14494 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14495
14496 for query_match in query_matches.into_iter() {
14497 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14498 let offset_range = if reversed {
14499 query_match.end()..query_match.start()
14500 } else {
14501 query_match.start()..query_match.end()
14502 };
14503
14504 if !select_next_state.wordwise
14505 || (!buffer.is_inside_word(offset_range.start, None)
14506 && !buffer.is_inside_word(offset_range.end, None))
14507 {
14508 new_selections.push(offset_range.start..offset_range.end);
14509 }
14510 }
14511
14512 select_next_state.done = true;
14513
14514 if new_selections.is_empty() {
14515 log::error!("bug: new_selections is empty in select_all_matches");
14516 return Ok(());
14517 }
14518
14519 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14520 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14521 selections.select_ranges(new_selections)
14522 });
14523
14524 Ok(())
14525 }
14526
14527 pub fn select_next(
14528 &mut self,
14529 action: &SelectNext,
14530 window: &mut Window,
14531 cx: &mut Context<Self>,
14532 ) -> Result<()> {
14533 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14535 self.select_next_match_internal(
14536 &display_map,
14537 action.replace_newest,
14538 Some(Autoscroll::newest()),
14539 window,
14540 cx,
14541 )?;
14542 Ok(())
14543 }
14544
14545 pub fn select_previous(
14546 &mut self,
14547 action: &SelectPrevious,
14548 window: &mut Window,
14549 cx: &mut Context<Self>,
14550 ) -> Result<()> {
14551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14552 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14553 let buffer = display_map.buffer_snapshot();
14554 let mut selections = self.selections.all::<usize>(&display_map);
14555 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14556 let query = &select_prev_state.query;
14557 if !select_prev_state.done {
14558 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14559 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14560 let mut next_selected_range = None;
14561 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14562 let bytes_before_last_selection =
14563 buffer.reversed_bytes_in_range(0..last_selection.start);
14564 let bytes_after_first_selection =
14565 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14566 let query_matches = query
14567 .stream_find_iter(bytes_before_last_selection)
14568 .map(|result| (last_selection.start, result))
14569 .chain(
14570 query
14571 .stream_find_iter(bytes_after_first_selection)
14572 .map(|result| (buffer.len(), result)),
14573 );
14574 for (end_offset, query_match) in query_matches {
14575 let query_match = query_match.unwrap(); // can only fail due to I/O
14576 let offset_range =
14577 end_offset - query_match.end()..end_offset - query_match.start();
14578
14579 if !select_prev_state.wordwise
14580 || (!buffer.is_inside_word(offset_range.start, None)
14581 && !buffer.is_inside_word(offset_range.end, None))
14582 {
14583 next_selected_range = Some(offset_range);
14584 break;
14585 }
14586 }
14587
14588 if let Some(next_selected_range) = next_selected_range {
14589 self.select_match_ranges(
14590 next_selected_range,
14591 last_selection.reversed,
14592 action.replace_newest,
14593 Some(Autoscroll::newest()),
14594 window,
14595 cx,
14596 );
14597 } else {
14598 select_prev_state.done = true;
14599 }
14600 }
14601
14602 self.select_prev_state = Some(select_prev_state);
14603 } else {
14604 let mut only_carets = true;
14605 let mut same_text_selected = true;
14606 let mut selected_text = None;
14607
14608 let mut selections_iter = selections.iter().peekable();
14609 while let Some(selection) = selections_iter.next() {
14610 if selection.start != selection.end {
14611 only_carets = false;
14612 }
14613
14614 if same_text_selected {
14615 if selected_text.is_none() {
14616 selected_text =
14617 Some(buffer.text_for_range(selection.range()).collect::<String>());
14618 }
14619
14620 if let Some(next_selection) = selections_iter.peek() {
14621 if next_selection.range().len() == selection.range().len() {
14622 let next_selected_text = buffer
14623 .text_for_range(next_selection.range())
14624 .collect::<String>();
14625 if Some(next_selected_text) != selected_text {
14626 same_text_selected = false;
14627 selected_text = None;
14628 }
14629 } else {
14630 same_text_selected = false;
14631 selected_text = None;
14632 }
14633 }
14634 }
14635 }
14636
14637 if only_carets {
14638 for selection in &mut selections {
14639 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14640 selection.start = word_range.start;
14641 selection.end = word_range.end;
14642 selection.goal = SelectionGoal::None;
14643 selection.reversed = false;
14644 self.select_match_ranges(
14645 selection.start..selection.end,
14646 selection.reversed,
14647 action.replace_newest,
14648 Some(Autoscroll::newest()),
14649 window,
14650 cx,
14651 );
14652 }
14653 if selections.len() == 1 {
14654 let selection = selections
14655 .last()
14656 .expect("ensured that there's only one selection");
14657 let query = buffer
14658 .text_for_range(selection.start..selection.end)
14659 .collect::<String>();
14660 let is_empty = query.is_empty();
14661 let select_state = SelectNextState {
14662 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14663 wordwise: true,
14664 done: is_empty,
14665 };
14666 self.select_prev_state = Some(select_state);
14667 } else {
14668 self.select_prev_state = None;
14669 }
14670 } else if let Some(selected_text) = selected_text {
14671 self.select_prev_state = Some(SelectNextState {
14672 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14673 wordwise: false,
14674 done: false,
14675 });
14676 self.select_previous(action, window, cx)?;
14677 }
14678 }
14679 Ok(())
14680 }
14681
14682 pub fn find_next_match(
14683 &mut self,
14684 _: &FindNextMatch,
14685 window: &mut Window,
14686 cx: &mut Context<Self>,
14687 ) -> Result<()> {
14688 let selections = self.selections.disjoint_anchors_arc();
14689 match selections.first() {
14690 Some(first) if selections.len() >= 2 => {
14691 self.change_selections(Default::default(), window, cx, |s| {
14692 s.select_ranges([first.range()]);
14693 });
14694 }
14695 _ => self.select_next(
14696 &SelectNext {
14697 replace_newest: true,
14698 },
14699 window,
14700 cx,
14701 )?,
14702 }
14703 Ok(())
14704 }
14705
14706 pub fn find_previous_match(
14707 &mut self,
14708 _: &FindPreviousMatch,
14709 window: &mut Window,
14710 cx: &mut Context<Self>,
14711 ) -> Result<()> {
14712 let selections = self.selections.disjoint_anchors_arc();
14713 match selections.last() {
14714 Some(last) if selections.len() >= 2 => {
14715 self.change_selections(Default::default(), window, cx, |s| {
14716 s.select_ranges([last.range()]);
14717 });
14718 }
14719 _ => self.select_previous(
14720 &SelectPrevious {
14721 replace_newest: true,
14722 },
14723 window,
14724 cx,
14725 )?,
14726 }
14727 Ok(())
14728 }
14729
14730 pub fn toggle_comments(
14731 &mut self,
14732 action: &ToggleComments,
14733 window: &mut Window,
14734 cx: &mut Context<Self>,
14735 ) {
14736 if self.read_only(cx) {
14737 return;
14738 }
14739 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14740 let text_layout_details = &self.text_layout_details(window);
14741 self.transact(window, cx, |this, window, cx| {
14742 let mut selections = this
14743 .selections
14744 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14745 let mut edits = Vec::new();
14746 let mut selection_edit_ranges = Vec::new();
14747 let mut last_toggled_row = None;
14748 let snapshot = this.buffer.read(cx).read(cx);
14749 let empty_str: Arc<str> = Arc::default();
14750 let mut suffixes_inserted = Vec::new();
14751 let ignore_indent = action.ignore_indent;
14752
14753 fn comment_prefix_range(
14754 snapshot: &MultiBufferSnapshot,
14755 row: MultiBufferRow,
14756 comment_prefix: &str,
14757 comment_prefix_whitespace: &str,
14758 ignore_indent: bool,
14759 ) -> Range<Point> {
14760 let indent_size = if ignore_indent {
14761 0
14762 } else {
14763 snapshot.indent_size_for_line(row).len
14764 };
14765
14766 let start = Point::new(row.0, indent_size);
14767
14768 let mut line_bytes = snapshot
14769 .bytes_in_range(start..snapshot.max_point())
14770 .flatten()
14771 .copied();
14772
14773 // If this line currently begins with the line comment prefix, then record
14774 // the range containing the prefix.
14775 if line_bytes
14776 .by_ref()
14777 .take(comment_prefix.len())
14778 .eq(comment_prefix.bytes())
14779 {
14780 // Include any whitespace that matches the comment prefix.
14781 let matching_whitespace_len = line_bytes
14782 .zip(comment_prefix_whitespace.bytes())
14783 .take_while(|(a, b)| a == b)
14784 .count() as u32;
14785 let end = Point::new(
14786 start.row,
14787 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14788 );
14789 start..end
14790 } else {
14791 start..start
14792 }
14793 }
14794
14795 fn comment_suffix_range(
14796 snapshot: &MultiBufferSnapshot,
14797 row: MultiBufferRow,
14798 comment_suffix: &str,
14799 comment_suffix_has_leading_space: bool,
14800 ) -> Range<Point> {
14801 let end = Point::new(row.0, snapshot.line_len(row));
14802 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14803
14804 let mut line_end_bytes = snapshot
14805 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14806 .flatten()
14807 .copied();
14808
14809 let leading_space_len = if suffix_start_column > 0
14810 && line_end_bytes.next() == Some(b' ')
14811 && comment_suffix_has_leading_space
14812 {
14813 1
14814 } else {
14815 0
14816 };
14817
14818 // If this line currently begins with the line comment prefix, then record
14819 // the range containing the prefix.
14820 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14821 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14822 start..end
14823 } else {
14824 end..end
14825 }
14826 }
14827
14828 // TODO: Handle selections that cross excerpts
14829 for selection in &mut selections {
14830 let start_column = snapshot
14831 .indent_size_for_line(MultiBufferRow(selection.start.row))
14832 .len;
14833 let language = if let Some(language) =
14834 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14835 {
14836 language
14837 } else {
14838 continue;
14839 };
14840
14841 selection_edit_ranges.clear();
14842
14843 // If multiple selections contain a given row, avoid processing that
14844 // row more than once.
14845 let mut start_row = MultiBufferRow(selection.start.row);
14846 if last_toggled_row == Some(start_row) {
14847 start_row = start_row.next_row();
14848 }
14849 let end_row =
14850 if selection.end.row > selection.start.row && selection.end.column == 0 {
14851 MultiBufferRow(selection.end.row - 1)
14852 } else {
14853 MultiBufferRow(selection.end.row)
14854 };
14855 last_toggled_row = Some(end_row);
14856
14857 if start_row > end_row {
14858 continue;
14859 }
14860
14861 // If the language has line comments, toggle those.
14862 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14863
14864 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14865 if ignore_indent {
14866 full_comment_prefixes = full_comment_prefixes
14867 .into_iter()
14868 .map(|s| Arc::from(s.trim_end()))
14869 .collect();
14870 }
14871
14872 if !full_comment_prefixes.is_empty() {
14873 let first_prefix = full_comment_prefixes
14874 .first()
14875 .expect("prefixes is non-empty");
14876 let prefix_trimmed_lengths = full_comment_prefixes
14877 .iter()
14878 .map(|p| p.trim_end_matches(' ').len())
14879 .collect::<SmallVec<[usize; 4]>>();
14880
14881 let mut all_selection_lines_are_comments = true;
14882
14883 for row in start_row.0..=end_row.0 {
14884 let row = MultiBufferRow(row);
14885 if start_row < end_row && snapshot.is_line_blank(row) {
14886 continue;
14887 }
14888
14889 let prefix_range = full_comment_prefixes
14890 .iter()
14891 .zip(prefix_trimmed_lengths.iter().copied())
14892 .map(|(prefix, trimmed_prefix_len)| {
14893 comment_prefix_range(
14894 snapshot.deref(),
14895 row,
14896 &prefix[..trimmed_prefix_len],
14897 &prefix[trimmed_prefix_len..],
14898 ignore_indent,
14899 )
14900 })
14901 .max_by_key(|range| range.end.column - range.start.column)
14902 .expect("prefixes is non-empty");
14903
14904 if prefix_range.is_empty() {
14905 all_selection_lines_are_comments = false;
14906 }
14907
14908 selection_edit_ranges.push(prefix_range);
14909 }
14910
14911 if all_selection_lines_are_comments {
14912 edits.extend(
14913 selection_edit_ranges
14914 .iter()
14915 .cloned()
14916 .map(|range| (range, empty_str.clone())),
14917 );
14918 } else {
14919 let min_column = selection_edit_ranges
14920 .iter()
14921 .map(|range| range.start.column)
14922 .min()
14923 .unwrap_or(0);
14924 edits.extend(selection_edit_ranges.iter().map(|range| {
14925 let position = Point::new(range.start.row, min_column);
14926 (position..position, first_prefix.clone())
14927 }));
14928 }
14929 } else if let Some(BlockCommentConfig {
14930 start: full_comment_prefix,
14931 end: comment_suffix,
14932 ..
14933 }) = language.block_comment()
14934 {
14935 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14936 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14937 let prefix_range = comment_prefix_range(
14938 snapshot.deref(),
14939 start_row,
14940 comment_prefix,
14941 comment_prefix_whitespace,
14942 ignore_indent,
14943 );
14944 let suffix_range = comment_suffix_range(
14945 snapshot.deref(),
14946 end_row,
14947 comment_suffix.trim_start_matches(' '),
14948 comment_suffix.starts_with(' '),
14949 );
14950
14951 if prefix_range.is_empty() || suffix_range.is_empty() {
14952 edits.push((
14953 prefix_range.start..prefix_range.start,
14954 full_comment_prefix.clone(),
14955 ));
14956 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14957 suffixes_inserted.push((end_row, comment_suffix.len()));
14958 } else {
14959 edits.push((prefix_range, empty_str.clone()));
14960 edits.push((suffix_range, empty_str.clone()));
14961 }
14962 } else {
14963 continue;
14964 }
14965 }
14966
14967 drop(snapshot);
14968 this.buffer.update(cx, |buffer, cx| {
14969 buffer.edit(edits, None, cx);
14970 });
14971
14972 // Adjust selections so that they end before any comment suffixes that
14973 // were inserted.
14974 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14975 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
14976 let snapshot = this.buffer.read(cx).read(cx);
14977 for selection in &mut selections {
14978 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14979 match row.cmp(&MultiBufferRow(selection.end.row)) {
14980 Ordering::Less => {
14981 suffixes_inserted.next();
14982 continue;
14983 }
14984 Ordering::Greater => break,
14985 Ordering::Equal => {
14986 if selection.end.column == snapshot.line_len(row) {
14987 if selection.is_empty() {
14988 selection.start.column -= suffix_len as u32;
14989 }
14990 selection.end.column -= suffix_len as u32;
14991 }
14992 break;
14993 }
14994 }
14995 }
14996 }
14997
14998 drop(snapshot);
14999 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15000
15001 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15002 let selections_on_single_row = selections.windows(2).all(|selections| {
15003 selections[0].start.row == selections[1].start.row
15004 && selections[0].end.row == selections[1].end.row
15005 && selections[0].start.row == selections[0].end.row
15006 });
15007 let selections_selecting = selections
15008 .iter()
15009 .any(|selection| selection.start != selection.end);
15010 let advance_downwards = action.advance_downwards
15011 && selections_on_single_row
15012 && !selections_selecting
15013 && !matches!(this.mode, EditorMode::SingleLine);
15014
15015 if advance_downwards {
15016 let snapshot = this.buffer.read(cx).snapshot(cx);
15017
15018 this.change_selections(Default::default(), window, cx, |s| {
15019 s.move_cursors_with(|display_snapshot, display_point, _| {
15020 let mut point = display_point.to_point(display_snapshot);
15021 point.row += 1;
15022 point = snapshot.clip_point(point, Bias::Left);
15023 let display_point = point.to_display_point(display_snapshot);
15024 let goal = SelectionGoal::HorizontalPosition(
15025 display_snapshot
15026 .x_for_display_point(display_point, text_layout_details)
15027 .into(),
15028 );
15029 (display_point, goal)
15030 })
15031 });
15032 }
15033 });
15034 }
15035
15036 pub fn select_enclosing_symbol(
15037 &mut self,
15038 _: &SelectEnclosingSymbol,
15039 window: &mut Window,
15040 cx: &mut Context<Self>,
15041 ) {
15042 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15043
15044 let buffer = self.buffer.read(cx).snapshot(cx);
15045 let old_selections = self
15046 .selections
15047 .all::<usize>(&self.display_snapshot(cx))
15048 .into_boxed_slice();
15049
15050 fn update_selection(
15051 selection: &Selection<usize>,
15052 buffer_snap: &MultiBufferSnapshot,
15053 ) -> Option<Selection<usize>> {
15054 let cursor = selection.head();
15055 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15056 for symbol in symbols.iter().rev() {
15057 let start = symbol.range.start.to_offset(buffer_snap);
15058 let end = symbol.range.end.to_offset(buffer_snap);
15059 let new_range = start..end;
15060 if start < selection.start || end > selection.end {
15061 return Some(Selection {
15062 id: selection.id,
15063 start: new_range.start,
15064 end: new_range.end,
15065 goal: SelectionGoal::None,
15066 reversed: selection.reversed,
15067 });
15068 }
15069 }
15070 None
15071 }
15072
15073 let mut selected_larger_symbol = false;
15074 let new_selections = old_selections
15075 .iter()
15076 .map(|selection| match update_selection(selection, &buffer) {
15077 Some(new_selection) => {
15078 if new_selection.range() != selection.range() {
15079 selected_larger_symbol = true;
15080 }
15081 new_selection
15082 }
15083 None => selection.clone(),
15084 })
15085 .collect::<Vec<_>>();
15086
15087 if selected_larger_symbol {
15088 self.change_selections(Default::default(), window, cx, |s| {
15089 s.select(new_selections);
15090 });
15091 }
15092 }
15093
15094 pub fn select_larger_syntax_node(
15095 &mut self,
15096 _: &SelectLargerSyntaxNode,
15097 window: &mut Window,
15098 cx: &mut Context<Self>,
15099 ) {
15100 let Some(visible_row_count) = self.visible_row_count() else {
15101 return;
15102 };
15103 let old_selections: Box<[_]> = self
15104 .selections
15105 .all::<usize>(&self.display_snapshot(cx))
15106 .into();
15107 if old_selections.is_empty() {
15108 return;
15109 }
15110
15111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15112
15113 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15114 let buffer = self.buffer.read(cx).snapshot(cx);
15115
15116 let mut selected_larger_node = false;
15117 let mut new_selections = old_selections
15118 .iter()
15119 .map(|selection| {
15120 let old_range = selection.start..selection.end;
15121
15122 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15123 // manually select word at selection
15124 if ["string_content", "inline"].contains(&node.kind()) {
15125 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15126 // ignore if word is already selected
15127 if !word_range.is_empty() && old_range != word_range {
15128 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15129 // only select word if start and end point belongs to same word
15130 if word_range == last_word_range {
15131 selected_larger_node = true;
15132 return Selection {
15133 id: selection.id,
15134 start: word_range.start,
15135 end: word_range.end,
15136 goal: SelectionGoal::None,
15137 reversed: selection.reversed,
15138 };
15139 }
15140 }
15141 }
15142 }
15143
15144 let mut new_range = old_range.clone();
15145 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15146 new_range = range;
15147 if !node.is_named() {
15148 continue;
15149 }
15150 if !display_map.intersects_fold(new_range.start)
15151 && !display_map.intersects_fold(new_range.end)
15152 {
15153 break;
15154 }
15155 }
15156
15157 selected_larger_node |= new_range != old_range;
15158 Selection {
15159 id: selection.id,
15160 start: new_range.start,
15161 end: new_range.end,
15162 goal: SelectionGoal::None,
15163 reversed: selection.reversed,
15164 }
15165 })
15166 .collect::<Vec<_>>();
15167
15168 if !selected_larger_node {
15169 return; // don't put this call in the history
15170 }
15171
15172 // scroll based on transformation done to the last selection created by the user
15173 let (last_old, last_new) = old_selections
15174 .last()
15175 .zip(new_selections.last().cloned())
15176 .expect("old_selections isn't empty");
15177
15178 // revert selection
15179 let is_selection_reversed = {
15180 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15181 new_selections.last_mut().expect("checked above").reversed =
15182 should_newest_selection_be_reversed;
15183 should_newest_selection_be_reversed
15184 };
15185
15186 if selected_larger_node {
15187 self.select_syntax_node_history.disable_clearing = true;
15188 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15189 s.select(new_selections.clone());
15190 });
15191 self.select_syntax_node_history.disable_clearing = false;
15192 }
15193
15194 let start_row = last_new.start.to_display_point(&display_map).row().0;
15195 let end_row = last_new.end.to_display_point(&display_map).row().0;
15196 let selection_height = end_row - start_row + 1;
15197 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15198
15199 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15200 let scroll_behavior = if fits_on_the_screen {
15201 self.request_autoscroll(Autoscroll::fit(), cx);
15202 SelectSyntaxNodeScrollBehavior::FitSelection
15203 } else if is_selection_reversed {
15204 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15205 SelectSyntaxNodeScrollBehavior::CursorTop
15206 } else {
15207 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15208 SelectSyntaxNodeScrollBehavior::CursorBottom
15209 };
15210
15211 self.select_syntax_node_history.push((
15212 old_selections,
15213 scroll_behavior,
15214 is_selection_reversed,
15215 ));
15216 }
15217
15218 pub fn select_smaller_syntax_node(
15219 &mut self,
15220 _: &SelectSmallerSyntaxNode,
15221 window: &mut Window,
15222 cx: &mut Context<Self>,
15223 ) {
15224 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15225
15226 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15227 self.select_syntax_node_history.pop()
15228 {
15229 if let Some(selection) = selections.last_mut() {
15230 selection.reversed = is_selection_reversed;
15231 }
15232
15233 self.select_syntax_node_history.disable_clearing = true;
15234 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15235 s.select(selections.to_vec());
15236 });
15237 self.select_syntax_node_history.disable_clearing = false;
15238
15239 match scroll_behavior {
15240 SelectSyntaxNodeScrollBehavior::CursorTop => {
15241 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15242 }
15243 SelectSyntaxNodeScrollBehavior::FitSelection => {
15244 self.request_autoscroll(Autoscroll::fit(), cx);
15245 }
15246 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15247 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15248 }
15249 }
15250 }
15251 }
15252
15253 pub fn unwrap_syntax_node(
15254 &mut self,
15255 _: &UnwrapSyntaxNode,
15256 window: &mut Window,
15257 cx: &mut Context<Self>,
15258 ) {
15259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15260
15261 let buffer = self.buffer.read(cx).snapshot(cx);
15262 let selections = self
15263 .selections
15264 .all::<usize>(&self.display_snapshot(cx))
15265 .into_iter()
15266 // subtracting the offset requires sorting
15267 .sorted_by_key(|i| i.start);
15268
15269 let full_edits = selections
15270 .into_iter()
15271 .filter_map(|selection| {
15272 let child = if selection.is_empty()
15273 && let Some((_, ancestor_range)) =
15274 buffer.syntax_ancestor(selection.start..selection.end)
15275 {
15276 ancestor_range
15277 } else {
15278 selection.range()
15279 };
15280
15281 let mut parent = child.clone();
15282 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15283 parent = ancestor_range;
15284 if parent.start < child.start || parent.end > child.end {
15285 break;
15286 }
15287 }
15288
15289 if parent == child {
15290 return None;
15291 }
15292 let text = buffer.text_for_range(child).collect::<String>();
15293 Some((selection.id, parent, text))
15294 })
15295 .collect::<Vec<_>>();
15296 if full_edits.is_empty() {
15297 return;
15298 }
15299
15300 self.transact(window, cx, |this, window, cx| {
15301 this.buffer.update(cx, |buffer, cx| {
15302 buffer.edit(
15303 full_edits
15304 .iter()
15305 .map(|(_, p, t)| (p.clone(), t.clone()))
15306 .collect::<Vec<_>>(),
15307 None,
15308 cx,
15309 );
15310 });
15311 this.change_selections(Default::default(), window, cx, |s| {
15312 let mut offset = 0;
15313 let mut selections = vec![];
15314 for (id, parent, text) in full_edits {
15315 let start = parent.start - offset;
15316 offset += parent.len() - text.len();
15317 selections.push(Selection {
15318 id,
15319 start,
15320 end: start + text.len(),
15321 reversed: false,
15322 goal: Default::default(),
15323 });
15324 }
15325 s.select(selections);
15326 });
15327 });
15328 }
15329
15330 pub fn select_next_syntax_node(
15331 &mut self,
15332 _: &SelectNextSyntaxNode,
15333 window: &mut Window,
15334 cx: &mut Context<Self>,
15335 ) {
15336 let old_selections: Box<[_]> = self
15337 .selections
15338 .all::<usize>(&self.display_snapshot(cx))
15339 .into();
15340 if old_selections.is_empty() {
15341 return;
15342 }
15343
15344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15345
15346 let buffer = self.buffer.read(cx).snapshot(cx);
15347 let mut selected_sibling = false;
15348
15349 let new_selections = old_selections
15350 .iter()
15351 .map(|selection| {
15352 let old_range = selection.start..selection.end;
15353
15354 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15355 let new_range = node.byte_range();
15356 selected_sibling = true;
15357 Selection {
15358 id: selection.id,
15359 start: new_range.start,
15360 end: new_range.end,
15361 goal: SelectionGoal::None,
15362 reversed: selection.reversed,
15363 }
15364 } else {
15365 selection.clone()
15366 }
15367 })
15368 .collect::<Vec<_>>();
15369
15370 if selected_sibling {
15371 self.change_selections(
15372 SelectionEffects::scroll(Autoscroll::fit()),
15373 window,
15374 cx,
15375 |s| {
15376 s.select(new_selections);
15377 },
15378 );
15379 }
15380 }
15381
15382 pub fn select_prev_syntax_node(
15383 &mut self,
15384 _: &SelectPreviousSyntaxNode,
15385 window: &mut Window,
15386 cx: &mut Context<Self>,
15387 ) {
15388 let old_selections: Box<[_]> = self
15389 .selections
15390 .all::<usize>(&self.display_snapshot(cx))
15391 .into();
15392 if old_selections.is_empty() {
15393 return;
15394 }
15395
15396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15397
15398 let buffer = self.buffer.read(cx).snapshot(cx);
15399 let mut selected_sibling = false;
15400
15401 let new_selections = old_selections
15402 .iter()
15403 .map(|selection| {
15404 let old_range = selection.start..selection.end;
15405
15406 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15407 let new_range = node.byte_range();
15408 selected_sibling = true;
15409 Selection {
15410 id: selection.id,
15411 start: new_range.start,
15412 end: new_range.end,
15413 goal: SelectionGoal::None,
15414 reversed: selection.reversed,
15415 }
15416 } else {
15417 selection.clone()
15418 }
15419 })
15420 .collect::<Vec<_>>();
15421
15422 if selected_sibling {
15423 self.change_selections(
15424 SelectionEffects::scroll(Autoscroll::fit()),
15425 window,
15426 cx,
15427 |s| {
15428 s.select(new_selections);
15429 },
15430 );
15431 }
15432 }
15433
15434 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15435 if !EditorSettings::get_global(cx).gutter.runnables {
15436 self.clear_tasks();
15437 return Task::ready(());
15438 }
15439 let project = self.project().map(Entity::downgrade);
15440 let task_sources = self.lsp_task_sources(cx);
15441 let multi_buffer = self.buffer.downgrade();
15442 cx.spawn_in(window, async move |editor, cx| {
15443 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15444 let Some(project) = project.and_then(|p| p.upgrade()) else {
15445 return;
15446 };
15447 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15448 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15449 }) else {
15450 return;
15451 };
15452
15453 let hide_runnables = project
15454 .update(cx, |project, _| project.is_via_collab())
15455 .unwrap_or(true);
15456 if hide_runnables {
15457 return;
15458 }
15459 let new_rows =
15460 cx.background_spawn({
15461 let snapshot = display_snapshot.clone();
15462 async move {
15463 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15464 }
15465 })
15466 .await;
15467 let Ok(lsp_tasks) =
15468 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15469 else {
15470 return;
15471 };
15472 let lsp_tasks = lsp_tasks.await;
15473
15474 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15475 lsp_tasks
15476 .into_iter()
15477 .flat_map(|(kind, tasks)| {
15478 tasks.into_iter().filter_map(move |(location, task)| {
15479 Some((kind.clone(), location?, task))
15480 })
15481 })
15482 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15483 let buffer = location.target.buffer;
15484 let buffer_snapshot = buffer.read(cx).snapshot();
15485 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15486 |(excerpt_id, snapshot, _)| {
15487 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15488 display_snapshot
15489 .buffer_snapshot()
15490 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15491 } else {
15492 None
15493 }
15494 },
15495 );
15496 if let Some(offset) = offset {
15497 let task_buffer_range =
15498 location.target.range.to_point(&buffer_snapshot);
15499 let context_buffer_range =
15500 task_buffer_range.to_offset(&buffer_snapshot);
15501 let context_range = BufferOffset(context_buffer_range.start)
15502 ..BufferOffset(context_buffer_range.end);
15503
15504 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15505 .or_insert_with(|| RunnableTasks {
15506 templates: Vec::new(),
15507 offset,
15508 column: task_buffer_range.start.column,
15509 extra_variables: HashMap::default(),
15510 context_range,
15511 })
15512 .templates
15513 .push((kind, task.original_task().clone()));
15514 }
15515
15516 acc
15517 })
15518 }) else {
15519 return;
15520 };
15521
15522 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15523 buffer.language_settings(cx).tasks.prefer_lsp
15524 }) else {
15525 return;
15526 };
15527
15528 let rows = Self::runnable_rows(
15529 project,
15530 display_snapshot,
15531 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15532 new_rows,
15533 cx.clone(),
15534 )
15535 .await;
15536 editor
15537 .update(cx, |editor, _| {
15538 editor.clear_tasks();
15539 for (key, mut value) in rows {
15540 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15541 value.templates.extend(lsp_tasks.templates);
15542 }
15543
15544 editor.insert_tasks(key, value);
15545 }
15546 for (key, value) in lsp_tasks_by_rows {
15547 editor.insert_tasks(key, value);
15548 }
15549 })
15550 .ok();
15551 })
15552 }
15553 fn fetch_runnable_ranges(
15554 snapshot: &DisplaySnapshot,
15555 range: Range<Anchor>,
15556 ) -> Vec<language::RunnableRange> {
15557 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15558 }
15559
15560 fn runnable_rows(
15561 project: Entity<Project>,
15562 snapshot: DisplaySnapshot,
15563 prefer_lsp: bool,
15564 runnable_ranges: Vec<RunnableRange>,
15565 cx: AsyncWindowContext,
15566 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15567 cx.spawn(async move |cx| {
15568 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15569 for mut runnable in runnable_ranges {
15570 let Some(tasks) = cx
15571 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15572 .ok()
15573 else {
15574 continue;
15575 };
15576 let mut tasks = tasks.await;
15577
15578 if prefer_lsp {
15579 tasks.retain(|(task_kind, _)| {
15580 !matches!(task_kind, TaskSourceKind::Language { .. })
15581 });
15582 }
15583 if tasks.is_empty() {
15584 continue;
15585 }
15586
15587 let point = runnable
15588 .run_range
15589 .start
15590 .to_point(&snapshot.buffer_snapshot());
15591 let Some(row) = snapshot
15592 .buffer_snapshot()
15593 .buffer_line_for_row(MultiBufferRow(point.row))
15594 .map(|(_, range)| range.start.row)
15595 else {
15596 continue;
15597 };
15598
15599 let context_range =
15600 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15601 runnable_rows.push((
15602 (runnable.buffer_id, row),
15603 RunnableTasks {
15604 templates: tasks,
15605 offset: snapshot
15606 .buffer_snapshot()
15607 .anchor_before(runnable.run_range.start),
15608 context_range,
15609 column: point.column,
15610 extra_variables: runnable.extra_captures,
15611 },
15612 ));
15613 }
15614 runnable_rows
15615 })
15616 }
15617
15618 fn templates_with_tags(
15619 project: &Entity<Project>,
15620 runnable: &mut Runnable,
15621 cx: &mut App,
15622 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15623 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15624 let (worktree_id, file) = project
15625 .buffer_for_id(runnable.buffer, cx)
15626 .and_then(|buffer| buffer.read(cx).file())
15627 .map(|file| (file.worktree_id(cx), file.clone()))
15628 .unzip();
15629
15630 (
15631 project.task_store().read(cx).task_inventory().cloned(),
15632 worktree_id,
15633 file,
15634 )
15635 });
15636
15637 let tags = mem::take(&mut runnable.tags);
15638 let language = runnable.language.clone();
15639 cx.spawn(async move |cx| {
15640 let mut templates_with_tags = Vec::new();
15641 if let Some(inventory) = inventory {
15642 for RunnableTag(tag) in tags {
15643 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15644 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15645 }) else {
15646 return templates_with_tags;
15647 };
15648 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15649 move |(_, template)| {
15650 template.tags.iter().any(|source_tag| source_tag == &tag)
15651 },
15652 ));
15653 }
15654 }
15655 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15656
15657 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15658 // Strongest source wins; if we have worktree tag binding, prefer that to
15659 // global and language bindings;
15660 // if we have a global binding, prefer that to language binding.
15661 let first_mismatch = templates_with_tags
15662 .iter()
15663 .position(|(tag_source, _)| tag_source != leading_tag_source);
15664 if let Some(index) = first_mismatch {
15665 templates_with_tags.truncate(index);
15666 }
15667 }
15668
15669 templates_with_tags
15670 })
15671 }
15672
15673 pub fn move_to_enclosing_bracket(
15674 &mut self,
15675 _: &MoveToEnclosingBracket,
15676 window: &mut Window,
15677 cx: &mut Context<Self>,
15678 ) {
15679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15680 self.change_selections(Default::default(), window, cx, |s| {
15681 s.move_offsets_with(|snapshot, selection| {
15682 let Some(enclosing_bracket_ranges) =
15683 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15684 else {
15685 return;
15686 };
15687
15688 let mut best_length = usize::MAX;
15689 let mut best_inside = false;
15690 let mut best_in_bracket_range = false;
15691 let mut best_destination = None;
15692 for (open, close) in enclosing_bracket_ranges {
15693 let close = close.to_inclusive();
15694 let length = close.end() - open.start;
15695 let inside = selection.start >= open.end && selection.end <= *close.start();
15696 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15697 || close.contains(&selection.head());
15698
15699 // If best is next to a bracket and current isn't, skip
15700 if !in_bracket_range && best_in_bracket_range {
15701 continue;
15702 }
15703
15704 // Prefer smaller lengths unless best is inside and current isn't
15705 if length > best_length && (best_inside || !inside) {
15706 continue;
15707 }
15708
15709 best_length = length;
15710 best_inside = inside;
15711 best_in_bracket_range = in_bracket_range;
15712 best_destination = Some(
15713 if close.contains(&selection.start) && close.contains(&selection.end) {
15714 if inside { open.end } else { open.start }
15715 } else if inside {
15716 *close.start()
15717 } else {
15718 *close.end()
15719 },
15720 );
15721 }
15722
15723 if let Some(destination) = best_destination {
15724 selection.collapse_to(destination, SelectionGoal::None);
15725 }
15726 })
15727 });
15728 }
15729
15730 pub fn undo_selection(
15731 &mut self,
15732 _: &UndoSelection,
15733 window: &mut Window,
15734 cx: &mut Context<Self>,
15735 ) {
15736 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15737 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15738 self.selection_history.mode = SelectionHistoryMode::Undoing;
15739 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15740 this.end_selection(window, cx);
15741 this.change_selections(
15742 SelectionEffects::scroll(Autoscroll::newest()),
15743 window,
15744 cx,
15745 |s| s.select_anchors(entry.selections.to_vec()),
15746 );
15747 });
15748 self.selection_history.mode = SelectionHistoryMode::Normal;
15749
15750 self.select_next_state = entry.select_next_state;
15751 self.select_prev_state = entry.select_prev_state;
15752 self.add_selections_state = entry.add_selections_state;
15753 }
15754 }
15755
15756 pub fn redo_selection(
15757 &mut self,
15758 _: &RedoSelection,
15759 window: &mut Window,
15760 cx: &mut Context<Self>,
15761 ) {
15762 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15763 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15764 self.selection_history.mode = SelectionHistoryMode::Redoing;
15765 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15766 this.end_selection(window, cx);
15767 this.change_selections(
15768 SelectionEffects::scroll(Autoscroll::newest()),
15769 window,
15770 cx,
15771 |s| s.select_anchors(entry.selections.to_vec()),
15772 );
15773 });
15774 self.selection_history.mode = SelectionHistoryMode::Normal;
15775
15776 self.select_next_state = entry.select_next_state;
15777 self.select_prev_state = entry.select_prev_state;
15778 self.add_selections_state = entry.add_selections_state;
15779 }
15780 }
15781
15782 pub fn expand_excerpts(
15783 &mut self,
15784 action: &ExpandExcerpts,
15785 _: &mut Window,
15786 cx: &mut Context<Self>,
15787 ) {
15788 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15789 }
15790
15791 pub fn expand_excerpts_down(
15792 &mut self,
15793 action: &ExpandExcerptsDown,
15794 _: &mut Window,
15795 cx: &mut Context<Self>,
15796 ) {
15797 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15798 }
15799
15800 pub fn expand_excerpts_up(
15801 &mut self,
15802 action: &ExpandExcerptsUp,
15803 _: &mut Window,
15804 cx: &mut Context<Self>,
15805 ) {
15806 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15807 }
15808
15809 pub fn expand_excerpts_for_direction(
15810 &mut self,
15811 lines: u32,
15812 direction: ExpandExcerptDirection,
15813
15814 cx: &mut Context<Self>,
15815 ) {
15816 let selections = self.selections.disjoint_anchors_arc();
15817
15818 let lines = if lines == 0 {
15819 EditorSettings::get_global(cx).expand_excerpt_lines
15820 } else {
15821 lines
15822 };
15823
15824 self.buffer.update(cx, |buffer, cx| {
15825 let snapshot = buffer.snapshot(cx);
15826 let mut excerpt_ids = selections
15827 .iter()
15828 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15829 .collect::<Vec<_>>();
15830 excerpt_ids.sort();
15831 excerpt_ids.dedup();
15832 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15833 })
15834 }
15835
15836 pub fn expand_excerpt(
15837 &mut self,
15838 excerpt: ExcerptId,
15839 direction: ExpandExcerptDirection,
15840 window: &mut Window,
15841 cx: &mut Context<Self>,
15842 ) {
15843 let current_scroll_position = self.scroll_position(cx);
15844 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15845 let mut should_scroll_up = false;
15846
15847 if direction == ExpandExcerptDirection::Down {
15848 let multi_buffer = self.buffer.read(cx);
15849 let snapshot = multi_buffer.snapshot(cx);
15850 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15851 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15852 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15853 {
15854 let buffer_snapshot = buffer.read(cx).snapshot();
15855 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15856 let last_row = buffer_snapshot.max_point().row;
15857 let lines_below = last_row.saturating_sub(excerpt_end_row);
15858 should_scroll_up = lines_below >= lines_to_expand;
15859 }
15860 }
15861
15862 self.buffer.update(cx, |buffer, cx| {
15863 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15864 });
15865
15866 if should_scroll_up {
15867 let new_scroll_position =
15868 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15869 self.set_scroll_position(new_scroll_position, window, cx);
15870 }
15871 }
15872
15873 pub fn go_to_singleton_buffer_point(
15874 &mut self,
15875 point: Point,
15876 window: &mut Window,
15877 cx: &mut Context<Self>,
15878 ) {
15879 self.go_to_singleton_buffer_range(point..point, window, cx);
15880 }
15881
15882 pub fn go_to_singleton_buffer_range(
15883 &mut self,
15884 range: Range<Point>,
15885 window: &mut Window,
15886 cx: &mut Context<Self>,
15887 ) {
15888 let multibuffer = self.buffer().read(cx);
15889 let Some(buffer) = multibuffer.as_singleton() else {
15890 return;
15891 };
15892 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15893 return;
15894 };
15895 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15896 return;
15897 };
15898 self.change_selections(
15899 SelectionEffects::default().nav_history(true),
15900 window,
15901 cx,
15902 |s| s.select_anchor_ranges([start..end]),
15903 );
15904 }
15905
15906 pub fn go_to_diagnostic(
15907 &mut self,
15908 action: &GoToDiagnostic,
15909 window: &mut Window,
15910 cx: &mut Context<Self>,
15911 ) {
15912 if !self.diagnostics_enabled() {
15913 return;
15914 }
15915 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15916 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15917 }
15918
15919 pub fn go_to_prev_diagnostic(
15920 &mut self,
15921 action: &GoToPreviousDiagnostic,
15922 window: &mut Window,
15923 cx: &mut Context<Self>,
15924 ) {
15925 if !self.diagnostics_enabled() {
15926 return;
15927 }
15928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15929 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15930 }
15931
15932 pub fn go_to_diagnostic_impl(
15933 &mut self,
15934 direction: Direction,
15935 severity: GoToDiagnosticSeverityFilter,
15936 window: &mut Window,
15937 cx: &mut Context<Self>,
15938 ) {
15939 let buffer = self.buffer.read(cx).snapshot(cx);
15940 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15941
15942 let mut active_group_id = None;
15943 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15944 && active_group.active_range.start.to_offset(&buffer) == selection.start
15945 {
15946 active_group_id = Some(active_group.group_id);
15947 }
15948
15949 fn filtered<'a>(
15950 snapshot: EditorSnapshot,
15951 severity: GoToDiagnosticSeverityFilter,
15952 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15953 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15954 diagnostics
15955 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15956 .filter(|entry| entry.range.start != entry.range.end)
15957 .filter(|entry| !entry.diagnostic.is_unnecessary)
15958 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15959 }
15960
15961 let snapshot = self.snapshot(window, cx);
15962 let before = filtered(
15963 snapshot.clone(),
15964 severity,
15965 buffer
15966 .diagnostics_in_range(0..selection.start)
15967 .filter(|entry| entry.range.start <= selection.start),
15968 );
15969 let after = filtered(
15970 snapshot,
15971 severity,
15972 buffer
15973 .diagnostics_in_range(selection.start..buffer.len())
15974 .filter(|entry| entry.range.start >= selection.start),
15975 );
15976
15977 let mut found: Option<DiagnosticEntryRef<usize>> = None;
15978 if direction == Direction::Prev {
15979 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15980 {
15981 for diagnostic in prev_diagnostics.into_iter().rev() {
15982 if diagnostic.range.start != selection.start
15983 || active_group_id
15984 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15985 {
15986 found = Some(diagnostic);
15987 break 'outer;
15988 }
15989 }
15990 }
15991 } else {
15992 for diagnostic in after.chain(before) {
15993 if diagnostic.range.start != selection.start
15994 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15995 {
15996 found = Some(diagnostic);
15997 break;
15998 }
15999 }
16000 }
16001 let Some(next_diagnostic) = found else {
16002 return;
16003 };
16004
16005 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16006 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16007 return;
16008 };
16009 self.change_selections(Default::default(), window, cx, |s| {
16010 s.select_ranges(vec![
16011 next_diagnostic.range.start..next_diagnostic.range.start,
16012 ])
16013 });
16014 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16015 self.refresh_edit_prediction(false, true, window, cx);
16016 }
16017
16018 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16020 let snapshot = self.snapshot(window, cx);
16021 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16022 self.go_to_hunk_before_or_after_position(
16023 &snapshot,
16024 selection.head(),
16025 Direction::Next,
16026 window,
16027 cx,
16028 );
16029 }
16030
16031 pub fn go_to_hunk_before_or_after_position(
16032 &mut self,
16033 snapshot: &EditorSnapshot,
16034 position: Point,
16035 direction: Direction,
16036 window: &mut Window,
16037 cx: &mut Context<Editor>,
16038 ) {
16039 let row = if direction == Direction::Next {
16040 self.hunk_after_position(snapshot, position)
16041 .map(|hunk| hunk.row_range.start)
16042 } else {
16043 self.hunk_before_position(snapshot, position)
16044 };
16045
16046 if let Some(row) = row {
16047 let destination = Point::new(row.0, 0);
16048 let autoscroll = Autoscroll::center();
16049
16050 self.unfold_ranges(&[destination..destination], false, false, cx);
16051 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16052 s.select_ranges([destination..destination]);
16053 });
16054 }
16055 }
16056
16057 fn hunk_after_position(
16058 &mut self,
16059 snapshot: &EditorSnapshot,
16060 position: Point,
16061 ) -> Option<MultiBufferDiffHunk> {
16062 snapshot
16063 .buffer_snapshot()
16064 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16065 .find(|hunk| hunk.row_range.start.0 > position.row)
16066 .or_else(|| {
16067 snapshot
16068 .buffer_snapshot()
16069 .diff_hunks_in_range(Point::zero()..position)
16070 .find(|hunk| hunk.row_range.end.0 < position.row)
16071 })
16072 }
16073
16074 fn go_to_prev_hunk(
16075 &mut self,
16076 _: &GoToPreviousHunk,
16077 window: &mut Window,
16078 cx: &mut Context<Self>,
16079 ) {
16080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16081 let snapshot = self.snapshot(window, cx);
16082 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16083 self.go_to_hunk_before_or_after_position(
16084 &snapshot,
16085 selection.head(),
16086 Direction::Prev,
16087 window,
16088 cx,
16089 );
16090 }
16091
16092 fn hunk_before_position(
16093 &mut self,
16094 snapshot: &EditorSnapshot,
16095 position: Point,
16096 ) -> Option<MultiBufferRow> {
16097 snapshot
16098 .buffer_snapshot()
16099 .diff_hunk_before(position)
16100 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16101 }
16102
16103 fn go_to_next_change(
16104 &mut self,
16105 _: &GoToNextChange,
16106 window: &mut Window,
16107 cx: &mut Context<Self>,
16108 ) {
16109 if let Some(selections) = self
16110 .change_list
16111 .next_change(1, Direction::Next)
16112 .map(|s| s.to_vec())
16113 {
16114 self.change_selections(Default::default(), window, cx, |s| {
16115 let map = s.display_map();
16116 s.select_display_ranges(selections.iter().map(|a| {
16117 let point = a.to_display_point(&map);
16118 point..point
16119 }))
16120 })
16121 }
16122 }
16123
16124 fn go_to_previous_change(
16125 &mut self,
16126 _: &GoToPreviousChange,
16127 window: &mut Window,
16128 cx: &mut Context<Self>,
16129 ) {
16130 if let Some(selections) = self
16131 .change_list
16132 .next_change(1, Direction::Prev)
16133 .map(|s| s.to_vec())
16134 {
16135 self.change_selections(Default::default(), window, cx, |s| {
16136 let map = s.display_map();
16137 s.select_display_ranges(selections.iter().map(|a| {
16138 let point = a.to_display_point(&map);
16139 point..point
16140 }))
16141 })
16142 }
16143 }
16144
16145 pub fn go_to_next_document_highlight(
16146 &mut self,
16147 _: &GoToNextDocumentHighlight,
16148 window: &mut Window,
16149 cx: &mut Context<Self>,
16150 ) {
16151 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16152 }
16153
16154 pub fn go_to_prev_document_highlight(
16155 &mut self,
16156 _: &GoToPreviousDocumentHighlight,
16157 window: &mut Window,
16158 cx: &mut Context<Self>,
16159 ) {
16160 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16161 }
16162
16163 pub fn go_to_document_highlight_before_or_after_position(
16164 &mut self,
16165 direction: Direction,
16166 window: &mut Window,
16167 cx: &mut Context<Editor>,
16168 ) {
16169 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16170 let snapshot = self.snapshot(window, cx);
16171 let buffer = &snapshot.buffer_snapshot();
16172 let position = self
16173 .selections
16174 .newest::<Point>(&snapshot.display_snapshot)
16175 .head();
16176 let anchor_position = buffer.anchor_after(position);
16177
16178 // Get all document highlights (both read and write)
16179 let mut all_highlights = Vec::new();
16180
16181 if let Some((_, read_highlights)) = self
16182 .background_highlights
16183 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16184 {
16185 all_highlights.extend(read_highlights.iter());
16186 }
16187
16188 if let Some((_, write_highlights)) = self
16189 .background_highlights
16190 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16191 {
16192 all_highlights.extend(write_highlights.iter());
16193 }
16194
16195 if all_highlights.is_empty() {
16196 return;
16197 }
16198
16199 // Sort highlights by position
16200 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16201
16202 let target_highlight = match direction {
16203 Direction::Next => {
16204 // Find the first highlight after the current position
16205 all_highlights
16206 .iter()
16207 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16208 }
16209 Direction::Prev => {
16210 // Find the last highlight before the current position
16211 all_highlights
16212 .iter()
16213 .rev()
16214 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16215 }
16216 };
16217
16218 if let Some(highlight) = target_highlight {
16219 let destination = highlight.start.to_point(buffer);
16220 let autoscroll = Autoscroll::center();
16221
16222 self.unfold_ranges(&[destination..destination], false, false, cx);
16223 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16224 s.select_ranges([destination..destination]);
16225 });
16226 }
16227 }
16228
16229 fn go_to_line<T: 'static>(
16230 &mut self,
16231 position: Anchor,
16232 highlight_color: Option<Hsla>,
16233 window: &mut Window,
16234 cx: &mut Context<Self>,
16235 ) {
16236 let snapshot = self.snapshot(window, cx).display_snapshot;
16237 let position = position.to_point(&snapshot.buffer_snapshot());
16238 let start = snapshot
16239 .buffer_snapshot()
16240 .clip_point(Point::new(position.row, 0), Bias::Left);
16241 let end = start + Point::new(1, 0);
16242 let start = snapshot.buffer_snapshot().anchor_before(start);
16243 let end = snapshot.buffer_snapshot().anchor_before(end);
16244
16245 self.highlight_rows::<T>(
16246 start..end,
16247 highlight_color
16248 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16249 Default::default(),
16250 cx,
16251 );
16252
16253 if self.buffer.read(cx).is_singleton() {
16254 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16255 }
16256 }
16257
16258 pub fn go_to_definition(
16259 &mut self,
16260 _: &GoToDefinition,
16261 window: &mut Window,
16262 cx: &mut Context<Self>,
16263 ) -> Task<Result<Navigated>> {
16264 let definition =
16265 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16266 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16267 cx.spawn_in(window, async move |editor, cx| {
16268 if definition.await? == Navigated::Yes {
16269 return Ok(Navigated::Yes);
16270 }
16271 match fallback_strategy {
16272 GoToDefinitionFallback::None => Ok(Navigated::No),
16273 GoToDefinitionFallback::FindAllReferences => {
16274 match editor.update_in(cx, |editor, window, cx| {
16275 editor.find_all_references(&FindAllReferences, window, cx)
16276 })? {
16277 Some(references) => references.await,
16278 None => Ok(Navigated::No),
16279 }
16280 }
16281 }
16282 })
16283 }
16284
16285 pub fn go_to_declaration(
16286 &mut self,
16287 _: &GoToDeclaration,
16288 window: &mut Window,
16289 cx: &mut Context<Self>,
16290 ) -> Task<Result<Navigated>> {
16291 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16292 }
16293
16294 pub fn go_to_declaration_split(
16295 &mut self,
16296 _: &GoToDeclaration,
16297 window: &mut Window,
16298 cx: &mut Context<Self>,
16299 ) -> Task<Result<Navigated>> {
16300 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16301 }
16302
16303 pub fn go_to_implementation(
16304 &mut self,
16305 _: &GoToImplementation,
16306 window: &mut Window,
16307 cx: &mut Context<Self>,
16308 ) -> Task<Result<Navigated>> {
16309 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16310 }
16311
16312 pub fn go_to_implementation_split(
16313 &mut self,
16314 _: &GoToImplementationSplit,
16315 window: &mut Window,
16316 cx: &mut Context<Self>,
16317 ) -> Task<Result<Navigated>> {
16318 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16319 }
16320
16321 pub fn go_to_type_definition(
16322 &mut self,
16323 _: &GoToTypeDefinition,
16324 window: &mut Window,
16325 cx: &mut Context<Self>,
16326 ) -> Task<Result<Navigated>> {
16327 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16328 }
16329
16330 pub fn go_to_definition_split(
16331 &mut self,
16332 _: &GoToDefinitionSplit,
16333 window: &mut Window,
16334 cx: &mut Context<Self>,
16335 ) -> Task<Result<Navigated>> {
16336 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16337 }
16338
16339 pub fn go_to_type_definition_split(
16340 &mut self,
16341 _: &GoToTypeDefinitionSplit,
16342 window: &mut Window,
16343 cx: &mut Context<Self>,
16344 ) -> Task<Result<Navigated>> {
16345 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16346 }
16347
16348 fn go_to_definition_of_kind(
16349 &mut self,
16350 kind: GotoDefinitionKind,
16351 split: bool,
16352 window: &mut Window,
16353 cx: &mut Context<Self>,
16354 ) -> Task<Result<Navigated>> {
16355 let Some(provider) = self.semantics_provider.clone() else {
16356 return Task::ready(Ok(Navigated::No));
16357 };
16358 let head = self
16359 .selections
16360 .newest::<usize>(&self.display_snapshot(cx))
16361 .head();
16362 let buffer = self.buffer.read(cx);
16363 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16364 return Task::ready(Ok(Navigated::No));
16365 };
16366 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16367 return Task::ready(Ok(Navigated::No));
16368 };
16369
16370 cx.spawn_in(window, async move |editor, cx| {
16371 let Some(definitions) = definitions.await? else {
16372 return Ok(Navigated::No);
16373 };
16374 let navigated = editor
16375 .update_in(cx, |editor, window, cx| {
16376 editor.navigate_to_hover_links(
16377 Some(kind),
16378 definitions
16379 .into_iter()
16380 .filter(|location| {
16381 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16382 })
16383 .map(HoverLink::Text)
16384 .collect::<Vec<_>>(),
16385 split,
16386 window,
16387 cx,
16388 )
16389 })?
16390 .await?;
16391 anyhow::Ok(navigated)
16392 })
16393 }
16394
16395 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16396 let selection = self.selections.newest_anchor();
16397 let head = selection.head();
16398 let tail = selection.tail();
16399
16400 let Some((buffer, start_position)) =
16401 self.buffer.read(cx).text_anchor_for_position(head, cx)
16402 else {
16403 return;
16404 };
16405
16406 let end_position = if head != tail {
16407 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16408 return;
16409 };
16410 Some(pos)
16411 } else {
16412 None
16413 };
16414
16415 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16416 let url = if let Some(end_pos) = end_position {
16417 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16418 } else {
16419 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16420 };
16421
16422 if let Some(url) = url {
16423 cx.update(|window, cx| {
16424 if parse_zed_link(&url, cx).is_some() {
16425 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16426 } else {
16427 cx.open_url(&url);
16428 }
16429 })?;
16430 }
16431
16432 anyhow::Ok(())
16433 });
16434
16435 url_finder.detach();
16436 }
16437
16438 pub fn open_selected_filename(
16439 &mut self,
16440 _: &OpenSelectedFilename,
16441 window: &mut Window,
16442 cx: &mut Context<Self>,
16443 ) {
16444 let Some(workspace) = self.workspace() else {
16445 return;
16446 };
16447
16448 let position = self.selections.newest_anchor().head();
16449
16450 let Some((buffer, buffer_position)) =
16451 self.buffer.read(cx).text_anchor_for_position(position, cx)
16452 else {
16453 return;
16454 };
16455
16456 let project = self.project.clone();
16457
16458 cx.spawn_in(window, async move |_, cx| {
16459 let result = find_file(&buffer, project, buffer_position, cx).await;
16460
16461 if let Some((_, path)) = result {
16462 workspace
16463 .update_in(cx, |workspace, window, cx| {
16464 workspace.open_resolved_path(path, window, cx)
16465 })?
16466 .await?;
16467 }
16468 anyhow::Ok(())
16469 })
16470 .detach();
16471 }
16472
16473 pub(crate) fn navigate_to_hover_links(
16474 &mut self,
16475 kind: Option<GotoDefinitionKind>,
16476 definitions: Vec<HoverLink>,
16477 split: bool,
16478 window: &mut Window,
16479 cx: &mut Context<Editor>,
16480 ) -> Task<Result<Navigated>> {
16481 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16482 let mut first_url_or_file = None;
16483 let definitions: Vec<_> = definitions
16484 .into_iter()
16485 .filter_map(|def| match def {
16486 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16487 HoverLink::InlayHint(lsp_location, server_id) => {
16488 let computation =
16489 self.compute_target_location(lsp_location, server_id, window, cx);
16490 Some(cx.background_spawn(computation))
16491 }
16492 HoverLink::Url(url) => {
16493 first_url_or_file = Some(Either::Left(url));
16494 None
16495 }
16496 HoverLink::File(path) => {
16497 first_url_or_file = Some(Either::Right(path));
16498 None
16499 }
16500 })
16501 .collect();
16502
16503 let workspace = self.workspace();
16504
16505 cx.spawn_in(window, async move |editor, cx| {
16506 let locations: Vec<Location> = future::join_all(definitions)
16507 .await
16508 .into_iter()
16509 .filter_map(|location| location.transpose())
16510 .collect::<Result<_>>()
16511 .context("location tasks")?;
16512 let mut locations = cx.update(|_, cx| {
16513 locations
16514 .into_iter()
16515 .map(|location| {
16516 let buffer = location.buffer.read(cx);
16517 (location.buffer, location.range.to_point(buffer))
16518 })
16519 .into_group_map()
16520 })?;
16521 let mut num_locations = 0;
16522 for ranges in locations.values_mut() {
16523 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16524 ranges.dedup();
16525 num_locations += ranges.len();
16526 }
16527
16528 if num_locations > 1 {
16529 let Some(workspace) = workspace else {
16530 return Ok(Navigated::No);
16531 };
16532
16533 let tab_kind = match kind {
16534 Some(GotoDefinitionKind::Implementation) => "Implementations",
16535 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16536 Some(GotoDefinitionKind::Declaration) => "Declarations",
16537 Some(GotoDefinitionKind::Type) => "Types",
16538 };
16539 let title = editor
16540 .update_in(cx, |_, _, cx| {
16541 let target = locations
16542 .iter()
16543 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16544 .map(|(buffer, location)| {
16545 buffer
16546 .read(cx)
16547 .text_for_range(location.clone())
16548 .collect::<String>()
16549 })
16550 .filter(|text| !text.contains('\n'))
16551 .unique()
16552 .take(3)
16553 .join(", ");
16554 if target.is_empty() {
16555 tab_kind.to_owned()
16556 } else {
16557 format!("{tab_kind} for {target}")
16558 }
16559 })
16560 .context("buffer title")?;
16561
16562 let opened = workspace
16563 .update_in(cx, |workspace, window, cx| {
16564 Self::open_locations_in_multibuffer(
16565 workspace,
16566 locations,
16567 title,
16568 split,
16569 MultibufferSelectionMode::First,
16570 window,
16571 cx,
16572 )
16573 })
16574 .is_ok();
16575
16576 anyhow::Ok(Navigated::from_bool(opened))
16577 } else if num_locations == 0 {
16578 // If there is one url or file, open it directly
16579 match first_url_or_file {
16580 Some(Either::Left(url)) => {
16581 cx.update(|_, cx| cx.open_url(&url))?;
16582 Ok(Navigated::Yes)
16583 }
16584 Some(Either::Right(path)) => {
16585 let Some(workspace) = workspace else {
16586 return Ok(Navigated::No);
16587 };
16588
16589 workspace
16590 .update_in(cx, |workspace, window, cx| {
16591 workspace.open_resolved_path(path, window, cx)
16592 })?
16593 .await?;
16594 Ok(Navigated::Yes)
16595 }
16596 None => Ok(Navigated::No),
16597 }
16598 } else {
16599 let Some(workspace) = workspace else {
16600 return Ok(Navigated::No);
16601 };
16602
16603 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16604 let target_range = target_ranges.first().unwrap().clone();
16605
16606 editor.update_in(cx, |editor, window, cx| {
16607 let range = target_range.to_point(target_buffer.read(cx));
16608 let range = editor.range_for_match(&range);
16609 let range = collapse_multiline_range(range);
16610
16611 if !split
16612 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16613 {
16614 editor.go_to_singleton_buffer_range(range, window, cx);
16615 } else {
16616 let pane = workspace.read(cx).active_pane().clone();
16617 window.defer(cx, move |window, cx| {
16618 let target_editor: Entity<Self> =
16619 workspace.update(cx, |workspace, cx| {
16620 let pane = if split {
16621 workspace.adjacent_pane(window, cx)
16622 } else {
16623 workspace.active_pane().clone()
16624 };
16625
16626 workspace.open_project_item(
16627 pane,
16628 target_buffer.clone(),
16629 true,
16630 true,
16631 window,
16632 cx,
16633 )
16634 });
16635 target_editor.update(cx, |target_editor, cx| {
16636 // When selecting a definition in a different buffer, disable the nav history
16637 // to avoid creating a history entry at the previous cursor location.
16638 pane.update(cx, |pane, _| pane.disable_history());
16639 target_editor.go_to_singleton_buffer_range(range, window, cx);
16640 pane.update(cx, |pane, _| pane.enable_history());
16641 });
16642 });
16643 }
16644 Navigated::Yes
16645 })
16646 }
16647 })
16648 }
16649
16650 fn compute_target_location(
16651 &self,
16652 lsp_location: lsp::Location,
16653 server_id: LanguageServerId,
16654 window: &mut Window,
16655 cx: &mut Context<Self>,
16656 ) -> Task<anyhow::Result<Option<Location>>> {
16657 let Some(project) = self.project.clone() else {
16658 return Task::ready(Ok(None));
16659 };
16660
16661 cx.spawn_in(window, async move |editor, cx| {
16662 let location_task = editor.update(cx, |_, cx| {
16663 project.update(cx, |project, cx| {
16664 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16665 })
16666 })?;
16667 let location = Some({
16668 let target_buffer_handle = location_task.await.context("open local buffer")?;
16669 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16670 let target_start = target_buffer
16671 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16672 let target_end = target_buffer
16673 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16674 target_buffer.anchor_after(target_start)
16675 ..target_buffer.anchor_before(target_end)
16676 })?;
16677 Location {
16678 buffer: target_buffer_handle,
16679 range,
16680 }
16681 });
16682 Ok(location)
16683 })
16684 }
16685
16686 fn go_to_next_reference(
16687 &mut self,
16688 _: &GoToNextReference,
16689 window: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) {
16692 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16693 if let Some(task) = task {
16694 task.detach();
16695 };
16696 }
16697
16698 fn go_to_prev_reference(
16699 &mut self,
16700 _: &GoToPreviousReference,
16701 window: &mut Window,
16702 cx: &mut Context<Self>,
16703 ) {
16704 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16705 if let Some(task) = task {
16706 task.detach();
16707 };
16708 }
16709
16710 pub fn go_to_reference_before_or_after_position(
16711 &mut self,
16712 direction: Direction,
16713 count: usize,
16714 window: &mut Window,
16715 cx: &mut Context<Self>,
16716 ) -> Option<Task<Result<()>>> {
16717 let selection = self.selections.newest_anchor();
16718 let head = selection.head();
16719
16720 let multi_buffer = self.buffer.read(cx);
16721
16722 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16723 let workspace = self.workspace()?;
16724 let project = workspace.read(cx).project().clone();
16725 let references =
16726 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16727 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16728 let Some(locations) = references.await? else {
16729 return Ok(());
16730 };
16731
16732 if locations.is_empty() {
16733 // totally normal - the cursor may be on something which is not
16734 // a symbol (e.g. a keyword)
16735 log::info!("no references found under cursor");
16736 return Ok(());
16737 }
16738
16739 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16740
16741 let multi_buffer_snapshot =
16742 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16743
16744 let (locations, current_location_index) =
16745 multi_buffer.update(cx, |multi_buffer, cx| {
16746 let mut locations = locations
16747 .into_iter()
16748 .filter_map(|loc| {
16749 let start = multi_buffer.buffer_anchor_to_anchor(
16750 &loc.buffer,
16751 loc.range.start,
16752 cx,
16753 )?;
16754 let end = multi_buffer.buffer_anchor_to_anchor(
16755 &loc.buffer,
16756 loc.range.end,
16757 cx,
16758 )?;
16759 Some(start..end)
16760 })
16761 .collect::<Vec<_>>();
16762
16763 // There is an O(n) implementation, but given this list will be
16764 // small (usually <100 items), the extra O(log(n)) factor isn't
16765 // worth the (surprisingly large amount of) extra complexity.
16766 locations
16767 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16768
16769 let head_offset = head.to_offset(&multi_buffer_snapshot);
16770
16771 let current_location_index = locations.iter().position(|loc| {
16772 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16773 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16774 });
16775
16776 (locations, current_location_index)
16777 })?;
16778
16779 let Some(current_location_index) = current_location_index else {
16780 // This indicates something has gone wrong, because we already
16781 // handle the "no references" case above
16782 log::error!(
16783 "failed to find current reference under cursor. Total references: {}",
16784 locations.len()
16785 );
16786 return Ok(());
16787 };
16788
16789 let destination_location_index = match direction {
16790 Direction::Next => (current_location_index + count) % locations.len(),
16791 Direction::Prev => {
16792 (current_location_index + locations.len() - count % locations.len())
16793 % locations.len()
16794 }
16795 };
16796
16797 // TODO(cameron): is this needed?
16798 // the thinking is to avoid "jumping to the current location" (avoid
16799 // polluting "jumplist" in vim terms)
16800 if current_location_index == destination_location_index {
16801 return Ok(());
16802 }
16803
16804 let Range { start, end } = locations[destination_location_index];
16805
16806 editor.update_in(cx, |editor, window, cx| {
16807 let effects = SelectionEffects::default();
16808
16809 editor.unfold_ranges(&[start..end], false, false, cx);
16810 editor.change_selections(effects, window, cx, |s| {
16811 s.select_ranges([start..start]);
16812 });
16813 })?;
16814
16815 Ok(())
16816 }))
16817 }
16818
16819 pub fn find_all_references(
16820 &mut self,
16821 _: &FindAllReferences,
16822 window: &mut Window,
16823 cx: &mut Context<Self>,
16824 ) -> Option<Task<Result<Navigated>>> {
16825 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16826 let multi_buffer = self.buffer.read(cx);
16827 let head = selection.head();
16828
16829 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16830 let head_anchor = multi_buffer_snapshot.anchor_at(
16831 head,
16832 if head < selection.tail() {
16833 Bias::Right
16834 } else {
16835 Bias::Left
16836 },
16837 );
16838
16839 match self
16840 .find_all_references_task_sources
16841 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16842 {
16843 Ok(_) => {
16844 log::info!(
16845 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16846 );
16847 return None;
16848 }
16849 Err(i) => {
16850 self.find_all_references_task_sources.insert(i, head_anchor);
16851 }
16852 }
16853
16854 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16855 let workspace = self.workspace()?;
16856 let project = workspace.read(cx).project().clone();
16857 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16858 Some(cx.spawn_in(window, async move |editor, cx| {
16859 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16860 if let Ok(i) = editor
16861 .find_all_references_task_sources
16862 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16863 {
16864 editor.find_all_references_task_sources.remove(i);
16865 }
16866 });
16867
16868 let Some(locations) = references.await? else {
16869 return anyhow::Ok(Navigated::No);
16870 };
16871 let mut locations = cx.update(|_, cx| {
16872 locations
16873 .into_iter()
16874 .map(|location| {
16875 let buffer = location.buffer.read(cx);
16876 (location.buffer, location.range.to_point(buffer))
16877 })
16878 .into_group_map()
16879 })?;
16880 if locations.is_empty() {
16881 return anyhow::Ok(Navigated::No);
16882 }
16883 for ranges in locations.values_mut() {
16884 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16885 ranges.dedup();
16886 }
16887
16888 workspace.update_in(cx, |workspace, window, cx| {
16889 let target = locations
16890 .iter()
16891 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16892 .map(|(buffer, location)| {
16893 buffer
16894 .read(cx)
16895 .text_for_range(location.clone())
16896 .collect::<String>()
16897 })
16898 .filter(|text| !text.contains('\n'))
16899 .unique()
16900 .take(3)
16901 .join(", ");
16902 let title = if target.is_empty() {
16903 "References".to_owned()
16904 } else {
16905 format!("References to {target}")
16906 };
16907 Self::open_locations_in_multibuffer(
16908 workspace,
16909 locations,
16910 title,
16911 false,
16912 MultibufferSelectionMode::First,
16913 window,
16914 cx,
16915 );
16916 Navigated::Yes
16917 })
16918 }))
16919 }
16920
16921 /// Opens a multibuffer with the given project locations in it
16922 pub fn open_locations_in_multibuffer(
16923 workspace: &mut Workspace,
16924 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16925 title: String,
16926 split: bool,
16927 multibuffer_selection_mode: MultibufferSelectionMode,
16928 window: &mut Window,
16929 cx: &mut Context<Workspace>,
16930 ) {
16931 if locations.is_empty() {
16932 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16933 return;
16934 }
16935
16936 let capability = workspace.project().read(cx).capability();
16937 let mut ranges = <Vec<Range<Anchor>>>::new();
16938
16939 // a key to find existing multibuffer editors with the same set of locations
16940 // to prevent us from opening more and more multibuffer tabs for searches and the like
16941 let mut key = (title.clone(), vec![]);
16942 let excerpt_buffer = cx.new(|cx| {
16943 let key = &mut key.1;
16944 let mut multibuffer = MultiBuffer::new(capability);
16945 for (buffer, mut ranges_for_buffer) in locations {
16946 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16947 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16948 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16949 PathKey::for_buffer(&buffer, cx),
16950 buffer.clone(),
16951 ranges_for_buffer,
16952 multibuffer_context_lines(cx),
16953 cx,
16954 );
16955 ranges.extend(new_ranges)
16956 }
16957
16958 multibuffer.with_title(title)
16959 });
16960 let existing = workspace.active_pane().update(cx, |pane, cx| {
16961 pane.items()
16962 .filter_map(|item| item.downcast::<Editor>())
16963 .find(|editor| {
16964 editor
16965 .read(cx)
16966 .lookup_key
16967 .as_ref()
16968 .and_then(|it| {
16969 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16970 })
16971 .is_some_and(|it| *it == key)
16972 })
16973 });
16974 let editor = existing.unwrap_or_else(|| {
16975 cx.new(|cx| {
16976 let mut editor = Editor::for_multibuffer(
16977 excerpt_buffer,
16978 Some(workspace.project().clone()),
16979 window,
16980 cx,
16981 );
16982 editor.lookup_key = Some(Box::new(key));
16983 editor
16984 })
16985 });
16986 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
16987 MultibufferSelectionMode::First => {
16988 if let Some(first_range) = ranges.first() {
16989 editor.change_selections(
16990 SelectionEffects::no_scroll(),
16991 window,
16992 cx,
16993 |selections| {
16994 selections.clear_disjoint();
16995 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
16996 },
16997 );
16998 }
16999 editor.highlight_background::<Self>(
17000 &ranges,
17001 |theme| theme.colors().editor_highlighted_line_background,
17002 cx,
17003 );
17004 }
17005 MultibufferSelectionMode::All => {
17006 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17007 selections.clear_disjoint();
17008 selections.select_anchor_ranges(ranges);
17009 });
17010 }
17011 });
17012
17013 let item = Box::new(editor);
17014 let item_id = item.item_id();
17015
17016 if split {
17017 let pane = workspace.adjacent_pane(window, cx);
17018 workspace.add_item(pane, item, None, true, true, window, cx);
17019 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17020 let (preview_item_id, preview_item_idx) =
17021 workspace.active_pane().read_with(cx, |pane, _| {
17022 (pane.preview_item_id(), pane.preview_item_idx())
17023 });
17024
17025 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17026
17027 if let Some(preview_item_id) = preview_item_id {
17028 workspace.active_pane().update(cx, |pane, cx| {
17029 pane.remove_item(preview_item_id, false, false, window, cx);
17030 });
17031 }
17032 } else {
17033 workspace.add_item_to_active_pane(item, None, true, window, cx);
17034 }
17035 workspace.active_pane().update(cx, |pane, cx| {
17036 pane.set_preview_item_id(Some(item_id), cx);
17037 });
17038 }
17039
17040 pub fn rename(
17041 &mut self,
17042 _: &Rename,
17043 window: &mut Window,
17044 cx: &mut Context<Self>,
17045 ) -> Option<Task<Result<()>>> {
17046 use language::ToOffset as _;
17047
17048 let provider = self.semantics_provider.clone()?;
17049 let selection = self.selections.newest_anchor().clone();
17050 let (cursor_buffer, cursor_buffer_position) = self
17051 .buffer
17052 .read(cx)
17053 .text_anchor_for_position(selection.head(), cx)?;
17054 let (tail_buffer, cursor_buffer_position_end) = self
17055 .buffer
17056 .read(cx)
17057 .text_anchor_for_position(selection.tail(), cx)?;
17058 if tail_buffer != cursor_buffer {
17059 return None;
17060 }
17061
17062 let snapshot = cursor_buffer.read(cx).snapshot();
17063 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17064 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17065 let prepare_rename = provider
17066 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17067 .unwrap_or_else(|| Task::ready(Ok(None)));
17068 drop(snapshot);
17069
17070 Some(cx.spawn_in(window, async move |this, cx| {
17071 let rename_range = if let Some(range) = prepare_rename.await? {
17072 Some(range)
17073 } else {
17074 this.update(cx, |this, cx| {
17075 let buffer = this.buffer.read(cx).snapshot(cx);
17076 let mut buffer_highlights = this
17077 .document_highlights_for_position(selection.head(), &buffer)
17078 .filter(|highlight| {
17079 highlight.start.excerpt_id == selection.head().excerpt_id
17080 && highlight.end.excerpt_id == selection.head().excerpt_id
17081 });
17082 buffer_highlights
17083 .next()
17084 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17085 })?
17086 };
17087 if let Some(rename_range) = rename_range {
17088 this.update_in(cx, |this, window, cx| {
17089 let snapshot = cursor_buffer.read(cx).snapshot();
17090 let rename_buffer_range = rename_range.to_offset(&snapshot);
17091 let cursor_offset_in_rename_range =
17092 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17093 let cursor_offset_in_rename_range_end =
17094 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17095
17096 this.take_rename(false, window, cx);
17097 let buffer = this.buffer.read(cx).read(cx);
17098 let cursor_offset = selection.head().to_offset(&buffer);
17099 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17100 let rename_end = rename_start + rename_buffer_range.len();
17101 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17102 let mut old_highlight_id = None;
17103 let old_name: Arc<str> = buffer
17104 .chunks(rename_start..rename_end, true)
17105 .map(|chunk| {
17106 if old_highlight_id.is_none() {
17107 old_highlight_id = chunk.syntax_highlight_id;
17108 }
17109 chunk.text
17110 })
17111 .collect::<String>()
17112 .into();
17113
17114 drop(buffer);
17115
17116 // Position the selection in the rename editor so that it matches the current selection.
17117 this.show_local_selections = false;
17118 let rename_editor = cx.new(|cx| {
17119 let mut editor = Editor::single_line(window, cx);
17120 editor.buffer.update(cx, |buffer, cx| {
17121 buffer.edit([(0..0, old_name.clone())], None, cx)
17122 });
17123 let rename_selection_range = match cursor_offset_in_rename_range
17124 .cmp(&cursor_offset_in_rename_range_end)
17125 {
17126 Ordering::Equal => {
17127 editor.select_all(&SelectAll, window, cx);
17128 return editor;
17129 }
17130 Ordering::Less => {
17131 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17132 }
17133 Ordering::Greater => {
17134 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17135 }
17136 };
17137 if rename_selection_range.end > old_name.len() {
17138 editor.select_all(&SelectAll, window, cx);
17139 } else {
17140 editor.change_selections(Default::default(), window, cx, |s| {
17141 s.select_ranges([rename_selection_range]);
17142 });
17143 }
17144 editor
17145 });
17146 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17147 if e == &EditorEvent::Focused {
17148 cx.emit(EditorEvent::FocusedIn)
17149 }
17150 })
17151 .detach();
17152
17153 let write_highlights =
17154 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17155 let read_highlights =
17156 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17157 let ranges = write_highlights
17158 .iter()
17159 .flat_map(|(_, ranges)| ranges.iter())
17160 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17161 .cloned()
17162 .collect();
17163
17164 this.highlight_text::<Rename>(
17165 ranges,
17166 HighlightStyle {
17167 fade_out: Some(0.6),
17168 ..Default::default()
17169 },
17170 cx,
17171 );
17172 let rename_focus_handle = rename_editor.focus_handle(cx);
17173 window.focus(&rename_focus_handle);
17174 let block_id = this.insert_blocks(
17175 [BlockProperties {
17176 style: BlockStyle::Flex,
17177 placement: BlockPlacement::Below(range.start),
17178 height: Some(1),
17179 render: Arc::new({
17180 let rename_editor = rename_editor.clone();
17181 move |cx: &mut BlockContext| {
17182 let mut text_style = cx.editor_style.text.clone();
17183 if let Some(highlight_style) = old_highlight_id
17184 .and_then(|h| h.style(&cx.editor_style.syntax))
17185 {
17186 text_style = text_style.highlight(highlight_style);
17187 }
17188 div()
17189 .block_mouse_except_scroll()
17190 .pl(cx.anchor_x)
17191 .child(EditorElement::new(
17192 &rename_editor,
17193 EditorStyle {
17194 background: cx.theme().system().transparent,
17195 local_player: cx.editor_style.local_player,
17196 text: text_style,
17197 scrollbar_width: cx.editor_style.scrollbar_width,
17198 syntax: cx.editor_style.syntax.clone(),
17199 status: cx.editor_style.status.clone(),
17200 inlay_hints_style: HighlightStyle {
17201 font_weight: Some(FontWeight::BOLD),
17202 ..make_inlay_hints_style(cx.app)
17203 },
17204 edit_prediction_styles: make_suggestion_styles(
17205 cx.app,
17206 ),
17207 ..EditorStyle::default()
17208 },
17209 ))
17210 .into_any_element()
17211 }
17212 }),
17213 priority: 0,
17214 }],
17215 Some(Autoscroll::fit()),
17216 cx,
17217 )[0];
17218 this.pending_rename = Some(RenameState {
17219 range,
17220 old_name,
17221 editor: rename_editor,
17222 block_id,
17223 });
17224 })?;
17225 }
17226
17227 Ok(())
17228 }))
17229 }
17230
17231 pub fn confirm_rename(
17232 &mut self,
17233 _: &ConfirmRename,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) -> Option<Task<Result<()>>> {
17237 let rename = self.take_rename(false, window, cx)?;
17238 let workspace = self.workspace()?.downgrade();
17239 let (buffer, start) = self
17240 .buffer
17241 .read(cx)
17242 .text_anchor_for_position(rename.range.start, cx)?;
17243 let (end_buffer, _) = self
17244 .buffer
17245 .read(cx)
17246 .text_anchor_for_position(rename.range.end, cx)?;
17247 if buffer != end_buffer {
17248 return None;
17249 }
17250
17251 let old_name = rename.old_name;
17252 let new_name = rename.editor.read(cx).text(cx);
17253
17254 let rename = self.semantics_provider.as_ref()?.perform_rename(
17255 &buffer,
17256 start,
17257 new_name.clone(),
17258 cx,
17259 )?;
17260
17261 Some(cx.spawn_in(window, async move |editor, cx| {
17262 let project_transaction = rename.await?;
17263 Self::open_project_transaction(
17264 &editor,
17265 workspace,
17266 project_transaction,
17267 format!("Rename: {} → {}", old_name, new_name),
17268 cx,
17269 )
17270 .await?;
17271
17272 editor.update(cx, |editor, cx| {
17273 editor.refresh_document_highlights(cx);
17274 })?;
17275 Ok(())
17276 }))
17277 }
17278
17279 fn take_rename(
17280 &mut self,
17281 moving_cursor: bool,
17282 window: &mut Window,
17283 cx: &mut Context<Self>,
17284 ) -> Option<RenameState> {
17285 let rename = self.pending_rename.take()?;
17286 if rename.editor.focus_handle(cx).is_focused(window) {
17287 window.focus(&self.focus_handle);
17288 }
17289
17290 self.remove_blocks(
17291 [rename.block_id].into_iter().collect(),
17292 Some(Autoscroll::fit()),
17293 cx,
17294 );
17295 self.clear_highlights::<Rename>(cx);
17296 self.show_local_selections = true;
17297
17298 if moving_cursor {
17299 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17300 editor
17301 .selections
17302 .newest::<usize>(&editor.display_snapshot(cx))
17303 .head()
17304 });
17305
17306 // Update the selection to match the position of the selection inside
17307 // the rename editor.
17308 let snapshot = self.buffer.read(cx).read(cx);
17309 let rename_range = rename.range.to_offset(&snapshot);
17310 let cursor_in_editor = snapshot
17311 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17312 .min(rename_range.end);
17313 drop(snapshot);
17314
17315 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17316 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17317 });
17318 } else {
17319 self.refresh_document_highlights(cx);
17320 }
17321
17322 Some(rename)
17323 }
17324
17325 pub fn pending_rename(&self) -> Option<&RenameState> {
17326 self.pending_rename.as_ref()
17327 }
17328
17329 fn format(
17330 &mut self,
17331 _: &Format,
17332 window: &mut Window,
17333 cx: &mut Context<Self>,
17334 ) -> Option<Task<Result<()>>> {
17335 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17336
17337 let project = match &self.project {
17338 Some(project) => project.clone(),
17339 None => return None,
17340 };
17341
17342 Some(self.perform_format(
17343 project,
17344 FormatTrigger::Manual,
17345 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17346 window,
17347 cx,
17348 ))
17349 }
17350
17351 fn format_selections(
17352 &mut self,
17353 _: &FormatSelections,
17354 window: &mut Window,
17355 cx: &mut Context<Self>,
17356 ) -> Option<Task<Result<()>>> {
17357 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17358
17359 let project = match &self.project {
17360 Some(project) => project.clone(),
17361 None => return None,
17362 };
17363
17364 let ranges = self
17365 .selections
17366 .all_adjusted(&self.display_snapshot(cx))
17367 .into_iter()
17368 .map(|selection| selection.range())
17369 .collect_vec();
17370
17371 Some(self.perform_format(
17372 project,
17373 FormatTrigger::Manual,
17374 FormatTarget::Ranges(ranges),
17375 window,
17376 cx,
17377 ))
17378 }
17379
17380 fn perform_format(
17381 &mut self,
17382 project: Entity<Project>,
17383 trigger: FormatTrigger,
17384 target: FormatTarget,
17385 window: &mut Window,
17386 cx: &mut Context<Self>,
17387 ) -> Task<Result<()>> {
17388 let buffer = self.buffer.clone();
17389 let (buffers, target) = match target {
17390 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17391 FormatTarget::Ranges(selection_ranges) => {
17392 let multi_buffer = buffer.read(cx);
17393 let snapshot = multi_buffer.read(cx);
17394 let mut buffers = HashSet::default();
17395 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17396 BTreeMap::new();
17397 for selection_range in selection_ranges {
17398 for (buffer, buffer_range, _) in
17399 snapshot.range_to_buffer_ranges(selection_range)
17400 {
17401 let buffer_id = buffer.remote_id();
17402 let start = buffer.anchor_before(buffer_range.start);
17403 let end = buffer.anchor_after(buffer_range.end);
17404 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17405 buffer_id_to_ranges
17406 .entry(buffer_id)
17407 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17408 .or_insert_with(|| vec![start..end]);
17409 }
17410 }
17411 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17412 }
17413 };
17414
17415 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17416 let selections_prev = transaction_id_prev
17417 .and_then(|transaction_id_prev| {
17418 // default to selections as they were after the last edit, if we have them,
17419 // instead of how they are now.
17420 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17421 // will take you back to where you made the last edit, instead of staying where you scrolled
17422 self.selection_history
17423 .transaction(transaction_id_prev)
17424 .map(|t| t.0.clone())
17425 })
17426 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17427
17428 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17429 let format = project.update(cx, |project, cx| {
17430 project.format(buffers, target, true, trigger, cx)
17431 });
17432
17433 cx.spawn_in(window, async move |editor, cx| {
17434 let transaction = futures::select_biased! {
17435 transaction = format.log_err().fuse() => transaction,
17436 () = timeout => {
17437 log::warn!("timed out waiting for formatting");
17438 None
17439 }
17440 };
17441
17442 buffer
17443 .update(cx, |buffer, cx| {
17444 if let Some(transaction) = transaction
17445 && !buffer.is_singleton()
17446 {
17447 buffer.push_transaction(&transaction.0, cx);
17448 }
17449 cx.notify();
17450 })
17451 .ok();
17452
17453 if let Some(transaction_id_now) =
17454 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17455 {
17456 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17457 if has_new_transaction {
17458 _ = editor.update(cx, |editor, _| {
17459 editor
17460 .selection_history
17461 .insert_transaction(transaction_id_now, selections_prev);
17462 });
17463 }
17464 }
17465
17466 Ok(())
17467 })
17468 }
17469
17470 fn organize_imports(
17471 &mut self,
17472 _: &OrganizeImports,
17473 window: &mut Window,
17474 cx: &mut Context<Self>,
17475 ) -> Option<Task<Result<()>>> {
17476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17477 let project = match &self.project {
17478 Some(project) => project.clone(),
17479 None => return None,
17480 };
17481 Some(self.perform_code_action_kind(
17482 project,
17483 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17484 window,
17485 cx,
17486 ))
17487 }
17488
17489 fn perform_code_action_kind(
17490 &mut self,
17491 project: Entity<Project>,
17492 kind: CodeActionKind,
17493 window: &mut Window,
17494 cx: &mut Context<Self>,
17495 ) -> Task<Result<()>> {
17496 let buffer = self.buffer.clone();
17497 let buffers = buffer.read(cx).all_buffers();
17498 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17499 let apply_action = project.update(cx, |project, cx| {
17500 project.apply_code_action_kind(buffers, kind, true, cx)
17501 });
17502 cx.spawn_in(window, async move |_, cx| {
17503 let transaction = futures::select_biased! {
17504 () = timeout => {
17505 log::warn!("timed out waiting for executing code action");
17506 None
17507 }
17508 transaction = apply_action.log_err().fuse() => transaction,
17509 };
17510 buffer
17511 .update(cx, |buffer, cx| {
17512 // check if we need this
17513 if let Some(transaction) = transaction
17514 && !buffer.is_singleton()
17515 {
17516 buffer.push_transaction(&transaction.0, cx);
17517 }
17518 cx.notify();
17519 })
17520 .ok();
17521 Ok(())
17522 })
17523 }
17524
17525 pub fn restart_language_server(
17526 &mut self,
17527 _: &RestartLanguageServer,
17528 _: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 if let Some(project) = self.project.clone() {
17532 self.buffer.update(cx, |multi_buffer, cx| {
17533 project.update(cx, |project, cx| {
17534 project.restart_language_servers_for_buffers(
17535 multi_buffer.all_buffers().into_iter().collect(),
17536 HashSet::default(),
17537 cx,
17538 );
17539 });
17540 })
17541 }
17542 }
17543
17544 pub fn stop_language_server(
17545 &mut self,
17546 _: &StopLanguageServer,
17547 _: &mut Window,
17548 cx: &mut Context<Self>,
17549 ) {
17550 if let Some(project) = self.project.clone() {
17551 self.buffer.update(cx, |multi_buffer, cx| {
17552 project.update(cx, |project, cx| {
17553 project.stop_language_servers_for_buffers(
17554 multi_buffer.all_buffers().into_iter().collect(),
17555 HashSet::default(),
17556 cx,
17557 );
17558 });
17559 });
17560 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17561 }
17562 }
17563
17564 fn cancel_language_server_work(
17565 workspace: &mut Workspace,
17566 _: &actions::CancelLanguageServerWork,
17567 _: &mut Window,
17568 cx: &mut Context<Workspace>,
17569 ) {
17570 let project = workspace.project();
17571 let buffers = workspace
17572 .active_item(cx)
17573 .and_then(|item| item.act_as::<Editor>(cx))
17574 .map_or(HashSet::default(), |editor| {
17575 editor.read(cx).buffer.read(cx).all_buffers()
17576 });
17577 project.update(cx, |project, cx| {
17578 project.cancel_language_server_work_for_buffers(buffers, cx);
17579 });
17580 }
17581
17582 fn show_character_palette(
17583 &mut self,
17584 _: &ShowCharacterPalette,
17585 window: &mut Window,
17586 _: &mut Context<Self>,
17587 ) {
17588 window.show_character_palette();
17589 }
17590
17591 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17592 if !self.diagnostics_enabled() {
17593 return;
17594 }
17595
17596 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17597 let buffer = self.buffer.read(cx).snapshot(cx);
17598 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17599 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17600 let is_valid = buffer
17601 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17602 .any(|entry| {
17603 entry.diagnostic.is_primary
17604 && !entry.range.is_empty()
17605 && entry.range.start == primary_range_start
17606 && entry.diagnostic.message == active_diagnostics.active_message
17607 });
17608
17609 if !is_valid {
17610 self.dismiss_diagnostics(cx);
17611 }
17612 }
17613 }
17614
17615 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17616 match &self.active_diagnostics {
17617 ActiveDiagnostic::Group(group) => Some(group),
17618 _ => None,
17619 }
17620 }
17621
17622 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17623 if !self.diagnostics_enabled() {
17624 return;
17625 }
17626 self.dismiss_diagnostics(cx);
17627 self.active_diagnostics = ActiveDiagnostic::All;
17628 }
17629
17630 fn activate_diagnostics(
17631 &mut self,
17632 buffer_id: BufferId,
17633 diagnostic: DiagnosticEntryRef<'_, usize>,
17634 window: &mut Window,
17635 cx: &mut Context<Self>,
17636 ) {
17637 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17638 return;
17639 }
17640 self.dismiss_diagnostics(cx);
17641 let snapshot = self.snapshot(window, cx);
17642 let buffer = self.buffer.read(cx).snapshot(cx);
17643 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17644 return;
17645 };
17646
17647 let diagnostic_group = buffer
17648 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17649 .collect::<Vec<_>>();
17650
17651 let blocks =
17652 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17653
17654 let blocks = self.display_map.update(cx, |display_map, cx| {
17655 display_map.insert_blocks(blocks, cx).into_iter().collect()
17656 });
17657 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17658 active_range: buffer.anchor_before(diagnostic.range.start)
17659 ..buffer.anchor_after(diagnostic.range.end),
17660 active_message: diagnostic.diagnostic.message.clone(),
17661 group_id: diagnostic.diagnostic.group_id,
17662 blocks,
17663 });
17664 cx.notify();
17665 }
17666
17667 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17668 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17669 return;
17670 };
17671
17672 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17673 if let ActiveDiagnostic::Group(group) = prev {
17674 self.display_map.update(cx, |display_map, cx| {
17675 display_map.remove_blocks(group.blocks, cx);
17676 });
17677 cx.notify();
17678 }
17679 }
17680
17681 /// Disable inline diagnostics rendering for this editor.
17682 pub fn disable_inline_diagnostics(&mut self) {
17683 self.inline_diagnostics_enabled = false;
17684 self.inline_diagnostics_update = Task::ready(());
17685 self.inline_diagnostics.clear();
17686 }
17687
17688 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17689 self.diagnostics_enabled = false;
17690 self.dismiss_diagnostics(cx);
17691 self.inline_diagnostics_update = Task::ready(());
17692 self.inline_diagnostics.clear();
17693 }
17694
17695 pub fn disable_word_completions(&mut self) {
17696 self.word_completions_enabled = false;
17697 }
17698
17699 pub fn diagnostics_enabled(&self) -> bool {
17700 self.diagnostics_enabled && self.mode.is_full()
17701 }
17702
17703 pub fn inline_diagnostics_enabled(&self) -> bool {
17704 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17705 }
17706
17707 pub fn show_inline_diagnostics(&self) -> bool {
17708 self.show_inline_diagnostics
17709 }
17710
17711 pub fn toggle_inline_diagnostics(
17712 &mut self,
17713 _: &ToggleInlineDiagnostics,
17714 window: &mut Window,
17715 cx: &mut Context<Editor>,
17716 ) {
17717 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17718 self.refresh_inline_diagnostics(false, window, cx);
17719 }
17720
17721 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17722 self.diagnostics_max_severity = severity;
17723 self.display_map.update(cx, |display_map, _| {
17724 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17725 });
17726 }
17727
17728 pub fn toggle_diagnostics(
17729 &mut self,
17730 _: &ToggleDiagnostics,
17731 window: &mut Window,
17732 cx: &mut Context<Editor>,
17733 ) {
17734 if !self.diagnostics_enabled() {
17735 return;
17736 }
17737
17738 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17739 EditorSettings::get_global(cx)
17740 .diagnostics_max_severity
17741 .filter(|severity| severity != &DiagnosticSeverity::Off)
17742 .unwrap_or(DiagnosticSeverity::Hint)
17743 } else {
17744 DiagnosticSeverity::Off
17745 };
17746 self.set_max_diagnostics_severity(new_severity, cx);
17747 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17748 self.active_diagnostics = ActiveDiagnostic::None;
17749 self.inline_diagnostics_update = Task::ready(());
17750 self.inline_diagnostics.clear();
17751 } else {
17752 self.refresh_inline_diagnostics(false, window, cx);
17753 }
17754
17755 cx.notify();
17756 }
17757
17758 pub fn toggle_minimap(
17759 &mut self,
17760 _: &ToggleMinimap,
17761 window: &mut Window,
17762 cx: &mut Context<Editor>,
17763 ) {
17764 if self.supports_minimap(cx) {
17765 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17766 }
17767 }
17768
17769 fn refresh_inline_diagnostics(
17770 &mut self,
17771 debounce: bool,
17772 window: &mut Window,
17773 cx: &mut Context<Self>,
17774 ) {
17775 let max_severity = ProjectSettings::get_global(cx)
17776 .diagnostics
17777 .inline
17778 .max_severity
17779 .unwrap_or(self.diagnostics_max_severity);
17780
17781 if !self.inline_diagnostics_enabled()
17782 || !self.show_inline_diagnostics
17783 || max_severity == DiagnosticSeverity::Off
17784 {
17785 self.inline_diagnostics_update = Task::ready(());
17786 self.inline_diagnostics.clear();
17787 return;
17788 }
17789
17790 let debounce_ms = ProjectSettings::get_global(cx)
17791 .diagnostics
17792 .inline
17793 .update_debounce_ms;
17794 let debounce = if debounce && debounce_ms > 0 {
17795 Some(Duration::from_millis(debounce_ms))
17796 } else {
17797 None
17798 };
17799 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17800 if let Some(debounce) = debounce {
17801 cx.background_executor().timer(debounce).await;
17802 }
17803 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17804 editor
17805 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17806 .ok()
17807 }) else {
17808 return;
17809 };
17810
17811 let new_inline_diagnostics = cx
17812 .background_spawn(async move {
17813 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17814 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17815 let message = diagnostic_entry
17816 .diagnostic
17817 .message
17818 .split_once('\n')
17819 .map(|(line, _)| line)
17820 .map(SharedString::new)
17821 .unwrap_or_else(|| {
17822 SharedString::new(&*diagnostic_entry.diagnostic.message)
17823 });
17824 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17825 let (Ok(i) | Err(i)) = inline_diagnostics
17826 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17827 inline_diagnostics.insert(
17828 i,
17829 (
17830 start_anchor,
17831 InlineDiagnostic {
17832 message,
17833 group_id: diagnostic_entry.diagnostic.group_id,
17834 start: diagnostic_entry.range.start.to_point(&snapshot),
17835 is_primary: diagnostic_entry.diagnostic.is_primary,
17836 severity: diagnostic_entry.diagnostic.severity,
17837 },
17838 ),
17839 );
17840 }
17841 inline_diagnostics
17842 })
17843 .await;
17844
17845 editor
17846 .update(cx, |editor, cx| {
17847 editor.inline_diagnostics = new_inline_diagnostics;
17848 cx.notify();
17849 })
17850 .ok();
17851 });
17852 }
17853
17854 fn pull_diagnostics(
17855 &mut self,
17856 buffer_id: Option<BufferId>,
17857 window: &Window,
17858 cx: &mut Context<Self>,
17859 ) -> Option<()> {
17860 if self.ignore_lsp_data() {
17861 return None;
17862 }
17863 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17864 .diagnostics
17865 .lsp_pull_diagnostics;
17866 if !pull_diagnostics_settings.enabled {
17867 return None;
17868 }
17869 let project = self.project()?.downgrade();
17870 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17871 let mut buffers = self.buffer.read(cx).all_buffers();
17872 buffers.retain(|buffer| {
17873 let buffer_id_to_retain = buffer.read(cx).remote_id();
17874 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17875 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17876 });
17877 if buffers.is_empty() {
17878 self.pull_diagnostics_task = Task::ready(());
17879 return None;
17880 }
17881
17882 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17883 cx.background_executor().timer(debounce).await;
17884
17885 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17886 buffers
17887 .into_iter()
17888 .filter_map(|buffer| {
17889 project
17890 .update(cx, |project, cx| {
17891 project.lsp_store().update(cx, |lsp_store, cx| {
17892 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17893 })
17894 })
17895 .ok()
17896 })
17897 .collect::<FuturesUnordered<_>>()
17898 }) else {
17899 return;
17900 };
17901
17902 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17903 match pull_task {
17904 Ok(()) => {
17905 if editor
17906 .update_in(cx, |editor, window, cx| {
17907 editor.update_diagnostics_state(window, cx);
17908 })
17909 .is_err()
17910 {
17911 return;
17912 }
17913 }
17914 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17915 }
17916 }
17917 });
17918
17919 Some(())
17920 }
17921
17922 pub fn set_selections_from_remote(
17923 &mut self,
17924 selections: Vec<Selection<Anchor>>,
17925 pending_selection: Option<Selection<Anchor>>,
17926 window: &mut Window,
17927 cx: &mut Context<Self>,
17928 ) {
17929 let old_cursor_position = self.selections.newest_anchor().head();
17930 self.selections.change_with(cx, |s| {
17931 s.select_anchors(selections);
17932 if let Some(pending_selection) = pending_selection {
17933 s.set_pending(pending_selection, SelectMode::Character);
17934 } else {
17935 s.clear_pending();
17936 }
17937 });
17938 self.selections_did_change(
17939 false,
17940 &old_cursor_position,
17941 SelectionEffects::default(),
17942 window,
17943 cx,
17944 );
17945 }
17946
17947 pub fn transact(
17948 &mut self,
17949 window: &mut Window,
17950 cx: &mut Context<Self>,
17951 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17952 ) -> Option<TransactionId> {
17953 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17954 this.start_transaction_at(Instant::now(), window, cx);
17955 update(this, window, cx);
17956 this.end_transaction_at(Instant::now(), cx)
17957 })
17958 }
17959
17960 pub fn start_transaction_at(
17961 &mut self,
17962 now: Instant,
17963 window: &mut Window,
17964 cx: &mut Context<Self>,
17965 ) -> Option<TransactionId> {
17966 self.end_selection(window, cx);
17967 if let Some(tx_id) = self
17968 .buffer
17969 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17970 {
17971 self.selection_history
17972 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17973 cx.emit(EditorEvent::TransactionBegun {
17974 transaction_id: tx_id,
17975 });
17976 Some(tx_id)
17977 } else {
17978 None
17979 }
17980 }
17981
17982 pub fn end_transaction_at(
17983 &mut self,
17984 now: Instant,
17985 cx: &mut Context<Self>,
17986 ) -> Option<TransactionId> {
17987 if let Some(transaction_id) = self
17988 .buffer
17989 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17990 {
17991 if let Some((_, end_selections)) =
17992 self.selection_history.transaction_mut(transaction_id)
17993 {
17994 *end_selections = Some(self.selections.disjoint_anchors_arc());
17995 } else {
17996 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17997 }
17998
17999 cx.emit(EditorEvent::Edited { transaction_id });
18000 Some(transaction_id)
18001 } else {
18002 None
18003 }
18004 }
18005
18006 pub fn modify_transaction_selection_history(
18007 &mut self,
18008 transaction_id: TransactionId,
18009 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18010 ) -> bool {
18011 self.selection_history
18012 .transaction_mut(transaction_id)
18013 .map(modify)
18014 .is_some()
18015 }
18016
18017 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18018 if self.selection_mark_mode {
18019 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18020 s.move_with(|_, sel| {
18021 sel.collapse_to(sel.head(), SelectionGoal::None);
18022 });
18023 })
18024 }
18025 self.selection_mark_mode = true;
18026 cx.notify();
18027 }
18028
18029 pub fn swap_selection_ends(
18030 &mut self,
18031 _: &actions::SwapSelectionEnds,
18032 window: &mut Window,
18033 cx: &mut Context<Self>,
18034 ) {
18035 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18036 s.move_with(|_, sel| {
18037 if sel.start != sel.end {
18038 sel.reversed = !sel.reversed
18039 }
18040 });
18041 });
18042 self.request_autoscroll(Autoscroll::newest(), cx);
18043 cx.notify();
18044 }
18045
18046 pub fn toggle_focus(
18047 workspace: &mut Workspace,
18048 _: &actions::ToggleFocus,
18049 window: &mut Window,
18050 cx: &mut Context<Workspace>,
18051 ) {
18052 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18053 return;
18054 };
18055 workspace.activate_item(&item, true, true, window, cx);
18056 }
18057
18058 pub fn toggle_fold(
18059 &mut self,
18060 _: &actions::ToggleFold,
18061 window: &mut Window,
18062 cx: &mut Context<Self>,
18063 ) {
18064 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18065 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18066 let selection = self.selections.newest::<Point>(&display_map);
18067
18068 let range = if selection.is_empty() {
18069 let point = selection.head().to_display_point(&display_map);
18070 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18071 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18072 .to_point(&display_map);
18073 start..end
18074 } else {
18075 selection.range()
18076 };
18077 if display_map.folds_in_range(range).next().is_some() {
18078 self.unfold_lines(&Default::default(), window, cx)
18079 } else {
18080 self.fold(&Default::default(), window, cx)
18081 }
18082 } else {
18083 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18084 let buffer_ids: HashSet<_> = self
18085 .selections
18086 .disjoint_anchor_ranges()
18087 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18088 .collect();
18089
18090 let should_unfold = buffer_ids
18091 .iter()
18092 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18093
18094 for buffer_id in buffer_ids {
18095 if should_unfold {
18096 self.unfold_buffer(buffer_id, cx);
18097 } else {
18098 self.fold_buffer(buffer_id, cx);
18099 }
18100 }
18101 }
18102 }
18103
18104 pub fn toggle_fold_recursive(
18105 &mut self,
18106 _: &actions::ToggleFoldRecursive,
18107 window: &mut Window,
18108 cx: &mut Context<Self>,
18109 ) {
18110 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18111
18112 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18113 let range = if selection.is_empty() {
18114 let point = selection.head().to_display_point(&display_map);
18115 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18116 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18117 .to_point(&display_map);
18118 start..end
18119 } else {
18120 selection.range()
18121 };
18122 if display_map.folds_in_range(range).next().is_some() {
18123 self.unfold_recursive(&Default::default(), window, cx)
18124 } else {
18125 self.fold_recursive(&Default::default(), window, cx)
18126 }
18127 }
18128
18129 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18130 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18131 let mut to_fold = Vec::new();
18132 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18133 let selections = self.selections.all_adjusted(&display_map);
18134
18135 for selection in selections {
18136 let range = selection.range().sorted();
18137 let buffer_start_row = range.start.row;
18138
18139 if range.start.row != range.end.row {
18140 let mut found = false;
18141 let mut row = range.start.row;
18142 while row <= range.end.row {
18143 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18144 {
18145 found = true;
18146 row = crease.range().end.row + 1;
18147 to_fold.push(crease);
18148 } else {
18149 row += 1
18150 }
18151 }
18152 if found {
18153 continue;
18154 }
18155 }
18156
18157 for row in (0..=range.start.row).rev() {
18158 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18159 && crease.range().end.row >= buffer_start_row
18160 {
18161 to_fold.push(crease);
18162 if row <= range.start.row {
18163 break;
18164 }
18165 }
18166 }
18167 }
18168
18169 self.fold_creases(to_fold, true, window, cx);
18170 } else {
18171 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18172 let buffer_ids = self
18173 .selections
18174 .disjoint_anchor_ranges()
18175 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18176 .collect::<HashSet<_>>();
18177 for buffer_id in buffer_ids {
18178 self.fold_buffer(buffer_id, cx);
18179 }
18180 }
18181 }
18182
18183 pub fn toggle_fold_all(
18184 &mut self,
18185 _: &actions::ToggleFoldAll,
18186 window: &mut Window,
18187 cx: &mut Context<Self>,
18188 ) {
18189 if self.buffer.read(cx).is_singleton() {
18190 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18191 let has_folds = display_map
18192 .folds_in_range(0..display_map.buffer_snapshot().len())
18193 .next()
18194 .is_some();
18195
18196 if has_folds {
18197 self.unfold_all(&actions::UnfoldAll, window, cx);
18198 } else {
18199 self.fold_all(&actions::FoldAll, window, cx);
18200 }
18201 } else {
18202 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18203 let should_unfold = buffer_ids
18204 .iter()
18205 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18206
18207 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18208 editor
18209 .update_in(cx, |editor, _, cx| {
18210 for buffer_id in buffer_ids {
18211 if should_unfold {
18212 editor.unfold_buffer(buffer_id, cx);
18213 } else {
18214 editor.fold_buffer(buffer_id, cx);
18215 }
18216 }
18217 })
18218 .ok();
18219 });
18220 }
18221 }
18222
18223 fn fold_at_level(
18224 &mut self,
18225 fold_at: &FoldAtLevel,
18226 window: &mut Window,
18227 cx: &mut Context<Self>,
18228 ) {
18229 if !self.buffer.read(cx).is_singleton() {
18230 return;
18231 }
18232
18233 let fold_at_level = fold_at.0;
18234 let snapshot = self.buffer.read(cx).snapshot(cx);
18235 let mut to_fold = Vec::new();
18236 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18237
18238 let row_ranges_to_keep: Vec<Range<u32>> = self
18239 .selections
18240 .all::<Point>(&self.display_snapshot(cx))
18241 .into_iter()
18242 .map(|sel| sel.start.row..sel.end.row)
18243 .collect();
18244
18245 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18246 while start_row < end_row {
18247 match self
18248 .snapshot(window, cx)
18249 .crease_for_buffer_row(MultiBufferRow(start_row))
18250 {
18251 Some(crease) => {
18252 let nested_start_row = crease.range().start.row + 1;
18253 let nested_end_row = crease.range().end.row;
18254
18255 if current_level < fold_at_level {
18256 stack.push((nested_start_row, nested_end_row, current_level + 1));
18257 } else if current_level == fold_at_level {
18258 // Fold iff there is no selection completely contained within the fold region
18259 if !row_ranges_to_keep.iter().any(|selection| {
18260 selection.end >= nested_start_row
18261 && selection.start <= nested_end_row
18262 }) {
18263 to_fold.push(crease);
18264 }
18265 }
18266
18267 start_row = nested_end_row + 1;
18268 }
18269 None => start_row += 1,
18270 }
18271 }
18272 }
18273
18274 self.fold_creases(to_fold, true, window, cx);
18275 }
18276
18277 pub fn fold_at_level_1(
18278 &mut self,
18279 _: &actions::FoldAtLevel1,
18280 window: &mut Window,
18281 cx: &mut Context<Self>,
18282 ) {
18283 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18284 }
18285
18286 pub fn fold_at_level_2(
18287 &mut self,
18288 _: &actions::FoldAtLevel2,
18289 window: &mut Window,
18290 cx: &mut Context<Self>,
18291 ) {
18292 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18293 }
18294
18295 pub fn fold_at_level_3(
18296 &mut self,
18297 _: &actions::FoldAtLevel3,
18298 window: &mut Window,
18299 cx: &mut Context<Self>,
18300 ) {
18301 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18302 }
18303
18304 pub fn fold_at_level_4(
18305 &mut self,
18306 _: &actions::FoldAtLevel4,
18307 window: &mut Window,
18308 cx: &mut Context<Self>,
18309 ) {
18310 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18311 }
18312
18313 pub fn fold_at_level_5(
18314 &mut self,
18315 _: &actions::FoldAtLevel5,
18316 window: &mut Window,
18317 cx: &mut Context<Self>,
18318 ) {
18319 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18320 }
18321
18322 pub fn fold_at_level_6(
18323 &mut self,
18324 _: &actions::FoldAtLevel6,
18325 window: &mut Window,
18326 cx: &mut Context<Self>,
18327 ) {
18328 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18329 }
18330
18331 pub fn fold_at_level_7(
18332 &mut self,
18333 _: &actions::FoldAtLevel7,
18334 window: &mut Window,
18335 cx: &mut Context<Self>,
18336 ) {
18337 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18338 }
18339
18340 pub fn fold_at_level_8(
18341 &mut self,
18342 _: &actions::FoldAtLevel8,
18343 window: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) {
18346 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18347 }
18348
18349 pub fn fold_at_level_9(
18350 &mut self,
18351 _: &actions::FoldAtLevel9,
18352 window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) {
18355 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18356 }
18357
18358 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18359 if self.buffer.read(cx).is_singleton() {
18360 let mut fold_ranges = Vec::new();
18361 let snapshot = self.buffer.read(cx).snapshot(cx);
18362
18363 for row in 0..snapshot.max_row().0 {
18364 if let Some(foldable_range) = self
18365 .snapshot(window, cx)
18366 .crease_for_buffer_row(MultiBufferRow(row))
18367 {
18368 fold_ranges.push(foldable_range);
18369 }
18370 }
18371
18372 self.fold_creases(fold_ranges, true, window, cx);
18373 } else {
18374 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18375 editor
18376 .update_in(cx, |editor, _, cx| {
18377 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18378 editor.fold_buffer(buffer_id, cx);
18379 }
18380 })
18381 .ok();
18382 });
18383 }
18384 }
18385
18386 pub fn fold_function_bodies(
18387 &mut self,
18388 _: &actions::FoldFunctionBodies,
18389 window: &mut Window,
18390 cx: &mut Context<Self>,
18391 ) {
18392 let snapshot = self.buffer.read(cx).snapshot(cx);
18393
18394 let ranges = snapshot
18395 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18396 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18397 .collect::<Vec<_>>();
18398
18399 let creases = ranges
18400 .into_iter()
18401 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18402 .collect();
18403
18404 self.fold_creases(creases, true, window, cx);
18405 }
18406
18407 pub fn fold_recursive(
18408 &mut self,
18409 _: &actions::FoldRecursive,
18410 window: &mut Window,
18411 cx: &mut Context<Self>,
18412 ) {
18413 let mut to_fold = Vec::new();
18414 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18415 let selections = self.selections.all_adjusted(&display_map);
18416
18417 for selection in selections {
18418 let range = selection.range().sorted();
18419 let buffer_start_row = range.start.row;
18420
18421 if range.start.row != range.end.row {
18422 let mut found = false;
18423 for row in range.start.row..=range.end.row {
18424 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18425 found = true;
18426 to_fold.push(crease);
18427 }
18428 }
18429 if found {
18430 continue;
18431 }
18432 }
18433
18434 for row in (0..=range.start.row).rev() {
18435 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18436 if crease.range().end.row >= buffer_start_row {
18437 to_fold.push(crease);
18438 } else {
18439 break;
18440 }
18441 }
18442 }
18443 }
18444
18445 self.fold_creases(to_fold, true, window, cx);
18446 }
18447
18448 pub fn fold_at(
18449 &mut self,
18450 buffer_row: MultiBufferRow,
18451 window: &mut Window,
18452 cx: &mut Context<Self>,
18453 ) {
18454 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18455
18456 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18457 let autoscroll = self
18458 .selections
18459 .all::<Point>(&display_map)
18460 .iter()
18461 .any(|selection| crease.range().overlaps(&selection.range()));
18462
18463 self.fold_creases(vec![crease], autoscroll, window, cx);
18464 }
18465 }
18466
18467 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18468 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18469 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18470 let buffer = display_map.buffer_snapshot();
18471 let selections = self.selections.all::<Point>(&display_map);
18472 let ranges = selections
18473 .iter()
18474 .map(|s| {
18475 let range = s.display_range(&display_map).sorted();
18476 let mut start = range.start.to_point(&display_map);
18477 let mut end = range.end.to_point(&display_map);
18478 start.column = 0;
18479 end.column = buffer.line_len(MultiBufferRow(end.row));
18480 start..end
18481 })
18482 .collect::<Vec<_>>();
18483
18484 self.unfold_ranges(&ranges, true, true, cx);
18485 } else {
18486 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18487 let buffer_ids = self
18488 .selections
18489 .disjoint_anchor_ranges()
18490 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18491 .collect::<HashSet<_>>();
18492 for buffer_id in buffer_ids {
18493 self.unfold_buffer(buffer_id, cx);
18494 }
18495 }
18496 }
18497
18498 pub fn unfold_recursive(
18499 &mut self,
18500 _: &UnfoldRecursive,
18501 _window: &mut Window,
18502 cx: &mut Context<Self>,
18503 ) {
18504 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18505 let selections = self.selections.all::<Point>(&display_map);
18506 let ranges = selections
18507 .iter()
18508 .map(|s| {
18509 let mut range = s.display_range(&display_map).sorted();
18510 *range.start.column_mut() = 0;
18511 *range.end.column_mut() = display_map.line_len(range.end.row());
18512 let start = range.start.to_point(&display_map);
18513 let end = range.end.to_point(&display_map);
18514 start..end
18515 })
18516 .collect::<Vec<_>>();
18517
18518 self.unfold_ranges(&ranges, true, true, cx);
18519 }
18520
18521 pub fn unfold_at(
18522 &mut self,
18523 buffer_row: MultiBufferRow,
18524 _window: &mut Window,
18525 cx: &mut Context<Self>,
18526 ) {
18527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18528
18529 let intersection_range = Point::new(buffer_row.0, 0)
18530 ..Point::new(
18531 buffer_row.0,
18532 display_map.buffer_snapshot().line_len(buffer_row),
18533 );
18534
18535 let autoscroll = self
18536 .selections
18537 .all::<Point>(&display_map)
18538 .iter()
18539 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18540
18541 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18542 }
18543
18544 pub fn unfold_all(
18545 &mut self,
18546 _: &actions::UnfoldAll,
18547 _window: &mut Window,
18548 cx: &mut Context<Self>,
18549 ) {
18550 if self.buffer.read(cx).is_singleton() {
18551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18552 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18553 } else {
18554 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18555 editor
18556 .update(cx, |editor, cx| {
18557 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18558 editor.unfold_buffer(buffer_id, cx);
18559 }
18560 })
18561 .ok();
18562 });
18563 }
18564 }
18565
18566 pub fn fold_selected_ranges(
18567 &mut self,
18568 _: &FoldSelectedRanges,
18569 window: &mut Window,
18570 cx: &mut Context<Self>,
18571 ) {
18572 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18573 let selections = self.selections.all_adjusted(&display_map);
18574 let ranges = selections
18575 .into_iter()
18576 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18577 .collect::<Vec<_>>();
18578 self.fold_creases(ranges, true, window, cx);
18579 }
18580
18581 pub fn fold_ranges<T: ToOffset + Clone>(
18582 &mut self,
18583 ranges: Vec<Range<T>>,
18584 auto_scroll: bool,
18585 window: &mut Window,
18586 cx: &mut Context<Self>,
18587 ) {
18588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18589 let ranges = ranges
18590 .into_iter()
18591 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18592 .collect::<Vec<_>>();
18593 self.fold_creases(ranges, auto_scroll, window, cx);
18594 }
18595
18596 pub fn fold_creases<T: ToOffset + Clone>(
18597 &mut self,
18598 creases: Vec<Crease<T>>,
18599 auto_scroll: bool,
18600 _window: &mut Window,
18601 cx: &mut Context<Self>,
18602 ) {
18603 if creases.is_empty() {
18604 return;
18605 }
18606
18607 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18608
18609 if auto_scroll {
18610 self.request_autoscroll(Autoscroll::fit(), cx);
18611 }
18612
18613 cx.notify();
18614
18615 self.scrollbar_marker_state.dirty = true;
18616 self.folds_did_change(cx);
18617 }
18618
18619 /// Removes any folds whose ranges intersect any of the given ranges.
18620 pub fn unfold_ranges<T: ToOffset + Clone>(
18621 &mut self,
18622 ranges: &[Range<T>],
18623 inclusive: bool,
18624 auto_scroll: bool,
18625 cx: &mut Context<Self>,
18626 ) {
18627 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18628 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18629 });
18630 self.folds_did_change(cx);
18631 }
18632
18633 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18634 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18635 return;
18636 }
18637 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18638 self.display_map.update(cx, |display_map, cx| {
18639 display_map.fold_buffers([buffer_id], cx)
18640 });
18641 cx.emit(EditorEvent::BufferFoldToggled {
18642 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18643 folded: true,
18644 });
18645 cx.notify();
18646 }
18647
18648 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18649 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18650 return;
18651 }
18652 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18653 self.display_map.update(cx, |display_map, cx| {
18654 display_map.unfold_buffers([buffer_id], cx);
18655 });
18656 cx.emit(EditorEvent::BufferFoldToggled {
18657 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18658 folded: false,
18659 });
18660 cx.notify();
18661 }
18662
18663 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18664 self.display_map.read(cx).is_buffer_folded(buffer)
18665 }
18666
18667 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18668 self.display_map.read(cx).folded_buffers()
18669 }
18670
18671 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18672 self.display_map.update(cx, |display_map, cx| {
18673 display_map.disable_header_for_buffer(buffer_id, cx);
18674 });
18675 cx.notify();
18676 }
18677
18678 /// Removes any folds with the given ranges.
18679 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18680 &mut self,
18681 ranges: &[Range<T>],
18682 type_id: TypeId,
18683 auto_scroll: bool,
18684 cx: &mut Context<Self>,
18685 ) {
18686 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18687 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18688 });
18689 self.folds_did_change(cx);
18690 }
18691
18692 fn remove_folds_with<T: ToOffset + Clone>(
18693 &mut self,
18694 ranges: &[Range<T>],
18695 auto_scroll: bool,
18696 cx: &mut Context<Self>,
18697 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18698 ) {
18699 if ranges.is_empty() {
18700 return;
18701 }
18702
18703 let mut buffers_affected = HashSet::default();
18704 let multi_buffer = self.buffer().read(cx);
18705 for range in ranges {
18706 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18707 buffers_affected.insert(buffer.read(cx).remote_id());
18708 };
18709 }
18710
18711 self.display_map.update(cx, update);
18712
18713 if auto_scroll {
18714 self.request_autoscroll(Autoscroll::fit(), cx);
18715 }
18716
18717 cx.notify();
18718 self.scrollbar_marker_state.dirty = true;
18719 self.active_indent_guides_state.dirty = true;
18720 }
18721
18722 pub fn update_renderer_widths(
18723 &mut self,
18724 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18725 cx: &mut Context<Self>,
18726 ) -> bool {
18727 self.display_map
18728 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18729 }
18730
18731 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18732 self.display_map.read(cx).fold_placeholder.clone()
18733 }
18734
18735 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18736 self.buffer.update(cx, |buffer, cx| {
18737 buffer.set_all_diff_hunks_expanded(cx);
18738 });
18739 }
18740
18741 pub fn expand_all_diff_hunks(
18742 &mut self,
18743 _: &ExpandAllDiffHunks,
18744 _window: &mut Window,
18745 cx: &mut Context<Self>,
18746 ) {
18747 self.buffer.update(cx, |buffer, cx| {
18748 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18749 });
18750 }
18751
18752 pub fn collapse_all_diff_hunks(
18753 &mut self,
18754 _: &CollapseAllDiffHunks,
18755 _window: &mut Window,
18756 cx: &mut Context<Self>,
18757 ) {
18758 self.buffer.update(cx, |buffer, cx| {
18759 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18760 });
18761 }
18762
18763 pub fn toggle_selected_diff_hunks(
18764 &mut self,
18765 _: &ToggleSelectedDiffHunks,
18766 _window: &mut Window,
18767 cx: &mut Context<Self>,
18768 ) {
18769 let ranges: Vec<_> = self
18770 .selections
18771 .disjoint_anchors()
18772 .iter()
18773 .map(|s| s.range())
18774 .collect();
18775 self.toggle_diff_hunks_in_ranges(ranges, cx);
18776 }
18777
18778 pub fn diff_hunks_in_ranges<'a>(
18779 &'a self,
18780 ranges: &'a [Range<Anchor>],
18781 buffer: &'a MultiBufferSnapshot,
18782 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18783 ranges.iter().flat_map(move |range| {
18784 let end_excerpt_id = range.end.excerpt_id;
18785 let range = range.to_point(buffer);
18786 let mut peek_end = range.end;
18787 if range.end.row < buffer.max_row().0 {
18788 peek_end = Point::new(range.end.row + 1, 0);
18789 }
18790 buffer
18791 .diff_hunks_in_range(range.start..peek_end)
18792 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18793 })
18794 }
18795
18796 pub fn has_stageable_diff_hunks_in_ranges(
18797 &self,
18798 ranges: &[Range<Anchor>],
18799 snapshot: &MultiBufferSnapshot,
18800 ) -> bool {
18801 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18802 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18803 }
18804
18805 pub fn toggle_staged_selected_diff_hunks(
18806 &mut self,
18807 _: &::git::ToggleStaged,
18808 _: &mut Window,
18809 cx: &mut Context<Self>,
18810 ) {
18811 let snapshot = self.buffer.read(cx).snapshot(cx);
18812 let ranges: Vec<_> = self
18813 .selections
18814 .disjoint_anchors()
18815 .iter()
18816 .map(|s| s.range())
18817 .collect();
18818 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18819 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18820 }
18821
18822 pub fn set_render_diff_hunk_controls(
18823 &mut self,
18824 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18825 cx: &mut Context<Self>,
18826 ) {
18827 self.render_diff_hunk_controls = render_diff_hunk_controls;
18828 cx.notify();
18829 }
18830
18831 pub fn stage_and_next(
18832 &mut self,
18833 _: &::git::StageAndNext,
18834 window: &mut Window,
18835 cx: &mut Context<Self>,
18836 ) {
18837 self.do_stage_or_unstage_and_next(true, window, cx);
18838 }
18839
18840 pub fn unstage_and_next(
18841 &mut self,
18842 _: &::git::UnstageAndNext,
18843 window: &mut Window,
18844 cx: &mut Context<Self>,
18845 ) {
18846 self.do_stage_or_unstage_and_next(false, window, cx);
18847 }
18848
18849 pub fn stage_or_unstage_diff_hunks(
18850 &mut self,
18851 stage: bool,
18852 ranges: Vec<Range<Anchor>>,
18853 cx: &mut Context<Self>,
18854 ) {
18855 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18856 cx.spawn(async move |this, cx| {
18857 task.await?;
18858 this.update(cx, |this, cx| {
18859 let snapshot = this.buffer.read(cx).snapshot(cx);
18860 let chunk_by = this
18861 .diff_hunks_in_ranges(&ranges, &snapshot)
18862 .chunk_by(|hunk| hunk.buffer_id);
18863 for (buffer_id, hunks) in &chunk_by {
18864 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18865 }
18866 })
18867 })
18868 .detach_and_log_err(cx);
18869 }
18870
18871 fn save_buffers_for_ranges_if_needed(
18872 &mut self,
18873 ranges: &[Range<Anchor>],
18874 cx: &mut Context<Editor>,
18875 ) -> Task<Result<()>> {
18876 let multibuffer = self.buffer.read(cx);
18877 let snapshot = multibuffer.read(cx);
18878 let buffer_ids: HashSet<_> = ranges
18879 .iter()
18880 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18881 .collect();
18882 drop(snapshot);
18883
18884 let mut buffers = HashSet::default();
18885 for buffer_id in buffer_ids {
18886 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18887 let buffer = buffer_entity.read(cx);
18888 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18889 {
18890 buffers.insert(buffer_entity);
18891 }
18892 }
18893 }
18894
18895 if let Some(project) = &self.project {
18896 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18897 } else {
18898 Task::ready(Ok(()))
18899 }
18900 }
18901
18902 fn do_stage_or_unstage_and_next(
18903 &mut self,
18904 stage: bool,
18905 window: &mut Window,
18906 cx: &mut Context<Self>,
18907 ) {
18908 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18909
18910 if ranges.iter().any(|range| range.start != range.end) {
18911 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18912 return;
18913 }
18914
18915 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18916 let snapshot = self.snapshot(window, cx);
18917 let position = self
18918 .selections
18919 .newest::<Point>(&snapshot.display_snapshot)
18920 .head();
18921 let mut row = snapshot
18922 .buffer_snapshot()
18923 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18924 .find(|hunk| hunk.row_range.start.0 > position.row)
18925 .map(|hunk| hunk.row_range.start);
18926
18927 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18928 // Outside of the project diff editor, wrap around to the beginning.
18929 if !all_diff_hunks_expanded {
18930 row = row.or_else(|| {
18931 snapshot
18932 .buffer_snapshot()
18933 .diff_hunks_in_range(Point::zero()..position)
18934 .find(|hunk| hunk.row_range.end.0 < position.row)
18935 .map(|hunk| hunk.row_range.start)
18936 });
18937 }
18938
18939 if let Some(row) = row {
18940 let destination = Point::new(row.0, 0);
18941 let autoscroll = Autoscroll::center();
18942
18943 self.unfold_ranges(&[destination..destination], false, false, cx);
18944 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18945 s.select_ranges([destination..destination]);
18946 });
18947 }
18948 }
18949
18950 fn do_stage_or_unstage(
18951 &self,
18952 stage: bool,
18953 buffer_id: BufferId,
18954 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18955 cx: &mut App,
18956 ) -> Option<()> {
18957 let project = self.project()?;
18958 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18959 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18960 let buffer_snapshot = buffer.read(cx).snapshot();
18961 let file_exists = buffer_snapshot
18962 .file()
18963 .is_some_and(|file| file.disk_state().exists());
18964 diff.update(cx, |diff, cx| {
18965 diff.stage_or_unstage_hunks(
18966 stage,
18967 &hunks
18968 .map(|hunk| buffer_diff::DiffHunk {
18969 buffer_range: hunk.buffer_range,
18970 diff_base_byte_range: hunk.diff_base_byte_range,
18971 secondary_status: hunk.secondary_status,
18972 range: Point::zero()..Point::zero(), // unused
18973 })
18974 .collect::<Vec<_>>(),
18975 &buffer_snapshot,
18976 file_exists,
18977 cx,
18978 )
18979 });
18980 None
18981 }
18982
18983 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18984 let ranges: Vec<_> = self
18985 .selections
18986 .disjoint_anchors()
18987 .iter()
18988 .map(|s| s.range())
18989 .collect();
18990 self.buffer
18991 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18992 }
18993
18994 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18995 self.buffer.update(cx, |buffer, cx| {
18996 let ranges = vec![Anchor::min()..Anchor::max()];
18997 if !buffer.all_diff_hunks_expanded()
18998 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18999 {
19000 buffer.collapse_diff_hunks(ranges, cx);
19001 true
19002 } else {
19003 false
19004 }
19005 })
19006 }
19007
19008 fn toggle_diff_hunks_in_ranges(
19009 &mut self,
19010 ranges: Vec<Range<Anchor>>,
19011 cx: &mut Context<Editor>,
19012 ) {
19013 self.buffer.update(cx, |buffer, cx| {
19014 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19015 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19016 })
19017 }
19018
19019 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19020 self.buffer.update(cx, |buffer, cx| {
19021 let snapshot = buffer.snapshot(cx);
19022 let excerpt_id = range.end.excerpt_id;
19023 let point_range = range.to_point(&snapshot);
19024 let expand = !buffer.single_hunk_is_expanded(range, cx);
19025 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19026 })
19027 }
19028
19029 pub(crate) fn apply_all_diff_hunks(
19030 &mut self,
19031 _: &ApplyAllDiffHunks,
19032 window: &mut Window,
19033 cx: &mut Context<Self>,
19034 ) {
19035 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19036
19037 let buffers = self.buffer.read(cx).all_buffers();
19038 for branch_buffer in buffers {
19039 branch_buffer.update(cx, |branch_buffer, cx| {
19040 branch_buffer.merge_into_base(Vec::new(), cx);
19041 });
19042 }
19043
19044 if let Some(project) = self.project.clone() {
19045 self.save(
19046 SaveOptions {
19047 format: true,
19048 autosave: false,
19049 },
19050 project,
19051 window,
19052 cx,
19053 )
19054 .detach_and_log_err(cx);
19055 }
19056 }
19057
19058 pub(crate) fn apply_selected_diff_hunks(
19059 &mut self,
19060 _: &ApplyDiffHunk,
19061 window: &mut Window,
19062 cx: &mut Context<Self>,
19063 ) {
19064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19065 let snapshot = self.snapshot(window, cx);
19066 let hunks = snapshot.hunks_for_ranges(
19067 self.selections
19068 .all(&snapshot.display_snapshot)
19069 .into_iter()
19070 .map(|selection| selection.range()),
19071 );
19072 let mut ranges_by_buffer = HashMap::default();
19073 self.transact(window, cx, |editor, _window, cx| {
19074 for hunk in hunks {
19075 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19076 ranges_by_buffer
19077 .entry(buffer.clone())
19078 .or_insert_with(Vec::new)
19079 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19080 }
19081 }
19082
19083 for (buffer, ranges) in ranges_by_buffer {
19084 buffer.update(cx, |buffer, cx| {
19085 buffer.merge_into_base(ranges, cx);
19086 });
19087 }
19088 });
19089
19090 if let Some(project) = self.project.clone() {
19091 self.save(
19092 SaveOptions {
19093 format: true,
19094 autosave: false,
19095 },
19096 project,
19097 window,
19098 cx,
19099 )
19100 .detach_and_log_err(cx);
19101 }
19102 }
19103
19104 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19105 if hovered != self.gutter_hovered {
19106 self.gutter_hovered = hovered;
19107 cx.notify();
19108 }
19109 }
19110
19111 pub fn insert_blocks(
19112 &mut self,
19113 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19114 autoscroll: Option<Autoscroll>,
19115 cx: &mut Context<Self>,
19116 ) -> Vec<CustomBlockId> {
19117 let blocks = self
19118 .display_map
19119 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19120 if let Some(autoscroll) = autoscroll {
19121 self.request_autoscroll(autoscroll, cx);
19122 }
19123 cx.notify();
19124 blocks
19125 }
19126
19127 pub fn resize_blocks(
19128 &mut self,
19129 heights: HashMap<CustomBlockId, u32>,
19130 autoscroll: Option<Autoscroll>,
19131 cx: &mut Context<Self>,
19132 ) {
19133 self.display_map
19134 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19135 if let Some(autoscroll) = autoscroll {
19136 self.request_autoscroll(autoscroll, cx);
19137 }
19138 cx.notify();
19139 }
19140
19141 pub fn replace_blocks(
19142 &mut self,
19143 renderers: HashMap<CustomBlockId, RenderBlock>,
19144 autoscroll: Option<Autoscroll>,
19145 cx: &mut Context<Self>,
19146 ) {
19147 self.display_map
19148 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19149 if let Some(autoscroll) = autoscroll {
19150 self.request_autoscroll(autoscroll, cx);
19151 }
19152 cx.notify();
19153 }
19154
19155 pub fn remove_blocks(
19156 &mut self,
19157 block_ids: HashSet<CustomBlockId>,
19158 autoscroll: Option<Autoscroll>,
19159 cx: &mut Context<Self>,
19160 ) {
19161 self.display_map.update(cx, |display_map, cx| {
19162 display_map.remove_blocks(block_ids, cx)
19163 });
19164 if let Some(autoscroll) = autoscroll {
19165 self.request_autoscroll(autoscroll, cx);
19166 }
19167 cx.notify();
19168 }
19169
19170 pub fn row_for_block(
19171 &self,
19172 block_id: CustomBlockId,
19173 cx: &mut Context<Self>,
19174 ) -> Option<DisplayRow> {
19175 self.display_map
19176 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19177 }
19178
19179 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19180 self.focused_block = Some(focused_block);
19181 }
19182
19183 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19184 self.focused_block.take()
19185 }
19186
19187 pub fn insert_creases(
19188 &mut self,
19189 creases: impl IntoIterator<Item = Crease<Anchor>>,
19190 cx: &mut Context<Self>,
19191 ) -> Vec<CreaseId> {
19192 self.display_map
19193 .update(cx, |map, cx| map.insert_creases(creases, cx))
19194 }
19195
19196 pub fn remove_creases(
19197 &mut self,
19198 ids: impl IntoIterator<Item = CreaseId>,
19199 cx: &mut Context<Self>,
19200 ) -> Vec<(CreaseId, Range<Anchor>)> {
19201 self.display_map
19202 .update(cx, |map, cx| map.remove_creases(ids, cx))
19203 }
19204
19205 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19206 self.display_map
19207 .update(cx, |map, cx| map.snapshot(cx))
19208 .longest_row()
19209 }
19210
19211 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19212 self.display_map
19213 .update(cx, |map, cx| map.snapshot(cx))
19214 .max_point()
19215 }
19216
19217 pub fn text(&self, cx: &App) -> String {
19218 self.buffer.read(cx).read(cx).text()
19219 }
19220
19221 pub fn is_empty(&self, cx: &App) -> bool {
19222 self.buffer.read(cx).read(cx).is_empty()
19223 }
19224
19225 pub fn text_option(&self, cx: &App) -> Option<String> {
19226 let text = self.text(cx);
19227 let text = text.trim();
19228
19229 if text.is_empty() {
19230 return None;
19231 }
19232
19233 Some(text.to_string())
19234 }
19235
19236 pub fn set_text(
19237 &mut self,
19238 text: impl Into<Arc<str>>,
19239 window: &mut Window,
19240 cx: &mut Context<Self>,
19241 ) {
19242 self.transact(window, cx, |this, _, cx| {
19243 this.buffer
19244 .read(cx)
19245 .as_singleton()
19246 .expect("you can only call set_text on editors for singleton buffers")
19247 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19248 });
19249 }
19250
19251 pub fn display_text(&self, cx: &mut App) -> String {
19252 self.display_map
19253 .update(cx, |map, cx| map.snapshot(cx))
19254 .text()
19255 }
19256
19257 fn create_minimap(
19258 &self,
19259 minimap_settings: MinimapSettings,
19260 window: &mut Window,
19261 cx: &mut Context<Self>,
19262 ) -> Option<Entity<Self>> {
19263 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19264 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19265 }
19266
19267 fn initialize_new_minimap(
19268 &self,
19269 minimap_settings: MinimapSettings,
19270 window: &mut Window,
19271 cx: &mut Context<Self>,
19272 ) -> Entity<Self> {
19273 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19274
19275 let mut minimap = Editor::new_internal(
19276 EditorMode::Minimap {
19277 parent: cx.weak_entity(),
19278 },
19279 self.buffer.clone(),
19280 None,
19281 Some(self.display_map.clone()),
19282 window,
19283 cx,
19284 );
19285 minimap.scroll_manager.clone_state(&self.scroll_manager);
19286 minimap.set_text_style_refinement(TextStyleRefinement {
19287 font_size: Some(MINIMAP_FONT_SIZE),
19288 font_weight: Some(MINIMAP_FONT_WEIGHT),
19289 ..Default::default()
19290 });
19291 minimap.update_minimap_configuration(minimap_settings, cx);
19292 cx.new(|_| minimap)
19293 }
19294
19295 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19296 let current_line_highlight = minimap_settings
19297 .current_line_highlight
19298 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19299 self.set_current_line_highlight(Some(current_line_highlight));
19300 }
19301
19302 pub fn minimap(&self) -> Option<&Entity<Self>> {
19303 self.minimap
19304 .as_ref()
19305 .filter(|_| self.minimap_visibility.visible())
19306 }
19307
19308 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19309 let mut wrap_guides = smallvec![];
19310
19311 if self.show_wrap_guides == Some(false) {
19312 return wrap_guides;
19313 }
19314
19315 let settings = self.buffer.read(cx).language_settings(cx);
19316 if settings.show_wrap_guides {
19317 match self.soft_wrap_mode(cx) {
19318 SoftWrap::Column(soft_wrap) => {
19319 wrap_guides.push((soft_wrap as usize, true));
19320 }
19321 SoftWrap::Bounded(soft_wrap) => {
19322 wrap_guides.push((soft_wrap as usize, true));
19323 }
19324 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19325 }
19326 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19327 }
19328
19329 wrap_guides
19330 }
19331
19332 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19333 let settings = self.buffer.read(cx).language_settings(cx);
19334 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19335 match mode {
19336 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19337 SoftWrap::None
19338 }
19339 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19340 language_settings::SoftWrap::PreferredLineLength => {
19341 SoftWrap::Column(settings.preferred_line_length)
19342 }
19343 language_settings::SoftWrap::Bounded => {
19344 SoftWrap::Bounded(settings.preferred_line_length)
19345 }
19346 }
19347 }
19348
19349 pub fn set_soft_wrap_mode(
19350 &mut self,
19351 mode: language_settings::SoftWrap,
19352
19353 cx: &mut Context<Self>,
19354 ) {
19355 self.soft_wrap_mode_override = Some(mode);
19356 cx.notify();
19357 }
19358
19359 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19360 self.hard_wrap = hard_wrap;
19361 cx.notify();
19362 }
19363
19364 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19365 self.text_style_refinement = Some(style);
19366 }
19367
19368 /// called by the Element so we know what style we were most recently rendered with.
19369 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19370 // We intentionally do not inform the display map about the minimap style
19371 // so that wrapping is not recalculated and stays consistent for the editor
19372 // and its linked minimap.
19373 if !self.mode.is_minimap() {
19374 let font = style.text.font();
19375 let font_size = style.text.font_size.to_pixels(window.rem_size());
19376 let display_map = self
19377 .placeholder_display_map
19378 .as_ref()
19379 .filter(|_| self.is_empty(cx))
19380 .unwrap_or(&self.display_map);
19381
19382 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19383 }
19384 self.style = Some(style);
19385 }
19386
19387 pub fn style(&self) -> Option<&EditorStyle> {
19388 self.style.as_ref()
19389 }
19390
19391 // Called by the element. This method is not designed to be called outside of the editor
19392 // element's layout code because it does not notify when rewrapping is computed synchronously.
19393 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19394 if self.is_empty(cx) {
19395 self.placeholder_display_map
19396 .as_ref()
19397 .map_or(false, |display_map| {
19398 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19399 })
19400 } else {
19401 self.display_map
19402 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19403 }
19404 }
19405
19406 pub fn set_soft_wrap(&mut self) {
19407 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19408 }
19409
19410 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19411 if self.soft_wrap_mode_override.is_some() {
19412 self.soft_wrap_mode_override.take();
19413 } else {
19414 let soft_wrap = match self.soft_wrap_mode(cx) {
19415 SoftWrap::GitDiff => return,
19416 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19417 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19418 language_settings::SoftWrap::None
19419 }
19420 };
19421 self.soft_wrap_mode_override = Some(soft_wrap);
19422 }
19423 cx.notify();
19424 }
19425
19426 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19427 let Some(workspace) = self.workspace() else {
19428 return;
19429 };
19430 let fs = workspace.read(cx).app_state().fs.clone();
19431 let current_show = TabBarSettings::get_global(cx).show;
19432 update_settings_file(fs, cx, move |setting, _| {
19433 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19434 });
19435 }
19436
19437 pub fn toggle_indent_guides(
19438 &mut self,
19439 _: &ToggleIndentGuides,
19440 _: &mut Window,
19441 cx: &mut Context<Self>,
19442 ) {
19443 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19444 self.buffer
19445 .read(cx)
19446 .language_settings(cx)
19447 .indent_guides
19448 .enabled
19449 });
19450 self.show_indent_guides = Some(!currently_enabled);
19451 cx.notify();
19452 }
19453
19454 fn should_show_indent_guides(&self) -> Option<bool> {
19455 self.show_indent_guides
19456 }
19457
19458 pub fn toggle_line_numbers(
19459 &mut self,
19460 _: &ToggleLineNumbers,
19461 _: &mut Window,
19462 cx: &mut Context<Self>,
19463 ) {
19464 let mut editor_settings = EditorSettings::get_global(cx).clone();
19465 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19466 EditorSettings::override_global(editor_settings, cx);
19467 }
19468
19469 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19470 if let Some(show_line_numbers) = self.show_line_numbers {
19471 return show_line_numbers;
19472 }
19473 EditorSettings::get_global(cx).gutter.line_numbers
19474 }
19475
19476 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19477 self.use_relative_line_numbers
19478 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19479 }
19480
19481 pub fn toggle_relative_line_numbers(
19482 &mut self,
19483 _: &ToggleRelativeLineNumbers,
19484 _: &mut Window,
19485 cx: &mut Context<Self>,
19486 ) {
19487 let is_relative = self.should_use_relative_line_numbers(cx);
19488 self.set_relative_line_number(Some(!is_relative), cx)
19489 }
19490
19491 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19492 self.use_relative_line_numbers = is_relative;
19493 cx.notify();
19494 }
19495
19496 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19497 self.show_gutter = show_gutter;
19498 cx.notify();
19499 }
19500
19501 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19502 self.show_scrollbars = ScrollbarAxes {
19503 horizontal: show,
19504 vertical: show,
19505 };
19506 cx.notify();
19507 }
19508
19509 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19510 self.show_scrollbars.vertical = show;
19511 cx.notify();
19512 }
19513
19514 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19515 self.show_scrollbars.horizontal = show;
19516 cx.notify();
19517 }
19518
19519 pub fn set_minimap_visibility(
19520 &mut self,
19521 minimap_visibility: MinimapVisibility,
19522 window: &mut Window,
19523 cx: &mut Context<Self>,
19524 ) {
19525 if self.minimap_visibility != minimap_visibility {
19526 if minimap_visibility.visible() && self.minimap.is_none() {
19527 let minimap_settings = EditorSettings::get_global(cx).minimap;
19528 self.minimap =
19529 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19530 }
19531 self.minimap_visibility = minimap_visibility;
19532 cx.notify();
19533 }
19534 }
19535
19536 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19537 self.set_show_scrollbars(false, cx);
19538 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19539 }
19540
19541 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19542 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19543 }
19544
19545 /// Normally the text in full mode and auto height editors is padded on the
19546 /// left side by roughly half a character width for improved hit testing.
19547 ///
19548 /// Use this method to disable this for cases where this is not wanted (e.g.
19549 /// if you want to align the editor text with some other text above or below)
19550 /// or if you want to add this padding to single-line editors.
19551 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19552 self.offset_content = offset_content;
19553 cx.notify();
19554 }
19555
19556 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19557 self.show_line_numbers = Some(show_line_numbers);
19558 cx.notify();
19559 }
19560
19561 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19562 self.disable_expand_excerpt_buttons = true;
19563 cx.notify();
19564 }
19565
19566 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19567 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19568 cx.notify();
19569 }
19570
19571 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19572 self.show_code_actions = Some(show_code_actions);
19573 cx.notify();
19574 }
19575
19576 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19577 self.show_runnables = Some(show_runnables);
19578 cx.notify();
19579 }
19580
19581 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19582 self.show_breakpoints = Some(show_breakpoints);
19583 cx.notify();
19584 }
19585
19586 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19587 if self.display_map.read(cx).masked != masked {
19588 self.display_map.update(cx, |map, _| map.masked = masked);
19589 }
19590 cx.notify()
19591 }
19592
19593 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19594 self.show_wrap_guides = Some(show_wrap_guides);
19595 cx.notify();
19596 }
19597
19598 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19599 self.show_indent_guides = Some(show_indent_guides);
19600 cx.notify();
19601 }
19602
19603 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19604 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19605 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19606 && let Some(dir) = file.abs_path(cx).parent()
19607 {
19608 return Some(dir.to_owned());
19609 }
19610 }
19611
19612 None
19613 }
19614
19615 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19616 self.active_excerpt(cx)?
19617 .1
19618 .read(cx)
19619 .file()
19620 .and_then(|f| f.as_local())
19621 }
19622
19623 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19624 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19625 let buffer = buffer.read(cx);
19626 if let Some(project_path) = buffer.project_path(cx) {
19627 let project = self.project()?.read(cx);
19628 project.absolute_path(&project_path, cx)
19629 } else {
19630 buffer
19631 .file()
19632 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19633 }
19634 })
19635 }
19636
19637 pub fn reveal_in_finder(
19638 &mut self,
19639 _: &RevealInFileManager,
19640 _window: &mut Window,
19641 cx: &mut Context<Self>,
19642 ) {
19643 if let Some(target) = self.target_file(cx) {
19644 cx.reveal_path(&target.abs_path(cx));
19645 }
19646 }
19647
19648 pub fn copy_path(
19649 &mut self,
19650 _: &zed_actions::workspace::CopyPath,
19651 _window: &mut Window,
19652 cx: &mut Context<Self>,
19653 ) {
19654 if let Some(path) = self.target_file_abs_path(cx)
19655 && let Some(path) = path.to_str()
19656 {
19657 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19658 } else {
19659 cx.propagate();
19660 }
19661 }
19662
19663 pub fn copy_relative_path(
19664 &mut self,
19665 _: &zed_actions::workspace::CopyRelativePath,
19666 _window: &mut Window,
19667 cx: &mut Context<Self>,
19668 ) {
19669 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19670 let project = self.project()?.read(cx);
19671 let path = buffer.read(cx).file()?.path();
19672 let path = path.display(project.path_style(cx));
19673 Some(path)
19674 }) {
19675 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19676 } else {
19677 cx.propagate();
19678 }
19679 }
19680
19681 /// Returns the project path for the editor's buffer, if any buffer is
19682 /// opened in the editor.
19683 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19684 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19685 buffer.read(cx).project_path(cx)
19686 } else {
19687 None
19688 }
19689 }
19690
19691 // Returns true if the editor handled a go-to-line request
19692 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19693 maybe!({
19694 let breakpoint_store = self.breakpoint_store.as_ref()?;
19695
19696 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19697 else {
19698 self.clear_row_highlights::<ActiveDebugLine>();
19699 return None;
19700 };
19701
19702 let position = active_stack_frame.position;
19703 let buffer_id = position.buffer_id?;
19704 let snapshot = self
19705 .project
19706 .as_ref()?
19707 .read(cx)
19708 .buffer_for_id(buffer_id, cx)?
19709 .read(cx)
19710 .snapshot();
19711
19712 let mut handled = false;
19713 for (id, ExcerptRange { context, .. }) in
19714 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19715 {
19716 if context.start.cmp(&position, &snapshot).is_ge()
19717 || context.end.cmp(&position, &snapshot).is_lt()
19718 {
19719 continue;
19720 }
19721 let snapshot = self.buffer.read(cx).snapshot(cx);
19722 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19723
19724 handled = true;
19725 self.clear_row_highlights::<ActiveDebugLine>();
19726
19727 self.go_to_line::<ActiveDebugLine>(
19728 multibuffer_anchor,
19729 Some(cx.theme().colors().editor_debugger_active_line_background),
19730 window,
19731 cx,
19732 );
19733
19734 cx.notify();
19735 }
19736
19737 handled.then_some(())
19738 })
19739 .is_some()
19740 }
19741
19742 pub fn copy_file_name_without_extension(
19743 &mut self,
19744 _: &CopyFileNameWithoutExtension,
19745 _: &mut Window,
19746 cx: &mut Context<Self>,
19747 ) {
19748 if let Some(file) = self.target_file(cx)
19749 && let Some(file_stem) = file.path().file_stem()
19750 {
19751 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19752 }
19753 }
19754
19755 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19756 if let Some(file) = self.target_file(cx)
19757 && let Some(name) = file.path().file_name()
19758 {
19759 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19760 }
19761 }
19762
19763 pub fn toggle_git_blame(
19764 &mut self,
19765 _: &::git::Blame,
19766 window: &mut Window,
19767 cx: &mut Context<Self>,
19768 ) {
19769 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19770
19771 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19772 self.start_git_blame(true, window, cx);
19773 }
19774
19775 cx.notify();
19776 }
19777
19778 pub fn toggle_git_blame_inline(
19779 &mut self,
19780 _: &ToggleGitBlameInline,
19781 window: &mut Window,
19782 cx: &mut Context<Self>,
19783 ) {
19784 self.toggle_git_blame_inline_internal(true, window, cx);
19785 cx.notify();
19786 }
19787
19788 pub fn open_git_blame_commit(
19789 &mut self,
19790 _: &OpenGitBlameCommit,
19791 window: &mut Window,
19792 cx: &mut Context<Self>,
19793 ) {
19794 self.open_git_blame_commit_internal(window, cx);
19795 }
19796
19797 fn open_git_blame_commit_internal(
19798 &mut self,
19799 window: &mut Window,
19800 cx: &mut Context<Self>,
19801 ) -> Option<()> {
19802 let blame = self.blame.as_ref()?;
19803 let snapshot = self.snapshot(window, cx);
19804 let cursor = self
19805 .selections
19806 .newest::<Point>(&snapshot.display_snapshot)
19807 .head();
19808 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19809 let (_, blame_entry) = blame
19810 .update(cx, |blame, cx| {
19811 blame
19812 .blame_for_rows(
19813 &[RowInfo {
19814 buffer_id: Some(buffer.remote_id()),
19815 buffer_row: Some(point.row),
19816 ..Default::default()
19817 }],
19818 cx,
19819 )
19820 .next()
19821 })
19822 .flatten()?;
19823 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19824 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19825 let workspace = self.workspace()?.downgrade();
19826 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19827 None
19828 }
19829
19830 pub fn git_blame_inline_enabled(&self) -> bool {
19831 self.git_blame_inline_enabled
19832 }
19833
19834 pub fn toggle_selection_menu(
19835 &mut self,
19836 _: &ToggleSelectionMenu,
19837 _: &mut Window,
19838 cx: &mut Context<Self>,
19839 ) {
19840 self.show_selection_menu = self
19841 .show_selection_menu
19842 .map(|show_selections_menu| !show_selections_menu)
19843 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19844
19845 cx.notify();
19846 }
19847
19848 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19849 self.show_selection_menu
19850 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19851 }
19852
19853 fn start_git_blame(
19854 &mut self,
19855 user_triggered: bool,
19856 window: &mut Window,
19857 cx: &mut Context<Self>,
19858 ) {
19859 if let Some(project) = self.project() {
19860 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19861 && buffer.read(cx).file().is_none()
19862 {
19863 return;
19864 }
19865
19866 let focused = self.focus_handle(cx).contains_focused(window, cx);
19867
19868 let project = project.clone();
19869 let blame = cx
19870 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19871 self.blame_subscription =
19872 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19873 self.blame = Some(blame);
19874 }
19875 }
19876
19877 fn toggle_git_blame_inline_internal(
19878 &mut self,
19879 user_triggered: bool,
19880 window: &mut Window,
19881 cx: &mut Context<Self>,
19882 ) {
19883 if self.git_blame_inline_enabled {
19884 self.git_blame_inline_enabled = false;
19885 self.show_git_blame_inline = false;
19886 self.show_git_blame_inline_delay_task.take();
19887 } else {
19888 self.git_blame_inline_enabled = true;
19889 self.start_git_blame_inline(user_triggered, window, cx);
19890 }
19891
19892 cx.notify();
19893 }
19894
19895 fn start_git_blame_inline(
19896 &mut self,
19897 user_triggered: bool,
19898 window: &mut Window,
19899 cx: &mut Context<Self>,
19900 ) {
19901 self.start_git_blame(user_triggered, window, cx);
19902
19903 if ProjectSettings::get_global(cx)
19904 .git
19905 .inline_blame_delay()
19906 .is_some()
19907 {
19908 self.start_inline_blame_timer(window, cx);
19909 } else {
19910 self.show_git_blame_inline = true
19911 }
19912 }
19913
19914 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19915 self.blame.as_ref()
19916 }
19917
19918 pub fn show_git_blame_gutter(&self) -> bool {
19919 self.show_git_blame_gutter
19920 }
19921
19922 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19923 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19924 }
19925
19926 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19927 self.show_git_blame_inline
19928 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19929 && !self.newest_selection_head_on_empty_line(cx)
19930 && self.has_blame_entries(cx)
19931 }
19932
19933 fn has_blame_entries(&self, cx: &App) -> bool {
19934 self.blame()
19935 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19936 }
19937
19938 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19939 let cursor_anchor = self.selections.newest_anchor().head();
19940
19941 let snapshot = self.buffer.read(cx).snapshot(cx);
19942 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19943
19944 snapshot.line_len(buffer_row) == 0
19945 }
19946
19947 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19948 let buffer_and_selection = maybe!({
19949 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19950 let selection_range = selection.range();
19951
19952 let multi_buffer = self.buffer().read(cx);
19953 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19954 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19955
19956 let (buffer, range, _) = if selection.reversed {
19957 buffer_ranges.first()
19958 } else {
19959 buffer_ranges.last()
19960 }?;
19961
19962 let selection = text::ToPoint::to_point(&range.start, buffer).row
19963 ..text::ToPoint::to_point(&range.end, buffer).row;
19964 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19965 });
19966
19967 let Some((buffer, selection)) = buffer_and_selection else {
19968 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19969 };
19970
19971 let Some(project) = self.project() else {
19972 return Task::ready(Err(anyhow!("editor does not have project")));
19973 };
19974
19975 project.update(cx, |project, cx| {
19976 project.get_permalink_to_line(&buffer, selection, cx)
19977 })
19978 }
19979
19980 pub fn copy_permalink_to_line(
19981 &mut self,
19982 _: &CopyPermalinkToLine,
19983 window: &mut Window,
19984 cx: &mut Context<Self>,
19985 ) {
19986 let permalink_task = self.get_permalink_to_line(cx);
19987 let workspace = self.workspace();
19988
19989 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19990 Ok(permalink) => {
19991 cx.update(|_, cx| {
19992 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19993 })
19994 .ok();
19995 }
19996 Err(err) => {
19997 let message = format!("Failed to copy permalink: {err}");
19998
19999 anyhow::Result::<()>::Err(err).log_err();
20000
20001 if let Some(workspace) = workspace {
20002 workspace
20003 .update_in(cx, |workspace, _, cx| {
20004 struct CopyPermalinkToLine;
20005
20006 workspace.show_toast(
20007 Toast::new(
20008 NotificationId::unique::<CopyPermalinkToLine>(),
20009 message,
20010 ),
20011 cx,
20012 )
20013 })
20014 .ok();
20015 }
20016 }
20017 })
20018 .detach();
20019 }
20020
20021 pub fn copy_file_location(
20022 &mut self,
20023 _: &CopyFileLocation,
20024 _: &mut Window,
20025 cx: &mut Context<Self>,
20026 ) {
20027 let selection = self
20028 .selections
20029 .newest::<Point>(&self.display_snapshot(cx))
20030 .start
20031 .row
20032 + 1;
20033 if let Some(file) = self.target_file(cx) {
20034 let path = file.path().display(file.path_style(cx));
20035 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20036 }
20037 }
20038
20039 pub fn open_permalink_to_line(
20040 &mut self,
20041 _: &OpenPermalinkToLine,
20042 window: &mut Window,
20043 cx: &mut Context<Self>,
20044 ) {
20045 let permalink_task = self.get_permalink_to_line(cx);
20046 let workspace = self.workspace();
20047
20048 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20049 Ok(permalink) => {
20050 cx.update(|_, cx| {
20051 cx.open_url(permalink.as_ref());
20052 })
20053 .ok();
20054 }
20055 Err(err) => {
20056 let message = format!("Failed to open permalink: {err}");
20057
20058 anyhow::Result::<()>::Err(err).log_err();
20059
20060 if let Some(workspace) = workspace {
20061 workspace
20062 .update(cx, |workspace, cx| {
20063 struct OpenPermalinkToLine;
20064
20065 workspace.show_toast(
20066 Toast::new(
20067 NotificationId::unique::<OpenPermalinkToLine>(),
20068 message,
20069 ),
20070 cx,
20071 )
20072 })
20073 .ok();
20074 }
20075 }
20076 })
20077 .detach();
20078 }
20079
20080 pub fn insert_uuid_v4(
20081 &mut self,
20082 _: &InsertUuidV4,
20083 window: &mut Window,
20084 cx: &mut Context<Self>,
20085 ) {
20086 self.insert_uuid(UuidVersion::V4, window, cx);
20087 }
20088
20089 pub fn insert_uuid_v7(
20090 &mut self,
20091 _: &InsertUuidV7,
20092 window: &mut Window,
20093 cx: &mut Context<Self>,
20094 ) {
20095 self.insert_uuid(UuidVersion::V7, window, cx);
20096 }
20097
20098 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20099 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20100 self.transact(window, cx, |this, window, cx| {
20101 let edits = this
20102 .selections
20103 .all::<Point>(&this.display_snapshot(cx))
20104 .into_iter()
20105 .map(|selection| {
20106 let uuid = match version {
20107 UuidVersion::V4 => uuid::Uuid::new_v4(),
20108 UuidVersion::V7 => uuid::Uuid::now_v7(),
20109 };
20110
20111 (selection.range(), uuid.to_string())
20112 });
20113 this.edit(edits, cx);
20114 this.refresh_edit_prediction(true, false, window, cx);
20115 });
20116 }
20117
20118 pub fn open_selections_in_multibuffer(
20119 &mut self,
20120 _: &OpenSelectionsInMultibuffer,
20121 window: &mut Window,
20122 cx: &mut Context<Self>,
20123 ) {
20124 let multibuffer = self.buffer.read(cx);
20125
20126 let Some(buffer) = multibuffer.as_singleton() else {
20127 return;
20128 };
20129
20130 let Some(workspace) = self.workspace() else {
20131 return;
20132 };
20133
20134 let title = multibuffer.title(cx).to_string();
20135
20136 let locations = self
20137 .selections
20138 .all_anchors(cx)
20139 .iter()
20140 .map(|selection| {
20141 (
20142 buffer.clone(),
20143 (selection.start.text_anchor..selection.end.text_anchor)
20144 .to_point(buffer.read(cx)),
20145 )
20146 })
20147 .into_group_map();
20148
20149 cx.spawn_in(window, async move |_, cx| {
20150 workspace.update_in(cx, |workspace, window, cx| {
20151 Self::open_locations_in_multibuffer(
20152 workspace,
20153 locations,
20154 format!("Selections for '{title}'"),
20155 false,
20156 MultibufferSelectionMode::All,
20157 window,
20158 cx,
20159 );
20160 })
20161 })
20162 .detach();
20163 }
20164
20165 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20166 /// last highlight added will be used.
20167 ///
20168 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20169 pub fn highlight_rows<T: 'static>(
20170 &mut self,
20171 range: Range<Anchor>,
20172 color: Hsla,
20173 options: RowHighlightOptions,
20174 cx: &mut Context<Self>,
20175 ) {
20176 let snapshot = self.buffer().read(cx).snapshot(cx);
20177 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20178 let ix = row_highlights.binary_search_by(|highlight| {
20179 Ordering::Equal
20180 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20181 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20182 });
20183
20184 if let Err(mut ix) = ix {
20185 let index = post_inc(&mut self.highlight_order);
20186
20187 // If this range intersects with the preceding highlight, then merge it with
20188 // the preceding highlight. Otherwise insert a new highlight.
20189 let mut merged = false;
20190 if ix > 0 {
20191 let prev_highlight = &mut row_highlights[ix - 1];
20192 if prev_highlight
20193 .range
20194 .end
20195 .cmp(&range.start, &snapshot)
20196 .is_ge()
20197 {
20198 ix -= 1;
20199 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20200 prev_highlight.range.end = range.end;
20201 }
20202 merged = true;
20203 prev_highlight.index = index;
20204 prev_highlight.color = color;
20205 prev_highlight.options = options;
20206 }
20207 }
20208
20209 if !merged {
20210 row_highlights.insert(
20211 ix,
20212 RowHighlight {
20213 range,
20214 index,
20215 color,
20216 options,
20217 type_id: TypeId::of::<T>(),
20218 },
20219 );
20220 }
20221
20222 // If any of the following highlights intersect with this one, merge them.
20223 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20224 let highlight = &row_highlights[ix];
20225 if next_highlight
20226 .range
20227 .start
20228 .cmp(&highlight.range.end, &snapshot)
20229 .is_le()
20230 {
20231 if next_highlight
20232 .range
20233 .end
20234 .cmp(&highlight.range.end, &snapshot)
20235 .is_gt()
20236 {
20237 row_highlights[ix].range.end = next_highlight.range.end;
20238 }
20239 row_highlights.remove(ix + 1);
20240 } else {
20241 break;
20242 }
20243 }
20244 }
20245 }
20246
20247 /// Remove any highlighted row ranges of the given type that intersect the
20248 /// given ranges.
20249 pub fn remove_highlighted_rows<T: 'static>(
20250 &mut self,
20251 ranges_to_remove: Vec<Range<Anchor>>,
20252 cx: &mut Context<Self>,
20253 ) {
20254 let snapshot = self.buffer().read(cx).snapshot(cx);
20255 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20256 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20257 row_highlights.retain(|highlight| {
20258 while let Some(range_to_remove) = ranges_to_remove.peek() {
20259 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20260 Ordering::Less | Ordering::Equal => {
20261 ranges_to_remove.next();
20262 }
20263 Ordering::Greater => {
20264 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20265 Ordering::Less | Ordering::Equal => {
20266 return false;
20267 }
20268 Ordering::Greater => break,
20269 }
20270 }
20271 }
20272 }
20273
20274 true
20275 })
20276 }
20277
20278 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20279 pub fn clear_row_highlights<T: 'static>(&mut self) {
20280 self.highlighted_rows.remove(&TypeId::of::<T>());
20281 }
20282
20283 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20284 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20285 self.highlighted_rows
20286 .get(&TypeId::of::<T>())
20287 .map_or(&[] as &[_], |vec| vec.as_slice())
20288 .iter()
20289 .map(|highlight| (highlight.range.clone(), highlight.color))
20290 }
20291
20292 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20293 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20294 /// Allows to ignore certain kinds of highlights.
20295 pub fn highlighted_display_rows(
20296 &self,
20297 window: &mut Window,
20298 cx: &mut App,
20299 ) -> BTreeMap<DisplayRow, LineHighlight> {
20300 let snapshot = self.snapshot(window, cx);
20301 let mut used_highlight_orders = HashMap::default();
20302 self.highlighted_rows
20303 .iter()
20304 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20305 .fold(
20306 BTreeMap::<DisplayRow, LineHighlight>::new(),
20307 |mut unique_rows, highlight| {
20308 let start = highlight.range.start.to_display_point(&snapshot);
20309 let end = highlight.range.end.to_display_point(&snapshot);
20310 let start_row = start.row().0;
20311 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20312 && end.column() == 0
20313 {
20314 end.row().0.saturating_sub(1)
20315 } else {
20316 end.row().0
20317 };
20318 for row in start_row..=end_row {
20319 let used_index =
20320 used_highlight_orders.entry(row).or_insert(highlight.index);
20321 if highlight.index >= *used_index {
20322 *used_index = highlight.index;
20323 unique_rows.insert(
20324 DisplayRow(row),
20325 LineHighlight {
20326 include_gutter: highlight.options.include_gutter,
20327 border: None,
20328 background: highlight.color.into(),
20329 type_id: Some(highlight.type_id),
20330 },
20331 );
20332 }
20333 }
20334 unique_rows
20335 },
20336 )
20337 }
20338
20339 pub fn highlighted_display_row_for_autoscroll(
20340 &self,
20341 snapshot: &DisplaySnapshot,
20342 ) -> Option<DisplayRow> {
20343 self.highlighted_rows
20344 .values()
20345 .flat_map(|highlighted_rows| highlighted_rows.iter())
20346 .filter_map(|highlight| {
20347 if highlight.options.autoscroll {
20348 Some(highlight.range.start.to_display_point(snapshot).row())
20349 } else {
20350 None
20351 }
20352 })
20353 .min()
20354 }
20355
20356 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20357 self.highlight_background::<SearchWithinRange>(
20358 ranges,
20359 |colors| colors.colors().editor_document_highlight_read_background,
20360 cx,
20361 )
20362 }
20363
20364 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20365 self.breadcrumb_header = Some(new_header);
20366 }
20367
20368 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20369 self.clear_background_highlights::<SearchWithinRange>(cx);
20370 }
20371
20372 pub fn highlight_background<T: 'static>(
20373 &mut self,
20374 ranges: &[Range<Anchor>],
20375 color_fetcher: fn(&Theme) -> Hsla,
20376 cx: &mut Context<Self>,
20377 ) {
20378 self.background_highlights.insert(
20379 HighlightKey::Type(TypeId::of::<T>()),
20380 (color_fetcher, Arc::from(ranges)),
20381 );
20382 self.scrollbar_marker_state.dirty = true;
20383 cx.notify();
20384 }
20385
20386 pub fn highlight_background_key<T: 'static>(
20387 &mut self,
20388 key: usize,
20389 ranges: &[Range<Anchor>],
20390 color_fetcher: fn(&Theme) -> Hsla,
20391 cx: &mut Context<Self>,
20392 ) {
20393 self.background_highlights.insert(
20394 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20395 (color_fetcher, Arc::from(ranges)),
20396 );
20397 self.scrollbar_marker_state.dirty = true;
20398 cx.notify();
20399 }
20400
20401 pub fn clear_background_highlights<T: 'static>(
20402 &mut self,
20403 cx: &mut Context<Self>,
20404 ) -> Option<BackgroundHighlight> {
20405 let text_highlights = self
20406 .background_highlights
20407 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20408 if !text_highlights.1.is_empty() {
20409 self.scrollbar_marker_state.dirty = true;
20410 cx.notify();
20411 }
20412 Some(text_highlights)
20413 }
20414
20415 pub fn highlight_gutter<T: 'static>(
20416 &mut self,
20417 ranges: impl Into<Vec<Range<Anchor>>>,
20418 color_fetcher: fn(&App) -> Hsla,
20419 cx: &mut Context<Self>,
20420 ) {
20421 self.gutter_highlights
20422 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20423 cx.notify();
20424 }
20425
20426 pub fn clear_gutter_highlights<T: 'static>(
20427 &mut self,
20428 cx: &mut Context<Self>,
20429 ) -> Option<GutterHighlight> {
20430 cx.notify();
20431 self.gutter_highlights.remove(&TypeId::of::<T>())
20432 }
20433
20434 pub fn insert_gutter_highlight<T: 'static>(
20435 &mut self,
20436 range: Range<Anchor>,
20437 color_fetcher: fn(&App) -> Hsla,
20438 cx: &mut Context<Self>,
20439 ) {
20440 let snapshot = self.buffer().read(cx).snapshot(cx);
20441 let mut highlights = self
20442 .gutter_highlights
20443 .remove(&TypeId::of::<T>())
20444 .map(|(_, highlights)| highlights)
20445 .unwrap_or_default();
20446 let ix = highlights.binary_search_by(|highlight| {
20447 Ordering::Equal
20448 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20449 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20450 });
20451 if let Err(ix) = ix {
20452 highlights.insert(ix, range);
20453 }
20454 self.gutter_highlights
20455 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20456 }
20457
20458 pub fn remove_gutter_highlights<T: 'static>(
20459 &mut self,
20460 ranges_to_remove: Vec<Range<Anchor>>,
20461 cx: &mut Context<Self>,
20462 ) {
20463 let snapshot = self.buffer().read(cx).snapshot(cx);
20464 let Some((color_fetcher, mut gutter_highlights)) =
20465 self.gutter_highlights.remove(&TypeId::of::<T>())
20466 else {
20467 return;
20468 };
20469 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20470 gutter_highlights.retain(|highlight| {
20471 while let Some(range_to_remove) = ranges_to_remove.peek() {
20472 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20473 Ordering::Less | Ordering::Equal => {
20474 ranges_to_remove.next();
20475 }
20476 Ordering::Greater => {
20477 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20478 Ordering::Less | Ordering::Equal => {
20479 return false;
20480 }
20481 Ordering::Greater => break,
20482 }
20483 }
20484 }
20485 }
20486
20487 true
20488 });
20489 self.gutter_highlights
20490 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20491 }
20492
20493 #[cfg(feature = "test-support")]
20494 pub fn all_text_highlights(
20495 &self,
20496 window: &mut Window,
20497 cx: &mut Context<Self>,
20498 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20499 let snapshot = self.snapshot(window, cx);
20500 self.display_map.update(cx, |display_map, _| {
20501 display_map
20502 .all_text_highlights()
20503 .map(|highlight| {
20504 let (style, ranges) = highlight.as_ref();
20505 (
20506 *style,
20507 ranges
20508 .iter()
20509 .map(|range| range.clone().to_display_points(&snapshot))
20510 .collect(),
20511 )
20512 })
20513 .collect()
20514 })
20515 }
20516
20517 #[cfg(feature = "test-support")]
20518 pub fn all_text_background_highlights(
20519 &self,
20520 window: &mut Window,
20521 cx: &mut Context<Self>,
20522 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20523 let snapshot = self.snapshot(window, cx);
20524 let buffer = &snapshot.buffer_snapshot();
20525 let start = buffer.anchor_before(0);
20526 let end = buffer.anchor_after(buffer.len());
20527 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20528 }
20529
20530 #[cfg(any(test, feature = "test-support"))]
20531 pub fn sorted_background_highlights_in_range(
20532 &self,
20533 search_range: Range<Anchor>,
20534 display_snapshot: &DisplaySnapshot,
20535 theme: &Theme,
20536 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20537 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20538 res.sort_by(|a, b| {
20539 a.0.start
20540 .cmp(&b.0.start)
20541 .then_with(|| a.0.end.cmp(&b.0.end))
20542 .then_with(|| a.1.cmp(&b.1))
20543 });
20544 res
20545 }
20546
20547 #[cfg(feature = "test-support")]
20548 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20549 let snapshot = self.buffer().read(cx).snapshot(cx);
20550
20551 let highlights = self
20552 .background_highlights
20553 .get(&HighlightKey::Type(TypeId::of::<
20554 items::BufferSearchHighlights,
20555 >()));
20556
20557 if let Some((_color, ranges)) = highlights {
20558 ranges
20559 .iter()
20560 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20561 .collect_vec()
20562 } else {
20563 vec![]
20564 }
20565 }
20566
20567 fn document_highlights_for_position<'a>(
20568 &'a self,
20569 position: Anchor,
20570 buffer: &'a MultiBufferSnapshot,
20571 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20572 let read_highlights = self
20573 .background_highlights
20574 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20575 .map(|h| &h.1);
20576 let write_highlights = self
20577 .background_highlights
20578 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20579 .map(|h| &h.1);
20580 let left_position = position.bias_left(buffer);
20581 let right_position = position.bias_right(buffer);
20582 read_highlights
20583 .into_iter()
20584 .chain(write_highlights)
20585 .flat_map(move |ranges| {
20586 let start_ix = match ranges.binary_search_by(|probe| {
20587 let cmp = probe.end.cmp(&left_position, buffer);
20588 if cmp.is_ge() {
20589 Ordering::Greater
20590 } else {
20591 Ordering::Less
20592 }
20593 }) {
20594 Ok(i) | Err(i) => i,
20595 };
20596
20597 ranges[start_ix..]
20598 .iter()
20599 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20600 })
20601 }
20602
20603 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20604 self.background_highlights
20605 .get(&HighlightKey::Type(TypeId::of::<T>()))
20606 .is_some_and(|(_, highlights)| !highlights.is_empty())
20607 }
20608
20609 /// Returns all background highlights for a given range.
20610 ///
20611 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20612 pub fn background_highlights_in_range(
20613 &self,
20614 search_range: Range<Anchor>,
20615 display_snapshot: &DisplaySnapshot,
20616 theme: &Theme,
20617 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20618 let mut results = Vec::new();
20619 for (color_fetcher, ranges) in self.background_highlights.values() {
20620 let color = color_fetcher(theme);
20621 let start_ix = match ranges.binary_search_by(|probe| {
20622 let cmp = probe
20623 .end
20624 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20625 if cmp.is_gt() {
20626 Ordering::Greater
20627 } else {
20628 Ordering::Less
20629 }
20630 }) {
20631 Ok(i) | Err(i) => i,
20632 };
20633 for range in &ranges[start_ix..] {
20634 if range
20635 .start
20636 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20637 .is_ge()
20638 {
20639 break;
20640 }
20641
20642 let start = range.start.to_display_point(display_snapshot);
20643 let end = range.end.to_display_point(display_snapshot);
20644 results.push((start..end, color))
20645 }
20646 }
20647 results
20648 }
20649
20650 pub fn gutter_highlights_in_range(
20651 &self,
20652 search_range: Range<Anchor>,
20653 display_snapshot: &DisplaySnapshot,
20654 cx: &App,
20655 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20656 let mut results = Vec::new();
20657 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20658 let color = color_fetcher(cx);
20659 let start_ix = match ranges.binary_search_by(|probe| {
20660 let cmp = probe
20661 .end
20662 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20663 if cmp.is_gt() {
20664 Ordering::Greater
20665 } else {
20666 Ordering::Less
20667 }
20668 }) {
20669 Ok(i) | Err(i) => i,
20670 };
20671 for range in &ranges[start_ix..] {
20672 if range
20673 .start
20674 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20675 .is_ge()
20676 {
20677 break;
20678 }
20679
20680 let start = range.start.to_display_point(display_snapshot);
20681 let end = range.end.to_display_point(display_snapshot);
20682 results.push((start..end, color))
20683 }
20684 }
20685 results
20686 }
20687
20688 /// Get the text ranges corresponding to the redaction query
20689 pub fn redacted_ranges(
20690 &self,
20691 search_range: Range<Anchor>,
20692 display_snapshot: &DisplaySnapshot,
20693 cx: &App,
20694 ) -> Vec<Range<DisplayPoint>> {
20695 display_snapshot
20696 .buffer_snapshot()
20697 .redacted_ranges(search_range, |file| {
20698 if let Some(file) = file {
20699 file.is_private()
20700 && EditorSettings::get(
20701 Some(SettingsLocation {
20702 worktree_id: file.worktree_id(cx),
20703 path: file.path().as_ref(),
20704 }),
20705 cx,
20706 )
20707 .redact_private_values
20708 } else {
20709 false
20710 }
20711 })
20712 .map(|range| {
20713 range.start.to_display_point(display_snapshot)
20714 ..range.end.to_display_point(display_snapshot)
20715 })
20716 .collect()
20717 }
20718
20719 pub fn highlight_text_key<T: 'static>(
20720 &mut self,
20721 key: usize,
20722 ranges: Vec<Range<Anchor>>,
20723 style: HighlightStyle,
20724 cx: &mut Context<Self>,
20725 ) {
20726 self.display_map.update(cx, |map, _| {
20727 map.highlight_text(
20728 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20729 ranges,
20730 style,
20731 );
20732 });
20733 cx.notify();
20734 }
20735
20736 pub fn highlight_text<T: 'static>(
20737 &mut self,
20738 ranges: Vec<Range<Anchor>>,
20739 style: HighlightStyle,
20740 cx: &mut Context<Self>,
20741 ) {
20742 self.display_map.update(cx, |map, _| {
20743 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20744 });
20745 cx.notify();
20746 }
20747
20748 pub fn text_highlights<'a, T: 'static>(
20749 &'a self,
20750 cx: &'a App,
20751 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20752 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20753 }
20754
20755 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20756 let cleared = self
20757 .display_map
20758 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20759 if cleared {
20760 cx.notify();
20761 }
20762 }
20763
20764 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20765 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20766 && self.focus_handle.is_focused(window)
20767 }
20768
20769 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20770 self.show_cursor_when_unfocused = is_enabled;
20771 cx.notify();
20772 }
20773
20774 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20775 cx.notify();
20776 }
20777
20778 fn on_debug_session_event(
20779 &mut self,
20780 _session: Entity<Session>,
20781 event: &SessionEvent,
20782 cx: &mut Context<Self>,
20783 ) {
20784 if let SessionEvent::InvalidateInlineValue = event {
20785 self.refresh_inline_values(cx);
20786 }
20787 }
20788
20789 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20790 let Some(project) = self.project.clone() else {
20791 return;
20792 };
20793
20794 if !self.inline_value_cache.enabled {
20795 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20796 self.splice_inlays(&inlays, Vec::new(), cx);
20797 return;
20798 }
20799
20800 let current_execution_position = self
20801 .highlighted_rows
20802 .get(&TypeId::of::<ActiveDebugLine>())
20803 .and_then(|lines| lines.last().map(|line| line.range.end));
20804
20805 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20806 let inline_values = editor
20807 .update(cx, |editor, cx| {
20808 let Some(current_execution_position) = current_execution_position else {
20809 return Some(Task::ready(Ok(Vec::new())));
20810 };
20811
20812 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20813 let snapshot = buffer.snapshot(cx);
20814
20815 let excerpt = snapshot.excerpt_containing(
20816 current_execution_position..current_execution_position,
20817 )?;
20818
20819 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20820 })?;
20821
20822 let range =
20823 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20824
20825 project.inline_values(buffer, range, cx)
20826 })
20827 .ok()
20828 .flatten()?
20829 .await
20830 .context("refreshing debugger inlays")
20831 .log_err()?;
20832
20833 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20834
20835 for (buffer_id, inline_value) in inline_values
20836 .into_iter()
20837 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20838 {
20839 buffer_inline_values
20840 .entry(buffer_id)
20841 .or_default()
20842 .push(inline_value);
20843 }
20844
20845 editor
20846 .update(cx, |editor, cx| {
20847 let snapshot = editor.buffer.read(cx).snapshot(cx);
20848 let mut new_inlays = Vec::default();
20849
20850 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20851 let buffer_id = buffer_snapshot.remote_id();
20852 buffer_inline_values
20853 .get(&buffer_id)
20854 .into_iter()
20855 .flatten()
20856 .for_each(|hint| {
20857 let inlay = Inlay::debugger(
20858 post_inc(&mut editor.next_inlay_id),
20859 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20860 hint.text(),
20861 );
20862 if !inlay.text().chars().contains(&'\n') {
20863 new_inlays.push(inlay);
20864 }
20865 });
20866 }
20867
20868 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20869 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20870
20871 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20872 })
20873 .ok()?;
20874 Some(())
20875 });
20876 }
20877
20878 fn on_buffer_event(
20879 &mut self,
20880 multibuffer: &Entity<MultiBuffer>,
20881 event: &multi_buffer::Event,
20882 window: &mut Window,
20883 cx: &mut Context<Self>,
20884 ) {
20885 match event {
20886 multi_buffer::Event::Edited { edited_buffer } => {
20887 self.scrollbar_marker_state.dirty = true;
20888 self.active_indent_guides_state.dirty = true;
20889 self.refresh_active_diagnostics(cx);
20890 self.refresh_code_actions(window, cx);
20891 self.refresh_selected_text_highlights(true, window, cx);
20892 self.refresh_single_line_folds(window, cx);
20893 self.refresh_matching_bracket_highlights(window, cx);
20894 if self.has_active_edit_prediction() {
20895 self.update_visible_edit_prediction(window, cx);
20896 }
20897
20898 if let Some(buffer) = edited_buffer {
20899 if buffer.read(cx).file().is_none() {
20900 cx.emit(EditorEvent::TitleChanged);
20901 }
20902
20903 if self.project.is_some() {
20904 let buffer_id = buffer.read(cx).remote_id();
20905 self.register_buffer(buffer_id, cx);
20906 self.update_lsp_data(Some(buffer_id), window, cx);
20907 self.refresh_inlay_hints(
20908 InlayHintRefreshReason::BufferEdited(buffer_id),
20909 cx,
20910 );
20911 }
20912 }
20913
20914 cx.emit(EditorEvent::BufferEdited);
20915 cx.emit(SearchEvent::MatchesInvalidated);
20916
20917 let Some(project) = &self.project else { return };
20918 let (telemetry, is_via_ssh) = {
20919 let project = project.read(cx);
20920 let telemetry = project.client().telemetry().clone();
20921 let is_via_ssh = project.is_via_remote_server();
20922 (telemetry, is_via_ssh)
20923 };
20924 telemetry.log_edit_event("editor", is_via_ssh);
20925 }
20926 multi_buffer::Event::ExcerptsAdded {
20927 buffer,
20928 predecessor,
20929 excerpts,
20930 } => {
20931 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20932 let buffer_id = buffer.read(cx).remote_id();
20933 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20934 && let Some(project) = &self.project
20935 {
20936 update_uncommitted_diff_for_buffer(
20937 cx.entity(),
20938 project,
20939 [buffer.clone()],
20940 self.buffer.clone(),
20941 cx,
20942 )
20943 .detach();
20944 }
20945 self.update_lsp_data(Some(buffer_id), window, cx);
20946 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20947 cx.emit(EditorEvent::ExcerptsAdded {
20948 buffer: buffer.clone(),
20949 predecessor: *predecessor,
20950 excerpts: excerpts.clone(),
20951 });
20952 }
20953 multi_buffer::Event::ExcerptsRemoved {
20954 ids,
20955 removed_buffer_ids,
20956 } => {
20957 if let Some(inlay_hints) = &mut self.inlay_hints {
20958 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
20959 }
20960 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20961 for buffer_id in removed_buffer_ids {
20962 self.registered_buffers.remove(buffer_id);
20963 }
20964 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20965 cx.emit(EditorEvent::ExcerptsRemoved {
20966 ids: ids.clone(),
20967 removed_buffer_ids: removed_buffer_ids.clone(),
20968 });
20969 }
20970 multi_buffer::Event::ExcerptsEdited {
20971 excerpt_ids,
20972 buffer_ids,
20973 } => {
20974 self.display_map.update(cx, |map, cx| {
20975 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20976 });
20977 cx.emit(EditorEvent::ExcerptsEdited {
20978 ids: excerpt_ids.clone(),
20979 });
20980 }
20981 multi_buffer::Event::ExcerptsExpanded { ids } => {
20982 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20983 self.refresh_document_highlights(cx);
20984 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20985 }
20986 multi_buffer::Event::Reparsed(buffer_id) => {
20987 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20988 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20989
20990 cx.emit(EditorEvent::Reparsed(*buffer_id));
20991 }
20992 multi_buffer::Event::DiffHunksToggled => {
20993 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20994 }
20995 multi_buffer::Event::LanguageChanged(buffer_id) => {
20996 self.registered_buffers.remove(&buffer_id);
20997 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20998 cx.emit(EditorEvent::Reparsed(*buffer_id));
20999 cx.notify();
21000 }
21001 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21002 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21003 multi_buffer::Event::FileHandleChanged
21004 | multi_buffer::Event::Reloaded
21005 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21006 multi_buffer::Event::DiagnosticsUpdated => {
21007 self.update_diagnostics_state(window, cx);
21008 }
21009 _ => {}
21010 };
21011 }
21012
21013 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21014 if !self.diagnostics_enabled() {
21015 return;
21016 }
21017 self.refresh_active_diagnostics(cx);
21018 self.refresh_inline_diagnostics(true, window, cx);
21019 self.scrollbar_marker_state.dirty = true;
21020 cx.notify();
21021 }
21022
21023 pub fn start_temporary_diff_override(&mut self) {
21024 self.load_diff_task.take();
21025 self.temporary_diff_override = true;
21026 }
21027
21028 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21029 self.temporary_diff_override = false;
21030 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21031 self.buffer.update(cx, |buffer, cx| {
21032 buffer.set_all_diff_hunks_collapsed(cx);
21033 });
21034
21035 if let Some(project) = self.project.clone() {
21036 self.load_diff_task = Some(
21037 update_uncommitted_diff_for_buffer(
21038 cx.entity(),
21039 &project,
21040 self.buffer.read(cx).all_buffers(),
21041 self.buffer.clone(),
21042 cx,
21043 )
21044 .shared(),
21045 );
21046 }
21047 }
21048
21049 fn on_display_map_changed(
21050 &mut self,
21051 _: Entity<DisplayMap>,
21052 _: &mut Window,
21053 cx: &mut Context<Self>,
21054 ) {
21055 cx.notify();
21056 }
21057
21058 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21059 if self.diagnostics_enabled() {
21060 let new_severity = EditorSettings::get_global(cx)
21061 .diagnostics_max_severity
21062 .unwrap_or(DiagnosticSeverity::Hint);
21063 self.set_max_diagnostics_severity(new_severity, cx);
21064 }
21065 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21066 self.update_edit_prediction_settings(cx);
21067 self.refresh_edit_prediction(true, false, window, cx);
21068 self.refresh_inline_values(cx);
21069 self.refresh_inlay_hints(
21070 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21071 self.selections.newest_anchor().head(),
21072 &self.buffer.read(cx).snapshot(cx),
21073 cx,
21074 )),
21075 cx,
21076 );
21077
21078 let old_cursor_shape = self.cursor_shape;
21079 let old_show_breadcrumbs = self.show_breadcrumbs;
21080
21081 {
21082 let editor_settings = EditorSettings::get_global(cx);
21083 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21084 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21085 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21086 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21087 }
21088
21089 if old_cursor_shape != self.cursor_shape {
21090 cx.emit(EditorEvent::CursorShapeChanged);
21091 }
21092
21093 if old_show_breadcrumbs != self.show_breadcrumbs {
21094 cx.emit(EditorEvent::BreadcrumbsChanged);
21095 }
21096
21097 let project_settings = ProjectSettings::get_global(cx);
21098 self.serialize_dirty_buffers =
21099 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21100
21101 if self.mode.is_full() {
21102 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21103 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21104 if self.show_inline_diagnostics != show_inline_diagnostics {
21105 self.show_inline_diagnostics = show_inline_diagnostics;
21106 self.refresh_inline_diagnostics(false, window, cx);
21107 }
21108
21109 if self.git_blame_inline_enabled != inline_blame_enabled {
21110 self.toggle_git_blame_inline_internal(false, window, cx);
21111 }
21112
21113 let minimap_settings = EditorSettings::get_global(cx).minimap;
21114 if self.minimap_visibility != MinimapVisibility::Disabled {
21115 if self.minimap_visibility.settings_visibility()
21116 != minimap_settings.minimap_enabled()
21117 {
21118 self.set_minimap_visibility(
21119 MinimapVisibility::for_mode(self.mode(), cx),
21120 window,
21121 cx,
21122 );
21123 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21124 minimap_entity.update(cx, |minimap_editor, cx| {
21125 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21126 })
21127 }
21128 }
21129 }
21130
21131 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21132 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21133 }) {
21134 if !inlay_splice.is_empty() {
21135 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21136 }
21137 self.refresh_colors_for_visible_range(None, window, cx);
21138 }
21139
21140 cx.notify();
21141 }
21142
21143 pub fn set_searchable(&mut self, searchable: bool) {
21144 self.searchable = searchable;
21145 }
21146
21147 pub fn searchable(&self) -> bool {
21148 self.searchable
21149 }
21150
21151 pub fn open_excerpts_in_split(
21152 &mut self,
21153 _: &OpenExcerptsSplit,
21154 window: &mut Window,
21155 cx: &mut Context<Self>,
21156 ) {
21157 self.open_excerpts_common(None, true, window, cx)
21158 }
21159
21160 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21161 self.open_excerpts_common(None, false, window, cx)
21162 }
21163
21164 fn open_excerpts_common(
21165 &mut self,
21166 jump_data: Option<JumpData>,
21167 split: bool,
21168 window: &mut Window,
21169 cx: &mut Context<Self>,
21170 ) {
21171 let Some(workspace) = self.workspace() else {
21172 cx.propagate();
21173 return;
21174 };
21175
21176 if self.buffer.read(cx).is_singleton() {
21177 cx.propagate();
21178 return;
21179 }
21180
21181 let mut new_selections_by_buffer = HashMap::default();
21182 match &jump_data {
21183 Some(JumpData::MultiBufferPoint {
21184 excerpt_id,
21185 position,
21186 anchor,
21187 line_offset_from_top,
21188 }) => {
21189 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21190 if let Some(buffer) = multi_buffer_snapshot
21191 .buffer_id_for_excerpt(*excerpt_id)
21192 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21193 {
21194 let buffer_snapshot = buffer.read(cx).snapshot();
21195 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21196 language::ToPoint::to_point(anchor, &buffer_snapshot)
21197 } else {
21198 buffer_snapshot.clip_point(*position, Bias::Left)
21199 };
21200 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21201 new_selections_by_buffer.insert(
21202 buffer,
21203 (
21204 vec![jump_to_offset..jump_to_offset],
21205 Some(*line_offset_from_top),
21206 ),
21207 );
21208 }
21209 }
21210 Some(JumpData::MultiBufferRow {
21211 row,
21212 line_offset_from_top,
21213 }) => {
21214 let point = MultiBufferPoint::new(row.0, 0);
21215 if let Some((buffer, buffer_point, _)) =
21216 self.buffer.read(cx).point_to_buffer_point(point, cx)
21217 {
21218 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21219 new_selections_by_buffer
21220 .entry(buffer)
21221 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21222 .0
21223 .push(buffer_offset..buffer_offset)
21224 }
21225 }
21226 None => {
21227 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21228 let multi_buffer = self.buffer.read(cx);
21229 for selection in selections {
21230 for (snapshot, range, _, anchor) in multi_buffer
21231 .snapshot(cx)
21232 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21233 {
21234 if let Some(anchor) = anchor {
21235 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21236 else {
21237 continue;
21238 };
21239 let offset = text::ToOffset::to_offset(
21240 &anchor.text_anchor,
21241 &buffer_handle.read(cx).snapshot(),
21242 );
21243 let range = offset..offset;
21244 new_selections_by_buffer
21245 .entry(buffer_handle)
21246 .or_insert((Vec::new(), None))
21247 .0
21248 .push(range)
21249 } else {
21250 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21251 else {
21252 continue;
21253 };
21254 new_selections_by_buffer
21255 .entry(buffer_handle)
21256 .or_insert((Vec::new(), None))
21257 .0
21258 .push(range)
21259 }
21260 }
21261 }
21262 }
21263 }
21264
21265 new_selections_by_buffer
21266 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21267
21268 if new_selections_by_buffer.is_empty() {
21269 return;
21270 }
21271
21272 // We defer the pane interaction because we ourselves are a workspace item
21273 // and activating a new item causes the pane to call a method on us reentrantly,
21274 // which panics if we're on the stack.
21275 window.defer(cx, move |window, cx| {
21276 workspace.update(cx, |workspace, cx| {
21277 let pane = if split {
21278 workspace.adjacent_pane(window, cx)
21279 } else {
21280 workspace.active_pane().clone()
21281 };
21282
21283 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21284 let editor = buffer
21285 .read(cx)
21286 .file()
21287 .is_none()
21288 .then(|| {
21289 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21290 // so `workspace.open_project_item` will never find them, always opening a new editor.
21291 // Instead, we try to activate the existing editor in the pane first.
21292 let (editor, pane_item_index) =
21293 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21294 let editor = item.downcast::<Editor>()?;
21295 let singleton_buffer =
21296 editor.read(cx).buffer().read(cx).as_singleton()?;
21297 if singleton_buffer == buffer {
21298 Some((editor, i))
21299 } else {
21300 None
21301 }
21302 })?;
21303 pane.update(cx, |pane, cx| {
21304 pane.activate_item(pane_item_index, true, true, window, cx)
21305 });
21306 Some(editor)
21307 })
21308 .flatten()
21309 .unwrap_or_else(|| {
21310 workspace.open_project_item::<Self>(
21311 pane.clone(),
21312 buffer,
21313 true,
21314 true,
21315 window,
21316 cx,
21317 )
21318 });
21319
21320 editor.update(cx, |editor, cx| {
21321 let autoscroll = match scroll_offset {
21322 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21323 None => Autoscroll::newest(),
21324 };
21325 let nav_history = editor.nav_history.take();
21326 editor.change_selections(
21327 SelectionEffects::scroll(autoscroll),
21328 window,
21329 cx,
21330 |s| {
21331 s.select_ranges(ranges);
21332 },
21333 );
21334 editor.nav_history = nav_history;
21335 });
21336 }
21337 })
21338 });
21339 }
21340
21341 // For now, don't allow opening excerpts in buffers that aren't backed by
21342 // regular project files.
21343 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21344 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21345 }
21346
21347 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21348 let snapshot = self.buffer.read(cx).read(cx);
21349 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21350 Some(
21351 ranges
21352 .iter()
21353 .map(move |range| {
21354 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21355 })
21356 .collect(),
21357 )
21358 }
21359
21360 fn selection_replacement_ranges(
21361 &self,
21362 range: Range<OffsetUtf16>,
21363 cx: &mut App,
21364 ) -> Vec<Range<OffsetUtf16>> {
21365 let selections = self
21366 .selections
21367 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21368 let newest_selection = selections
21369 .iter()
21370 .max_by_key(|selection| selection.id)
21371 .unwrap();
21372 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21373 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21374 let snapshot = self.buffer.read(cx).read(cx);
21375 selections
21376 .into_iter()
21377 .map(|mut selection| {
21378 selection.start.0 =
21379 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21380 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21381 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21382 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21383 })
21384 .collect()
21385 }
21386
21387 fn report_editor_event(
21388 &self,
21389 reported_event: ReportEditorEvent,
21390 file_extension: Option<String>,
21391 cx: &App,
21392 ) {
21393 if cfg!(any(test, feature = "test-support")) {
21394 return;
21395 }
21396
21397 let Some(project) = &self.project else { return };
21398
21399 // If None, we are in a file without an extension
21400 let file = self
21401 .buffer
21402 .read(cx)
21403 .as_singleton()
21404 .and_then(|b| b.read(cx).file());
21405 let file_extension = file_extension.or(file
21406 .as_ref()
21407 .and_then(|file| Path::new(file.file_name(cx)).extension())
21408 .and_then(|e| e.to_str())
21409 .map(|a| a.to_string()));
21410
21411 let vim_mode = vim_enabled(cx);
21412
21413 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21414 let copilot_enabled = edit_predictions_provider
21415 == language::language_settings::EditPredictionProvider::Copilot;
21416 let copilot_enabled_for_language = self
21417 .buffer
21418 .read(cx)
21419 .language_settings(cx)
21420 .show_edit_predictions;
21421
21422 let project = project.read(cx);
21423 let event_type = reported_event.event_type();
21424
21425 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21426 telemetry::event!(
21427 event_type,
21428 type = if auto_saved {"autosave"} else {"manual"},
21429 file_extension,
21430 vim_mode,
21431 copilot_enabled,
21432 copilot_enabled_for_language,
21433 edit_predictions_provider,
21434 is_via_ssh = project.is_via_remote_server(),
21435 );
21436 } else {
21437 telemetry::event!(
21438 event_type,
21439 file_extension,
21440 vim_mode,
21441 copilot_enabled,
21442 copilot_enabled_for_language,
21443 edit_predictions_provider,
21444 is_via_ssh = project.is_via_remote_server(),
21445 );
21446 };
21447 }
21448
21449 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21450 /// with each line being an array of {text, highlight} objects.
21451 fn copy_highlight_json(
21452 &mut self,
21453 _: &CopyHighlightJson,
21454 window: &mut Window,
21455 cx: &mut Context<Self>,
21456 ) {
21457 #[derive(Serialize)]
21458 struct Chunk<'a> {
21459 text: String,
21460 highlight: Option<&'a str>,
21461 }
21462
21463 let snapshot = self.buffer.read(cx).snapshot(cx);
21464 let range = self
21465 .selected_text_range(false, window, cx)
21466 .and_then(|selection| {
21467 if selection.range.is_empty() {
21468 None
21469 } else {
21470 Some(
21471 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21472 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21473 )
21474 }
21475 })
21476 .unwrap_or_else(|| 0..snapshot.len());
21477
21478 let chunks = snapshot.chunks(range, true);
21479 let mut lines = Vec::new();
21480 let mut line: VecDeque<Chunk> = VecDeque::new();
21481
21482 let Some(style) = self.style.as_ref() else {
21483 return;
21484 };
21485
21486 for chunk in chunks {
21487 let highlight = chunk
21488 .syntax_highlight_id
21489 .and_then(|id| id.name(&style.syntax));
21490 let mut chunk_lines = chunk.text.split('\n').peekable();
21491 while let Some(text) = chunk_lines.next() {
21492 let mut merged_with_last_token = false;
21493 if let Some(last_token) = line.back_mut()
21494 && last_token.highlight == highlight
21495 {
21496 last_token.text.push_str(text);
21497 merged_with_last_token = true;
21498 }
21499
21500 if !merged_with_last_token {
21501 line.push_back(Chunk {
21502 text: text.into(),
21503 highlight,
21504 });
21505 }
21506
21507 if chunk_lines.peek().is_some() {
21508 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21509 line.pop_front();
21510 }
21511 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21512 line.pop_back();
21513 }
21514
21515 lines.push(mem::take(&mut line));
21516 }
21517 }
21518 }
21519
21520 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21521 return;
21522 };
21523 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21524 }
21525
21526 pub fn open_context_menu(
21527 &mut self,
21528 _: &OpenContextMenu,
21529 window: &mut Window,
21530 cx: &mut Context<Self>,
21531 ) {
21532 self.request_autoscroll(Autoscroll::newest(), cx);
21533 let position = self
21534 .selections
21535 .newest_display(&self.display_snapshot(cx))
21536 .start;
21537 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21538 }
21539
21540 pub fn replay_insert_event(
21541 &mut self,
21542 text: &str,
21543 relative_utf16_range: Option<Range<isize>>,
21544 window: &mut Window,
21545 cx: &mut Context<Self>,
21546 ) {
21547 if !self.input_enabled {
21548 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21549 return;
21550 }
21551 if let Some(relative_utf16_range) = relative_utf16_range {
21552 let selections = self
21553 .selections
21554 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21555 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21556 let new_ranges = selections.into_iter().map(|range| {
21557 let start = OffsetUtf16(
21558 range
21559 .head()
21560 .0
21561 .saturating_add_signed(relative_utf16_range.start),
21562 );
21563 let end = OffsetUtf16(
21564 range
21565 .head()
21566 .0
21567 .saturating_add_signed(relative_utf16_range.end),
21568 );
21569 start..end
21570 });
21571 s.select_ranges(new_ranges);
21572 });
21573 }
21574
21575 self.handle_input(text, window, cx);
21576 }
21577
21578 pub fn is_focused(&self, window: &Window) -> bool {
21579 self.focus_handle.is_focused(window)
21580 }
21581
21582 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21583 cx.emit(EditorEvent::Focused);
21584
21585 if let Some(descendant) = self
21586 .last_focused_descendant
21587 .take()
21588 .and_then(|descendant| descendant.upgrade())
21589 {
21590 window.focus(&descendant);
21591 } else {
21592 if let Some(blame) = self.blame.as_ref() {
21593 blame.update(cx, GitBlame::focus)
21594 }
21595
21596 self.blink_manager.update(cx, BlinkManager::enable);
21597 self.show_cursor_names(window, cx);
21598 self.buffer.update(cx, |buffer, cx| {
21599 buffer.finalize_last_transaction(cx);
21600 if self.leader_id.is_none() {
21601 buffer.set_active_selections(
21602 &self.selections.disjoint_anchors_arc(),
21603 self.selections.line_mode(),
21604 self.cursor_shape,
21605 cx,
21606 );
21607 }
21608 });
21609 }
21610 }
21611
21612 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21613 cx.emit(EditorEvent::FocusedIn)
21614 }
21615
21616 fn handle_focus_out(
21617 &mut self,
21618 event: FocusOutEvent,
21619 _window: &mut Window,
21620 cx: &mut Context<Self>,
21621 ) {
21622 if event.blurred != self.focus_handle {
21623 self.last_focused_descendant = Some(event.blurred);
21624 }
21625 self.selection_drag_state = SelectionDragState::None;
21626 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21627 }
21628
21629 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21630 self.blink_manager.update(cx, BlinkManager::disable);
21631 self.buffer
21632 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21633
21634 if let Some(blame) = self.blame.as_ref() {
21635 blame.update(cx, GitBlame::blur)
21636 }
21637 if !self.hover_state.focused(window, cx) {
21638 hide_hover(self, cx);
21639 }
21640 if !self
21641 .context_menu
21642 .borrow()
21643 .as_ref()
21644 .is_some_and(|context_menu| context_menu.focused(window, cx))
21645 {
21646 self.hide_context_menu(window, cx);
21647 }
21648 self.take_active_edit_prediction(cx);
21649 cx.emit(EditorEvent::Blurred);
21650 cx.notify();
21651 }
21652
21653 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21654 let mut pending: String = window
21655 .pending_input_keystrokes()
21656 .into_iter()
21657 .flatten()
21658 .filter_map(|keystroke| {
21659 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21660 keystroke.key_char.clone()
21661 } else {
21662 None
21663 }
21664 })
21665 .collect();
21666
21667 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21668 pending = "".to_string();
21669 }
21670
21671 let existing_pending = self
21672 .text_highlights::<PendingInput>(cx)
21673 .map(|(_, ranges)| ranges.to_vec());
21674 if existing_pending.is_none() && pending.is_empty() {
21675 return;
21676 }
21677 let transaction =
21678 self.transact(window, cx, |this, window, cx| {
21679 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21680 let edits = selections
21681 .iter()
21682 .map(|selection| (selection.end..selection.end, pending.clone()));
21683 this.edit(edits, cx);
21684 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21685 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21686 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21687 }));
21688 });
21689 if let Some(existing_ranges) = existing_pending {
21690 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21691 this.edit(edits, cx);
21692 }
21693 });
21694
21695 let snapshot = self.snapshot(window, cx);
21696 let ranges = self
21697 .selections
21698 .all::<usize>(&snapshot.display_snapshot)
21699 .into_iter()
21700 .map(|selection| {
21701 snapshot.buffer_snapshot().anchor_after(selection.end)
21702 ..snapshot
21703 .buffer_snapshot()
21704 .anchor_before(selection.end + pending.len())
21705 })
21706 .collect();
21707
21708 if pending.is_empty() {
21709 self.clear_highlights::<PendingInput>(cx);
21710 } else {
21711 self.highlight_text::<PendingInput>(
21712 ranges,
21713 HighlightStyle {
21714 underline: Some(UnderlineStyle {
21715 thickness: px(1.),
21716 color: None,
21717 wavy: false,
21718 }),
21719 ..Default::default()
21720 },
21721 cx,
21722 );
21723 }
21724
21725 self.ime_transaction = self.ime_transaction.or(transaction);
21726 if let Some(transaction) = self.ime_transaction {
21727 self.buffer.update(cx, |buffer, cx| {
21728 buffer.group_until_transaction(transaction, cx);
21729 });
21730 }
21731
21732 if self.text_highlights::<PendingInput>(cx).is_none() {
21733 self.ime_transaction.take();
21734 }
21735 }
21736
21737 pub fn register_action_renderer(
21738 &mut self,
21739 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21740 ) -> Subscription {
21741 let id = self.next_editor_action_id.post_inc();
21742 self.editor_actions
21743 .borrow_mut()
21744 .insert(id, Box::new(listener));
21745
21746 let editor_actions = self.editor_actions.clone();
21747 Subscription::new(move || {
21748 editor_actions.borrow_mut().remove(&id);
21749 })
21750 }
21751
21752 pub fn register_action<A: Action>(
21753 &mut self,
21754 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21755 ) -> Subscription {
21756 let id = self.next_editor_action_id.post_inc();
21757 let listener = Arc::new(listener);
21758 self.editor_actions.borrow_mut().insert(
21759 id,
21760 Box::new(move |_, window, _| {
21761 let listener = listener.clone();
21762 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21763 let action = action.downcast_ref().unwrap();
21764 if phase == DispatchPhase::Bubble {
21765 listener(action, window, cx)
21766 }
21767 })
21768 }),
21769 );
21770
21771 let editor_actions = self.editor_actions.clone();
21772 Subscription::new(move || {
21773 editor_actions.borrow_mut().remove(&id);
21774 })
21775 }
21776
21777 pub fn file_header_size(&self) -> u32 {
21778 FILE_HEADER_HEIGHT
21779 }
21780
21781 pub fn restore(
21782 &mut self,
21783 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21784 window: &mut Window,
21785 cx: &mut Context<Self>,
21786 ) {
21787 let workspace = self.workspace();
21788 let project = self.project();
21789 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21790 let mut tasks = Vec::new();
21791 for (buffer_id, changes) in revert_changes {
21792 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21793 buffer.update(cx, |buffer, cx| {
21794 buffer.edit(
21795 changes
21796 .into_iter()
21797 .map(|(range, text)| (range, text.to_string())),
21798 None,
21799 cx,
21800 );
21801 });
21802
21803 if let Some(project) =
21804 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21805 {
21806 project.update(cx, |project, cx| {
21807 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21808 })
21809 }
21810 }
21811 }
21812 tasks
21813 });
21814 cx.spawn_in(window, async move |_, cx| {
21815 for (buffer, task) in save_tasks {
21816 let result = task.await;
21817 if result.is_err() {
21818 let Some(path) = buffer
21819 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21820 .ok()
21821 else {
21822 continue;
21823 };
21824 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21825 let Some(task) = cx
21826 .update_window_entity(workspace, |workspace, window, cx| {
21827 workspace
21828 .open_path_preview(path, None, false, false, false, window, cx)
21829 })
21830 .ok()
21831 else {
21832 continue;
21833 };
21834 task.await.log_err();
21835 }
21836 }
21837 }
21838 })
21839 .detach();
21840 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21841 selections.refresh()
21842 });
21843 }
21844
21845 pub fn to_pixel_point(
21846 &self,
21847 source: multi_buffer::Anchor,
21848 editor_snapshot: &EditorSnapshot,
21849 window: &mut Window,
21850 ) -> Option<gpui::Point<Pixels>> {
21851 let source_point = source.to_display_point(editor_snapshot);
21852 self.display_to_pixel_point(source_point, editor_snapshot, window)
21853 }
21854
21855 pub fn display_to_pixel_point(
21856 &self,
21857 source: DisplayPoint,
21858 editor_snapshot: &EditorSnapshot,
21859 window: &mut Window,
21860 ) -> Option<gpui::Point<Pixels>> {
21861 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21862 let text_layout_details = self.text_layout_details(window);
21863 let scroll_top = text_layout_details
21864 .scroll_anchor
21865 .scroll_position(editor_snapshot)
21866 .y;
21867
21868 if source.row().as_f64() < scroll_top.floor() {
21869 return None;
21870 }
21871 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21872 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21873 Some(gpui::Point::new(source_x, source_y))
21874 }
21875
21876 pub fn has_visible_completions_menu(&self) -> bool {
21877 !self.edit_prediction_preview_is_active()
21878 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21879 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21880 })
21881 }
21882
21883 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21884 if self.mode.is_minimap() {
21885 return;
21886 }
21887 self.addons
21888 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21889 }
21890
21891 pub fn unregister_addon<T: Addon>(&mut self) {
21892 self.addons.remove(&std::any::TypeId::of::<T>());
21893 }
21894
21895 pub fn addon<T: Addon>(&self) -> Option<&T> {
21896 let type_id = std::any::TypeId::of::<T>();
21897 self.addons
21898 .get(&type_id)
21899 .and_then(|item| item.to_any().downcast_ref::<T>())
21900 }
21901
21902 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21903 let type_id = std::any::TypeId::of::<T>();
21904 self.addons
21905 .get_mut(&type_id)
21906 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21907 }
21908
21909 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21910 let text_layout_details = self.text_layout_details(window);
21911 let style = &text_layout_details.editor_style;
21912 let font_id = window.text_system().resolve_font(&style.text.font());
21913 let font_size = style.text.font_size.to_pixels(window.rem_size());
21914 let line_height = style.text.line_height_in_pixels(window.rem_size());
21915 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21916 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21917
21918 CharacterDimensions {
21919 em_width,
21920 em_advance,
21921 line_height,
21922 }
21923 }
21924
21925 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21926 self.load_diff_task.clone()
21927 }
21928
21929 fn read_metadata_from_db(
21930 &mut self,
21931 item_id: u64,
21932 workspace_id: WorkspaceId,
21933 window: &mut Window,
21934 cx: &mut Context<Editor>,
21935 ) {
21936 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21937 && !self.mode.is_minimap()
21938 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21939 {
21940 let buffer_snapshot = OnceCell::new();
21941
21942 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21943 && !folds.is_empty()
21944 {
21945 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21946 self.fold_ranges(
21947 folds
21948 .into_iter()
21949 .map(|(start, end)| {
21950 snapshot.clip_offset(start, Bias::Left)
21951 ..snapshot.clip_offset(end, Bias::Right)
21952 })
21953 .collect(),
21954 false,
21955 window,
21956 cx,
21957 );
21958 }
21959
21960 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21961 && !selections.is_empty()
21962 {
21963 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21964 // skip adding the initial selection to selection history
21965 self.selection_history.mode = SelectionHistoryMode::Skipping;
21966 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21967 s.select_ranges(selections.into_iter().map(|(start, end)| {
21968 snapshot.clip_offset(start, Bias::Left)
21969 ..snapshot.clip_offset(end, Bias::Right)
21970 }));
21971 });
21972 self.selection_history.mode = SelectionHistoryMode::Normal;
21973 };
21974 }
21975
21976 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21977 }
21978
21979 fn update_lsp_data(
21980 &mut self,
21981 for_buffer: Option<BufferId>,
21982 window: &mut Window,
21983 cx: &mut Context<'_, Self>,
21984 ) {
21985 self.pull_diagnostics(for_buffer, window, cx);
21986 self.refresh_colors_for_visible_range(for_buffer, window, cx);
21987 }
21988
21989 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
21990 if self.ignore_lsp_data() {
21991 return;
21992 }
21993 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
21994 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
21995 }
21996 }
21997
21998 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
21999 if !self.registered_buffers.contains_key(&buffer_id)
22000 && let Some(project) = self.project.as_ref()
22001 {
22002 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22003 project.update(cx, |project, cx| {
22004 self.registered_buffers.insert(
22005 buffer_id,
22006 project.register_buffer_with_language_servers(&buffer, cx),
22007 );
22008 });
22009 } else {
22010 self.registered_buffers.remove(&buffer_id);
22011 }
22012 }
22013 }
22014
22015 fn ignore_lsp_data(&self) -> bool {
22016 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22017 // skip any LSP updates for it.
22018 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22019 }
22020}
22021
22022fn edit_for_markdown_paste<'a>(
22023 buffer: &MultiBufferSnapshot,
22024 range: Range<usize>,
22025 to_insert: &'a str,
22026 url: Option<url::Url>,
22027) -> (Range<usize>, Cow<'a, str>) {
22028 if url.is_none() {
22029 return (range, Cow::Borrowed(to_insert));
22030 };
22031
22032 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22033
22034 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22035 Cow::Borrowed(to_insert)
22036 } else {
22037 Cow::Owned(format!("[{old_text}]({to_insert})"))
22038 };
22039 (range, new_text)
22040}
22041
22042fn vim_enabled(cx: &App) -> bool {
22043 vim_mode_setting::VimModeSetting::try_get(cx)
22044 .map(|vim_mode| vim_mode.0)
22045 .unwrap_or(false)
22046}
22047
22048fn process_completion_for_edit(
22049 completion: &Completion,
22050 intent: CompletionIntent,
22051 buffer: &Entity<Buffer>,
22052 cursor_position: &text::Anchor,
22053 cx: &mut Context<Editor>,
22054) -> CompletionEdit {
22055 let buffer = buffer.read(cx);
22056 let buffer_snapshot = buffer.snapshot();
22057 let (snippet, new_text) = if completion.is_snippet() {
22058 let mut snippet_source = completion.new_text.clone();
22059 // Workaround for typescript language server issues so that methods don't expand within
22060 // strings and functions with type expressions. The previous point is used because the query
22061 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22062 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22063 let previous_point = if previous_point.column > 0 {
22064 cursor_position.to_previous_offset(&buffer_snapshot)
22065 } else {
22066 cursor_position.to_offset(&buffer_snapshot)
22067 };
22068 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22069 && scope.prefers_label_for_snippet_in_completion()
22070 && let Some(label) = completion.label()
22071 && matches!(
22072 completion.kind(),
22073 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22074 )
22075 {
22076 snippet_source = label;
22077 }
22078 match Snippet::parse(&snippet_source).log_err() {
22079 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22080 None => (None, completion.new_text.clone()),
22081 }
22082 } else {
22083 (None, completion.new_text.clone())
22084 };
22085
22086 let mut range_to_replace = {
22087 let replace_range = &completion.replace_range;
22088 if let CompletionSource::Lsp {
22089 insert_range: Some(insert_range),
22090 ..
22091 } = &completion.source
22092 {
22093 debug_assert_eq!(
22094 insert_range.start, replace_range.start,
22095 "insert_range and replace_range should start at the same position"
22096 );
22097 debug_assert!(
22098 insert_range
22099 .start
22100 .cmp(cursor_position, &buffer_snapshot)
22101 .is_le(),
22102 "insert_range should start before or at cursor position"
22103 );
22104 debug_assert!(
22105 replace_range
22106 .start
22107 .cmp(cursor_position, &buffer_snapshot)
22108 .is_le(),
22109 "replace_range should start before or at cursor position"
22110 );
22111
22112 let should_replace = match intent {
22113 CompletionIntent::CompleteWithInsert => false,
22114 CompletionIntent::CompleteWithReplace => true,
22115 CompletionIntent::Complete | CompletionIntent::Compose => {
22116 let insert_mode =
22117 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22118 .completions
22119 .lsp_insert_mode;
22120 match insert_mode {
22121 LspInsertMode::Insert => false,
22122 LspInsertMode::Replace => true,
22123 LspInsertMode::ReplaceSubsequence => {
22124 let mut text_to_replace = buffer.chars_for_range(
22125 buffer.anchor_before(replace_range.start)
22126 ..buffer.anchor_after(replace_range.end),
22127 );
22128 let mut current_needle = text_to_replace.next();
22129 for haystack_ch in completion.label.text.chars() {
22130 if let Some(needle_ch) = current_needle
22131 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22132 {
22133 current_needle = text_to_replace.next();
22134 }
22135 }
22136 current_needle.is_none()
22137 }
22138 LspInsertMode::ReplaceSuffix => {
22139 if replace_range
22140 .end
22141 .cmp(cursor_position, &buffer_snapshot)
22142 .is_gt()
22143 {
22144 let range_after_cursor = *cursor_position..replace_range.end;
22145 let text_after_cursor = buffer
22146 .text_for_range(
22147 buffer.anchor_before(range_after_cursor.start)
22148 ..buffer.anchor_after(range_after_cursor.end),
22149 )
22150 .collect::<String>()
22151 .to_ascii_lowercase();
22152 completion
22153 .label
22154 .text
22155 .to_ascii_lowercase()
22156 .ends_with(&text_after_cursor)
22157 } else {
22158 true
22159 }
22160 }
22161 }
22162 }
22163 };
22164
22165 if should_replace {
22166 replace_range.clone()
22167 } else {
22168 insert_range.clone()
22169 }
22170 } else {
22171 replace_range.clone()
22172 }
22173 };
22174
22175 if range_to_replace
22176 .end
22177 .cmp(cursor_position, &buffer_snapshot)
22178 .is_lt()
22179 {
22180 range_to_replace.end = *cursor_position;
22181 }
22182
22183 CompletionEdit {
22184 new_text,
22185 replace_range: range_to_replace.to_offset(buffer),
22186 snippet,
22187 }
22188}
22189
22190struct CompletionEdit {
22191 new_text: String,
22192 replace_range: Range<usize>,
22193 snippet: Option<Snippet>,
22194}
22195
22196fn insert_extra_newline_brackets(
22197 buffer: &MultiBufferSnapshot,
22198 range: Range<usize>,
22199 language: &language::LanguageScope,
22200) -> bool {
22201 let leading_whitespace_len = buffer
22202 .reversed_chars_at(range.start)
22203 .take_while(|c| c.is_whitespace() && *c != '\n')
22204 .map(|c| c.len_utf8())
22205 .sum::<usize>();
22206 let trailing_whitespace_len = buffer
22207 .chars_at(range.end)
22208 .take_while(|c| c.is_whitespace() && *c != '\n')
22209 .map(|c| c.len_utf8())
22210 .sum::<usize>();
22211 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22212
22213 language.brackets().any(|(pair, enabled)| {
22214 let pair_start = pair.start.trim_end();
22215 let pair_end = pair.end.trim_start();
22216
22217 enabled
22218 && pair.newline
22219 && buffer.contains_str_at(range.end, pair_end)
22220 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22221 })
22222}
22223
22224fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22225 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22226 [(buffer, range, _)] => (*buffer, range.clone()),
22227 _ => return false,
22228 };
22229 let pair = {
22230 let mut result: Option<BracketMatch> = None;
22231
22232 for pair in buffer
22233 .all_bracket_ranges(range.clone())
22234 .filter(move |pair| {
22235 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22236 })
22237 {
22238 let len = pair.close_range.end - pair.open_range.start;
22239
22240 if let Some(existing) = &result {
22241 let existing_len = existing.close_range.end - existing.open_range.start;
22242 if len > existing_len {
22243 continue;
22244 }
22245 }
22246
22247 result = Some(pair);
22248 }
22249
22250 result
22251 };
22252 let Some(pair) = pair else {
22253 return false;
22254 };
22255 pair.newline_only
22256 && buffer
22257 .chars_for_range(pair.open_range.end..range.start)
22258 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22259 .all(|c| c.is_whitespace() && c != '\n')
22260}
22261
22262fn update_uncommitted_diff_for_buffer(
22263 editor: Entity<Editor>,
22264 project: &Entity<Project>,
22265 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22266 buffer: Entity<MultiBuffer>,
22267 cx: &mut App,
22268) -> Task<()> {
22269 let mut tasks = Vec::new();
22270 project.update(cx, |project, cx| {
22271 for buffer in buffers {
22272 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22273 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22274 }
22275 }
22276 });
22277 cx.spawn(async move |cx| {
22278 let diffs = future::join_all(tasks).await;
22279 if editor
22280 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22281 .unwrap_or(false)
22282 {
22283 return;
22284 }
22285
22286 buffer
22287 .update(cx, |buffer, cx| {
22288 for diff in diffs.into_iter().flatten() {
22289 buffer.add_diff(diff, cx);
22290 }
22291 })
22292 .ok();
22293 })
22294}
22295
22296fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22297 let tab_size = tab_size.get() as usize;
22298 let mut width = offset;
22299
22300 for ch in text.chars() {
22301 width += if ch == '\t' {
22302 tab_size - (width % tab_size)
22303 } else {
22304 1
22305 };
22306 }
22307
22308 width - offset
22309}
22310
22311#[cfg(test)]
22312mod tests {
22313 use super::*;
22314
22315 #[test]
22316 fn test_string_size_with_expanded_tabs() {
22317 let nz = |val| NonZeroU32::new(val).unwrap();
22318 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22319 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22320 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22321 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22322 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22323 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22324 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22325 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22326 }
22327}
22328
22329/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22330struct WordBreakingTokenizer<'a> {
22331 input: &'a str,
22332}
22333
22334impl<'a> WordBreakingTokenizer<'a> {
22335 fn new(input: &'a str) -> Self {
22336 Self { input }
22337 }
22338}
22339
22340fn is_char_ideographic(ch: char) -> bool {
22341 use unicode_script::Script::*;
22342 use unicode_script::UnicodeScript;
22343 matches!(ch.script(), Han | Tangut | Yi)
22344}
22345
22346fn is_grapheme_ideographic(text: &str) -> bool {
22347 text.chars().any(is_char_ideographic)
22348}
22349
22350fn is_grapheme_whitespace(text: &str) -> bool {
22351 text.chars().any(|x| x.is_whitespace())
22352}
22353
22354fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22355 text.chars()
22356 .next()
22357 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22358}
22359
22360#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22361enum WordBreakToken<'a> {
22362 Word { token: &'a str, grapheme_len: usize },
22363 InlineWhitespace { token: &'a str, grapheme_len: usize },
22364 Newline,
22365}
22366
22367impl<'a> Iterator for WordBreakingTokenizer<'a> {
22368 /// Yields a span, the count of graphemes in the token, and whether it was
22369 /// whitespace. Note that it also breaks at word boundaries.
22370 type Item = WordBreakToken<'a>;
22371
22372 fn next(&mut self) -> Option<Self::Item> {
22373 use unicode_segmentation::UnicodeSegmentation;
22374 if self.input.is_empty() {
22375 return None;
22376 }
22377
22378 let mut iter = self.input.graphemes(true).peekable();
22379 let mut offset = 0;
22380 let mut grapheme_len = 0;
22381 if let Some(first_grapheme) = iter.next() {
22382 let is_newline = first_grapheme == "\n";
22383 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22384 offset += first_grapheme.len();
22385 grapheme_len += 1;
22386 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22387 if let Some(grapheme) = iter.peek().copied()
22388 && should_stay_with_preceding_ideograph(grapheme)
22389 {
22390 offset += grapheme.len();
22391 grapheme_len += 1;
22392 }
22393 } else {
22394 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22395 let mut next_word_bound = words.peek().copied();
22396 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22397 next_word_bound = words.next();
22398 }
22399 while let Some(grapheme) = iter.peek().copied() {
22400 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22401 break;
22402 };
22403 if is_grapheme_whitespace(grapheme) != is_whitespace
22404 || (grapheme == "\n") != is_newline
22405 {
22406 break;
22407 };
22408 offset += grapheme.len();
22409 grapheme_len += 1;
22410 iter.next();
22411 }
22412 }
22413 let token = &self.input[..offset];
22414 self.input = &self.input[offset..];
22415 if token == "\n" {
22416 Some(WordBreakToken::Newline)
22417 } else if is_whitespace {
22418 Some(WordBreakToken::InlineWhitespace {
22419 token,
22420 grapheme_len,
22421 })
22422 } else {
22423 Some(WordBreakToken::Word {
22424 token,
22425 grapheme_len,
22426 })
22427 }
22428 } else {
22429 None
22430 }
22431 }
22432}
22433
22434#[test]
22435fn test_word_breaking_tokenizer() {
22436 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22437 ("", &[]),
22438 (" ", &[whitespace(" ", 2)]),
22439 ("Ʒ", &[word("Ʒ", 1)]),
22440 ("Ǽ", &[word("Ǽ", 1)]),
22441 ("⋑", &[word("⋑", 1)]),
22442 ("⋑⋑", &[word("⋑⋑", 2)]),
22443 (
22444 "原理,进而",
22445 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22446 ),
22447 (
22448 "hello world",
22449 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22450 ),
22451 (
22452 "hello, world",
22453 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22454 ),
22455 (
22456 " hello world",
22457 &[
22458 whitespace(" ", 2),
22459 word("hello", 5),
22460 whitespace(" ", 1),
22461 word("world", 5),
22462 ],
22463 ),
22464 (
22465 "这是什么 \n 钢笔",
22466 &[
22467 word("这", 1),
22468 word("是", 1),
22469 word("什", 1),
22470 word("么", 1),
22471 whitespace(" ", 1),
22472 newline(),
22473 whitespace(" ", 1),
22474 word("钢", 1),
22475 word("笔", 1),
22476 ],
22477 ),
22478 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22479 ];
22480
22481 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22482 WordBreakToken::Word {
22483 token,
22484 grapheme_len,
22485 }
22486 }
22487
22488 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22489 WordBreakToken::InlineWhitespace {
22490 token,
22491 grapheme_len,
22492 }
22493 }
22494
22495 fn newline() -> WordBreakToken<'static> {
22496 WordBreakToken::Newline
22497 }
22498
22499 for (input, result) in tests {
22500 assert_eq!(
22501 WordBreakingTokenizer::new(input)
22502 .collect::<Vec<_>>()
22503 .as_slice(),
22504 *result,
22505 );
22506 }
22507}
22508
22509fn wrap_with_prefix(
22510 first_line_prefix: String,
22511 subsequent_lines_prefix: String,
22512 unwrapped_text: String,
22513 wrap_column: usize,
22514 tab_size: NonZeroU32,
22515 preserve_existing_whitespace: bool,
22516) -> String {
22517 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22518 let subsequent_lines_prefix_len =
22519 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22520 let mut wrapped_text = String::new();
22521 let mut current_line = first_line_prefix;
22522 let mut is_first_line = true;
22523
22524 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22525 let mut current_line_len = first_line_prefix_len;
22526 let mut in_whitespace = false;
22527 for token in tokenizer {
22528 let have_preceding_whitespace = in_whitespace;
22529 match token {
22530 WordBreakToken::Word {
22531 token,
22532 grapheme_len,
22533 } => {
22534 in_whitespace = false;
22535 let current_prefix_len = if is_first_line {
22536 first_line_prefix_len
22537 } else {
22538 subsequent_lines_prefix_len
22539 };
22540 if current_line_len + grapheme_len > wrap_column
22541 && current_line_len != current_prefix_len
22542 {
22543 wrapped_text.push_str(current_line.trim_end());
22544 wrapped_text.push('\n');
22545 is_first_line = false;
22546 current_line = subsequent_lines_prefix.clone();
22547 current_line_len = subsequent_lines_prefix_len;
22548 }
22549 current_line.push_str(token);
22550 current_line_len += grapheme_len;
22551 }
22552 WordBreakToken::InlineWhitespace {
22553 mut token,
22554 mut grapheme_len,
22555 } => {
22556 in_whitespace = true;
22557 if have_preceding_whitespace && !preserve_existing_whitespace {
22558 continue;
22559 }
22560 if !preserve_existing_whitespace {
22561 // Keep a single whitespace grapheme as-is
22562 if let Some(first) =
22563 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22564 {
22565 token = first;
22566 } else {
22567 token = " ";
22568 }
22569 grapheme_len = 1;
22570 }
22571 let current_prefix_len = if is_first_line {
22572 first_line_prefix_len
22573 } else {
22574 subsequent_lines_prefix_len
22575 };
22576 if current_line_len + grapheme_len > wrap_column {
22577 wrapped_text.push_str(current_line.trim_end());
22578 wrapped_text.push('\n');
22579 is_first_line = false;
22580 current_line = subsequent_lines_prefix.clone();
22581 current_line_len = subsequent_lines_prefix_len;
22582 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22583 current_line.push_str(token);
22584 current_line_len += grapheme_len;
22585 }
22586 }
22587 WordBreakToken::Newline => {
22588 in_whitespace = true;
22589 let current_prefix_len = if is_first_line {
22590 first_line_prefix_len
22591 } else {
22592 subsequent_lines_prefix_len
22593 };
22594 if preserve_existing_whitespace {
22595 wrapped_text.push_str(current_line.trim_end());
22596 wrapped_text.push('\n');
22597 is_first_line = false;
22598 current_line = subsequent_lines_prefix.clone();
22599 current_line_len = subsequent_lines_prefix_len;
22600 } else if have_preceding_whitespace {
22601 continue;
22602 } else if current_line_len + 1 > wrap_column
22603 && current_line_len != current_prefix_len
22604 {
22605 wrapped_text.push_str(current_line.trim_end());
22606 wrapped_text.push('\n');
22607 is_first_line = false;
22608 current_line = subsequent_lines_prefix.clone();
22609 current_line_len = subsequent_lines_prefix_len;
22610 } else if current_line_len != current_prefix_len {
22611 current_line.push(' ');
22612 current_line_len += 1;
22613 }
22614 }
22615 }
22616 }
22617
22618 if !current_line.is_empty() {
22619 wrapped_text.push_str(¤t_line);
22620 }
22621 wrapped_text
22622}
22623
22624#[test]
22625fn test_wrap_with_prefix() {
22626 assert_eq!(
22627 wrap_with_prefix(
22628 "# ".to_string(),
22629 "# ".to_string(),
22630 "abcdefg".to_string(),
22631 4,
22632 NonZeroU32::new(4).unwrap(),
22633 false,
22634 ),
22635 "# abcdefg"
22636 );
22637 assert_eq!(
22638 wrap_with_prefix(
22639 "".to_string(),
22640 "".to_string(),
22641 "\thello world".to_string(),
22642 8,
22643 NonZeroU32::new(4).unwrap(),
22644 false,
22645 ),
22646 "hello\nworld"
22647 );
22648 assert_eq!(
22649 wrap_with_prefix(
22650 "// ".to_string(),
22651 "// ".to_string(),
22652 "xx \nyy zz aa bb cc".to_string(),
22653 12,
22654 NonZeroU32::new(4).unwrap(),
22655 false,
22656 ),
22657 "// xx yy zz\n// aa bb cc"
22658 );
22659 assert_eq!(
22660 wrap_with_prefix(
22661 String::new(),
22662 String::new(),
22663 "这是什么 \n 钢笔".to_string(),
22664 3,
22665 NonZeroU32::new(4).unwrap(),
22666 false,
22667 ),
22668 "这是什\n么 钢\n笔"
22669 );
22670 assert_eq!(
22671 wrap_with_prefix(
22672 String::new(),
22673 String::new(),
22674 format!("foo{}bar", '\u{2009}'), // thin space
22675 80,
22676 NonZeroU32::new(4).unwrap(),
22677 false,
22678 ),
22679 format!("foo{}bar", '\u{2009}')
22680 );
22681}
22682
22683pub trait CollaborationHub {
22684 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22685 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22686 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22687}
22688
22689impl CollaborationHub for Entity<Project> {
22690 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22691 self.read(cx).collaborators()
22692 }
22693
22694 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22695 self.read(cx).user_store().read(cx).participant_indices()
22696 }
22697
22698 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22699 let this = self.read(cx);
22700 let user_ids = this.collaborators().values().map(|c| c.user_id);
22701 this.user_store().read(cx).participant_names(user_ids, cx)
22702 }
22703}
22704
22705pub trait SemanticsProvider {
22706 fn hover(
22707 &self,
22708 buffer: &Entity<Buffer>,
22709 position: text::Anchor,
22710 cx: &mut App,
22711 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22712
22713 fn inline_values(
22714 &self,
22715 buffer_handle: Entity<Buffer>,
22716 range: Range<text::Anchor>,
22717 cx: &mut App,
22718 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22719
22720 fn applicable_inlay_chunks(
22721 &self,
22722 buffer: &Entity<Buffer>,
22723 ranges: &[Range<text::Anchor>],
22724 cx: &mut App,
22725 ) -> Vec<Range<BufferRow>>;
22726
22727 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22728
22729 fn inlay_hints(
22730 &self,
22731 invalidate: InvalidationStrategy,
22732 buffer: Entity<Buffer>,
22733 ranges: Vec<Range<text::Anchor>>,
22734 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22735 cx: &mut App,
22736 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22737
22738 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22739
22740 fn document_highlights(
22741 &self,
22742 buffer: &Entity<Buffer>,
22743 position: text::Anchor,
22744 cx: &mut App,
22745 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22746
22747 fn definitions(
22748 &self,
22749 buffer: &Entity<Buffer>,
22750 position: text::Anchor,
22751 kind: GotoDefinitionKind,
22752 cx: &mut App,
22753 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22754
22755 fn range_for_rename(
22756 &self,
22757 buffer: &Entity<Buffer>,
22758 position: text::Anchor,
22759 cx: &mut App,
22760 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22761
22762 fn perform_rename(
22763 &self,
22764 buffer: &Entity<Buffer>,
22765 position: text::Anchor,
22766 new_name: String,
22767 cx: &mut App,
22768 ) -> Option<Task<Result<ProjectTransaction>>>;
22769}
22770
22771pub trait CompletionProvider {
22772 fn completions(
22773 &self,
22774 excerpt_id: ExcerptId,
22775 buffer: &Entity<Buffer>,
22776 buffer_position: text::Anchor,
22777 trigger: CompletionContext,
22778 window: &mut Window,
22779 cx: &mut Context<Editor>,
22780 ) -> Task<Result<Vec<CompletionResponse>>>;
22781
22782 fn resolve_completions(
22783 &self,
22784 _buffer: Entity<Buffer>,
22785 _completion_indices: Vec<usize>,
22786 _completions: Rc<RefCell<Box<[Completion]>>>,
22787 _cx: &mut Context<Editor>,
22788 ) -> Task<Result<bool>> {
22789 Task::ready(Ok(false))
22790 }
22791
22792 fn apply_additional_edits_for_completion(
22793 &self,
22794 _buffer: Entity<Buffer>,
22795 _completions: Rc<RefCell<Box<[Completion]>>>,
22796 _completion_index: usize,
22797 _push_to_history: bool,
22798 _cx: &mut Context<Editor>,
22799 ) -> Task<Result<Option<language::Transaction>>> {
22800 Task::ready(Ok(None))
22801 }
22802
22803 fn is_completion_trigger(
22804 &self,
22805 buffer: &Entity<Buffer>,
22806 position: language::Anchor,
22807 text: &str,
22808 trigger_in_words: bool,
22809 menu_is_open: bool,
22810 cx: &mut Context<Editor>,
22811 ) -> bool;
22812
22813 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22814
22815 fn sort_completions(&self) -> bool {
22816 true
22817 }
22818
22819 fn filter_completions(&self) -> bool {
22820 true
22821 }
22822}
22823
22824pub trait CodeActionProvider {
22825 fn id(&self) -> Arc<str>;
22826
22827 fn code_actions(
22828 &self,
22829 buffer: &Entity<Buffer>,
22830 range: Range<text::Anchor>,
22831 window: &mut Window,
22832 cx: &mut App,
22833 ) -> Task<Result<Vec<CodeAction>>>;
22834
22835 fn apply_code_action(
22836 &self,
22837 buffer_handle: Entity<Buffer>,
22838 action: CodeAction,
22839 excerpt_id: ExcerptId,
22840 push_to_history: bool,
22841 window: &mut Window,
22842 cx: &mut App,
22843 ) -> Task<Result<ProjectTransaction>>;
22844}
22845
22846impl CodeActionProvider for Entity<Project> {
22847 fn id(&self) -> Arc<str> {
22848 "project".into()
22849 }
22850
22851 fn code_actions(
22852 &self,
22853 buffer: &Entity<Buffer>,
22854 range: Range<text::Anchor>,
22855 _window: &mut Window,
22856 cx: &mut App,
22857 ) -> Task<Result<Vec<CodeAction>>> {
22858 self.update(cx, |project, cx| {
22859 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22860 let code_actions = project.code_actions(buffer, range, None, cx);
22861 cx.background_spawn(async move {
22862 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22863 Ok(code_lens_actions
22864 .context("code lens fetch")?
22865 .into_iter()
22866 .flatten()
22867 .chain(
22868 code_actions
22869 .context("code action fetch")?
22870 .into_iter()
22871 .flatten(),
22872 )
22873 .collect())
22874 })
22875 })
22876 }
22877
22878 fn apply_code_action(
22879 &self,
22880 buffer_handle: Entity<Buffer>,
22881 action: CodeAction,
22882 _excerpt_id: ExcerptId,
22883 push_to_history: bool,
22884 _window: &mut Window,
22885 cx: &mut App,
22886 ) -> Task<Result<ProjectTransaction>> {
22887 self.update(cx, |project, cx| {
22888 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22889 })
22890 }
22891}
22892
22893fn snippet_completions(
22894 project: &Project,
22895 buffer: &Entity<Buffer>,
22896 buffer_position: text::Anchor,
22897 cx: &mut App,
22898) -> Task<Result<CompletionResponse>> {
22899 let languages = buffer.read(cx).languages_at(buffer_position);
22900 let snippet_store = project.snippets().read(cx);
22901
22902 let scopes: Vec<_> = languages
22903 .iter()
22904 .filter_map(|language| {
22905 let language_name = language.lsp_id();
22906 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22907
22908 if snippets.is_empty() {
22909 None
22910 } else {
22911 Some((language.default_scope(), snippets))
22912 }
22913 })
22914 .collect();
22915
22916 if scopes.is_empty() {
22917 return Task::ready(Ok(CompletionResponse {
22918 completions: vec![],
22919 display_options: CompletionDisplayOptions::default(),
22920 is_incomplete: false,
22921 }));
22922 }
22923
22924 let snapshot = buffer.read(cx).text_snapshot();
22925 let executor = cx.background_executor().clone();
22926
22927 cx.background_spawn(async move {
22928 let mut is_incomplete = false;
22929 let mut completions: Vec<Completion> = Vec::new();
22930 for (scope, snippets) in scopes.into_iter() {
22931 let classifier =
22932 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22933
22934 const MAX_WORD_PREFIX_LEN: usize = 128;
22935 let last_word: String = snapshot
22936 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22937 .take(MAX_WORD_PREFIX_LEN)
22938 .take_while(|c| classifier.is_word(*c))
22939 .collect::<String>()
22940 .chars()
22941 .rev()
22942 .collect();
22943
22944 if last_word.is_empty() {
22945 return Ok(CompletionResponse {
22946 completions: vec![],
22947 display_options: CompletionDisplayOptions::default(),
22948 is_incomplete: true,
22949 });
22950 }
22951
22952 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22953 let to_lsp = |point: &text::Anchor| {
22954 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22955 point_to_lsp(end)
22956 };
22957 let lsp_end = to_lsp(&buffer_position);
22958
22959 let candidates = snippets
22960 .iter()
22961 .enumerate()
22962 .flat_map(|(ix, snippet)| {
22963 snippet
22964 .prefix
22965 .iter()
22966 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22967 })
22968 .collect::<Vec<StringMatchCandidate>>();
22969
22970 const MAX_RESULTS: usize = 100;
22971 let mut matches = fuzzy::match_strings(
22972 &candidates,
22973 &last_word,
22974 last_word.chars().any(|c| c.is_uppercase()),
22975 true,
22976 MAX_RESULTS,
22977 &Default::default(),
22978 executor.clone(),
22979 )
22980 .await;
22981
22982 if matches.len() >= MAX_RESULTS {
22983 is_incomplete = true;
22984 }
22985
22986 // Remove all candidates where the query's start does not match the start of any word in the candidate
22987 if let Some(query_start) = last_word.chars().next() {
22988 matches.retain(|string_match| {
22989 split_words(&string_match.string).any(|word| {
22990 // Check that the first codepoint of the word as lowercase matches the first
22991 // codepoint of the query as lowercase
22992 word.chars()
22993 .flat_map(|codepoint| codepoint.to_lowercase())
22994 .zip(query_start.to_lowercase())
22995 .all(|(word_cp, query_cp)| word_cp == query_cp)
22996 })
22997 });
22998 }
22999
23000 let matched_strings = matches
23001 .into_iter()
23002 .map(|m| m.string)
23003 .collect::<HashSet<_>>();
23004
23005 completions.extend(snippets.iter().filter_map(|snippet| {
23006 let matching_prefix = snippet
23007 .prefix
23008 .iter()
23009 .find(|prefix| matched_strings.contains(*prefix))?;
23010 let start = as_offset - last_word.len();
23011 let start = snapshot.anchor_before(start);
23012 let range = start..buffer_position;
23013 let lsp_start = to_lsp(&start);
23014 let lsp_range = lsp::Range {
23015 start: lsp_start,
23016 end: lsp_end,
23017 };
23018 Some(Completion {
23019 replace_range: range,
23020 new_text: snippet.body.clone(),
23021 source: CompletionSource::Lsp {
23022 insert_range: None,
23023 server_id: LanguageServerId(usize::MAX),
23024 resolved: true,
23025 lsp_completion: Box::new(lsp::CompletionItem {
23026 label: snippet.prefix.first().unwrap().clone(),
23027 kind: Some(CompletionItemKind::SNIPPET),
23028 label_details: snippet.description.as_ref().map(|description| {
23029 lsp::CompletionItemLabelDetails {
23030 detail: Some(description.clone()),
23031 description: None,
23032 }
23033 }),
23034 insert_text_format: Some(InsertTextFormat::SNIPPET),
23035 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23036 lsp::InsertReplaceEdit {
23037 new_text: snippet.body.clone(),
23038 insert: lsp_range,
23039 replace: lsp_range,
23040 },
23041 )),
23042 filter_text: Some(snippet.body.clone()),
23043 sort_text: Some(char::MAX.to_string()),
23044 ..lsp::CompletionItem::default()
23045 }),
23046 lsp_defaults: None,
23047 },
23048 label: CodeLabel::plain(matching_prefix.clone(), None),
23049 icon_path: None,
23050 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23051 single_line: snippet.name.clone().into(),
23052 plain_text: snippet
23053 .description
23054 .clone()
23055 .map(|description| description.into()),
23056 }),
23057 insert_text_mode: None,
23058 confirm: None,
23059 })
23060 }))
23061 }
23062
23063 Ok(CompletionResponse {
23064 completions,
23065 display_options: CompletionDisplayOptions::default(),
23066 is_incomplete,
23067 })
23068 })
23069}
23070
23071impl CompletionProvider for Entity<Project> {
23072 fn completions(
23073 &self,
23074 _excerpt_id: ExcerptId,
23075 buffer: &Entity<Buffer>,
23076 buffer_position: text::Anchor,
23077 options: CompletionContext,
23078 _window: &mut Window,
23079 cx: &mut Context<Editor>,
23080 ) -> Task<Result<Vec<CompletionResponse>>> {
23081 self.update(cx, |project, cx| {
23082 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23083 let project_completions = project.completions(buffer, buffer_position, options, cx);
23084 cx.background_spawn(async move {
23085 let mut responses = project_completions.await?;
23086 let snippets = snippets.await?;
23087 if !snippets.completions.is_empty() {
23088 responses.push(snippets);
23089 }
23090 Ok(responses)
23091 })
23092 })
23093 }
23094
23095 fn resolve_completions(
23096 &self,
23097 buffer: Entity<Buffer>,
23098 completion_indices: Vec<usize>,
23099 completions: Rc<RefCell<Box<[Completion]>>>,
23100 cx: &mut Context<Editor>,
23101 ) -> Task<Result<bool>> {
23102 self.update(cx, |project, cx| {
23103 project.lsp_store().update(cx, |lsp_store, cx| {
23104 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23105 })
23106 })
23107 }
23108
23109 fn apply_additional_edits_for_completion(
23110 &self,
23111 buffer: Entity<Buffer>,
23112 completions: Rc<RefCell<Box<[Completion]>>>,
23113 completion_index: usize,
23114 push_to_history: bool,
23115 cx: &mut Context<Editor>,
23116 ) -> Task<Result<Option<language::Transaction>>> {
23117 self.update(cx, |project, cx| {
23118 project.lsp_store().update(cx, |lsp_store, cx| {
23119 lsp_store.apply_additional_edits_for_completion(
23120 buffer,
23121 completions,
23122 completion_index,
23123 push_to_history,
23124 cx,
23125 )
23126 })
23127 })
23128 }
23129
23130 fn is_completion_trigger(
23131 &self,
23132 buffer: &Entity<Buffer>,
23133 position: language::Anchor,
23134 text: &str,
23135 trigger_in_words: bool,
23136 menu_is_open: bool,
23137 cx: &mut Context<Editor>,
23138 ) -> bool {
23139 let mut chars = text.chars();
23140 let char = if let Some(char) = chars.next() {
23141 char
23142 } else {
23143 return false;
23144 };
23145 if chars.next().is_some() {
23146 return false;
23147 }
23148
23149 let buffer = buffer.read(cx);
23150 let snapshot = buffer.snapshot();
23151 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23152 return false;
23153 }
23154 let classifier = snapshot
23155 .char_classifier_at(position)
23156 .scope_context(Some(CharScopeContext::Completion));
23157 if trigger_in_words && classifier.is_word(char) {
23158 return true;
23159 }
23160
23161 buffer.completion_triggers().contains(text)
23162 }
23163}
23164
23165impl SemanticsProvider for Entity<Project> {
23166 fn hover(
23167 &self,
23168 buffer: &Entity<Buffer>,
23169 position: text::Anchor,
23170 cx: &mut App,
23171 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23172 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23173 }
23174
23175 fn document_highlights(
23176 &self,
23177 buffer: &Entity<Buffer>,
23178 position: text::Anchor,
23179 cx: &mut App,
23180 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23181 Some(self.update(cx, |project, cx| {
23182 project.document_highlights(buffer, position, cx)
23183 }))
23184 }
23185
23186 fn definitions(
23187 &self,
23188 buffer: &Entity<Buffer>,
23189 position: text::Anchor,
23190 kind: GotoDefinitionKind,
23191 cx: &mut App,
23192 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23193 Some(self.update(cx, |project, cx| match kind {
23194 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23195 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23196 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23197 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23198 }))
23199 }
23200
23201 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23202 self.update(cx, |project, cx| {
23203 if project
23204 .active_debug_session(cx)
23205 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23206 {
23207 return true;
23208 }
23209
23210 buffer.update(cx, |buffer, cx| {
23211 project.any_language_server_supports_inlay_hints(buffer, cx)
23212 })
23213 })
23214 }
23215
23216 fn inline_values(
23217 &self,
23218 buffer_handle: Entity<Buffer>,
23219 range: Range<text::Anchor>,
23220 cx: &mut App,
23221 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23222 self.update(cx, |project, cx| {
23223 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23224
23225 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23226 })
23227 }
23228
23229 fn applicable_inlay_chunks(
23230 &self,
23231 buffer: &Entity<Buffer>,
23232 ranges: &[Range<text::Anchor>],
23233 cx: &mut App,
23234 ) -> Vec<Range<BufferRow>> {
23235 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23236 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23237 })
23238 }
23239
23240 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23241 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23242 lsp_store.invalidate_inlay_hints(for_buffers)
23243 });
23244 }
23245
23246 fn inlay_hints(
23247 &self,
23248 invalidate: InvalidationStrategy,
23249 buffer: Entity<Buffer>,
23250 ranges: Vec<Range<text::Anchor>>,
23251 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23252 cx: &mut App,
23253 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23254 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23255 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23256 }))
23257 }
23258
23259 fn range_for_rename(
23260 &self,
23261 buffer: &Entity<Buffer>,
23262 position: text::Anchor,
23263 cx: &mut App,
23264 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23265 Some(self.update(cx, |project, cx| {
23266 let buffer = buffer.clone();
23267 let task = project.prepare_rename(buffer.clone(), position, cx);
23268 cx.spawn(async move |_, cx| {
23269 Ok(match task.await? {
23270 PrepareRenameResponse::Success(range) => Some(range),
23271 PrepareRenameResponse::InvalidPosition => None,
23272 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23273 // Fallback on using TreeSitter info to determine identifier range
23274 buffer.read_with(cx, |buffer, _| {
23275 let snapshot = buffer.snapshot();
23276 let (range, kind) = snapshot.surrounding_word(position, None);
23277 if kind != Some(CharKind::Word) {
23278 return None;
23279 }
23280 Some(
23281 snapshot.anchor_before(range.start)
23282 ..snapshot.anchor_after(range.end),
23283 )
23284 })?
23285 }
23286 })
23287 })
23288 }))
23289 }
23290
23291 fn perform_rename(
23292 &self,
23293 buffer: &Entity<Buffer>,
23294 position: text::Anchor,
23295 new_name: String,
23296 cx: &mut App,
23297 ) -> Option<Task<Result<ProjectTransaction>>> {
23298 Some(self.update(cx, |project, cx| {
23299 project.perform_rename(buffer.clone(), position, new_name, cx)
23300 }))
23301 }
23302}
23303
23304fn consume_contiguous_rows(
23305 contiguous_row_selections: &mut Vec<Selection<Point>>,
23306 selection: &Selection<Point>,
23307 display_map: &DisplaySnapshot,
23308 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23309) -> (MultiBufferRow, MultiBufferRow) {
23310 contiguous_row_selections.push(selection.clone());
23311 let start_row = starting_row(selection, display_map);
23312 let mut end_row = ending_row(selection, display_map);
23313
23314 while let Some(next_selection) = selections.peek() {
23315 if next_selection.start.row <= end_row.0 {
23316 end_row = ending_row(next_selection, display_map);
23317 contiguous_row_selections.push(selections.next().unwrap().clone());
23318 } else {
23319 break;
23320 }
23321 }
23322 (start_row, end_row)
23323}
23324
23325fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23326 if selection.start.column > 0 {
23327 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23328 } else {
23329 MultiBufferRow(selection.start.row)
23330 }
23331}
23332
23333fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23334 if next_selection.end.column > 0 || next_selection.is_empty() {
23335 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23336 } else {
23337 MultiBufferRow(next_selection.end.row)
23338 }
23339}
23340
23341impl EditorSnapshot {
23342 pub fn remote_selections_in_range<'a>(
23343 &'a self,
23344 range: &'a Range<Anchor>,
23345 collaboration_hub: &dyn CollaborationHub,
23346 cx: &'a App,
23347 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23348 let participant_names = collaboration_hub.user_names(cx);
23349 let participant_indices = collaboration_hub.user_participant_indices(cx);
23350 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23351 let collaborators_by_replica_id = collaborators_by_peer_id
23352 .values()
23353 .map(|collaborator| (collaborator.replica_id, collaborator))
23354 .collect::<HashMap<_, _>>();
23355 self.buffer_snapshot()
23356 .selections_in_range(range, false)
23357 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23358 if replica_id == ReplicaId::AGENT {
23359 Some(RemoteSelection {
23360 replica_id,
23361 selection,
23362 cursor_shape,
23363 line_mode,
23364 collaborator_id: CollaboratorId::Agent,
23365 user_name: Some("Agent".into()),
23366 color: cx.theme().players().agent(),
23367 })
23368 } else {
23369 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23370 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23371 let user_name = participant_names.get(&collaborator.user_id).cloned();
23372 Some(RemoteSelection {
23373 replica_id,
23374 selection,
23375 cursor_shape,
23376 line_mode,
23377 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23378 user_name,
23379 color: if let Some(index) = participant_index {
23380 cx.theme().players().color_for_participant(index.0)
23381 } else {
23382 cx.theme().players().absent()
23383 },
23384 })
23385 }
23386 })
23387 }
23388
23389 pub fn hunks_for_ranges(
23390 &self,
23391 ranges: impl IntoIterator<Item = Range<Point>>,
23392 ) -> Vec<MultiBufferDiffHunk> {
23393 let mut hunks = Vec::new();
23394 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23395 HashMap::default();
23396 for query_range in ranges {
23397 let query_rows =
23398 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23399 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23400 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23401 ) {
23402 // Include deleted hunks that are adjacent to the query range, because
23403 // otherwise they would be missed.
23404 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23405 if hunk.status().is_deleted() {
23406 intersects_range |= hunk.row_range.start == query_rows.end;
23407 intersects_range |= hunk.row_range.end == query_rows.start;
23408 }
23409 if intersects_range {
23410 if !processed_buffer_rows
23411 .entry(hunk.buffer_id)
23412 .or_default()
23413 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23414 {
23415 continue;
23416 }
23417 hunks.push(hunk);
23418 }
23419 }
23420 }
23421
23422 hunks
23423 }
23424
23425 fn display_diff_hunks_for_rows<'a>(
23426 &'a self,
23427 display_rows: Range<DisplayRow>,
23428 folded_buffers: &'a HashSet<BufferId>,
23429 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23430 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23431 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23432
23433 self.buffer_snapshot()
23434 .diff_hunks_in_range(buffer_start..buffer_end)
23435 .filter_map(|hunk| {
23436 if folded_buffers.contains(&hunk.buffer_id) {
23437 return None;
23438 }
23439
23440 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23441 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23442
23443 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23444 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23445
23446 let display_hunk = if hunk_display_start.column() != 0 {
23447 DisplayDiffHunk::Folded {
23448 display_row: hunk_display_start.row(),
23449 }
23450 } else {
23451 let mut end_row = hunk_display_end.row();
23452 if hunk_display_end.column() > 0 {
23453 end_row.0 += 1;
23454 }
23455 let is_created_file = hunk.is_created_file();
23456 DisplayDiffHunk::Unfolded {
23457 status: hunk.status(),
23458 diff_base_byte_range: hunk.diff_base_byte_range,
23459 display_row_range: hunk_display_start.row()..end_row,
23460 multi_buffer_range: Anchor::range_in_buffer(
23461 hunk.excerpt_id,
23462 hunk.buffer_id,
23463 hunk.buffer_range,
23464 ),
23465 is_created_file,
23466 }
23467 };
23468
23469 Some(display_hunk)
23470 })
23471 }
23472
23473 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23474 self.display_snapshot
23475 .buffer_snapshot()
23476 .language_at(position)
23477 }
23478
23479 pub fn is_focused(&self) -> bool {
23480 self.is_focused
23481 }
23482
23483 pub fn placeholder_text(&self) -> Option<String> {
23484 self.placeholder_display_snapshot
23485 .as_ref()
23486 .map(|display_map| display_map.text())
23487 }
23488
23489 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23490 self.scroll_anchor.scroll_position(&self.display_snapshot)
23491 }
23492
23493 fn gutter_dimensions(
23494 &self,
23495 font_id: FontId,
23496 font_size: Pixels,
23497 max_line_number_width: Pixels,
23498 cx: &App,
23499 ) -> Option<GutterDimensions> {
23500 if !self.show_gutter {
23501 return None;
23502 }
23503
23504 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23505 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23506
23507 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23508 matches!(
23509 ProjectSettings::get_global(cx).git.git_gutter,
23510 GitGutterSetting::TrackedFiles
23511 )
23512 });
23513 let gutter_settings = EditorSettings::get_global(cx).gutter;
23514 let show_line_numbers = self
23515 .show_line_numbers
23516 .unwrap_or(gutter_settings.line_numbers);
23517 let line_gutter_width = if show_line_numbers {
23518 // Avoid flicker-like gutter resizes when the line number gains another digit by
23519 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23520 let min_width_for_number_on_gutter =
23521 ch_advance * gutter_settings.min_line_number_digits as f32;
23522 max_line_number_width.max(min_width_for_number_on_gutter)
23523 } else {
23524 0.0.into()
23525 };
23526
23527 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23528 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23529
23530 let git_blame_entries_width =
23531 self.git_blame_gutter_max_author_length
23532 .map(|max_author_length| {
23533 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23534 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23535
23536 /// The number of characters to dedicate to gaps and margins.
23537 const SPACING_WIDTH: usize = 4;
23538
23539 let max_char_count = max_author_length.min(renderer.max_author_length())
23540 + ::git::SHORT_SHA_LENGTH
23541 + MAX_RELATIVE_TIMESTAMP.len()
23542 + SPACING_WIDTH;
23543
23544 ch_advance * max_char_count
23545 });
23546
23547 let is_singleton = self.buffer_snapshot().is_singleton();
23548
23549 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23550 left_padding += if !is_singleton {
23551 ch_width * 4.0
23552 } else if show_runnables || show_breakpoints {
23553 ch_width * 3.0
23554 } else if show_git_gutter && show_line_numbers {
23555 ch_width * 2.0
23556 } else if show_git_gutter || show_line_numbers {
23557 ch_width
23558 } else {
23559 px(0.)
23560 };
23561
23562 let shows_folds = is_singleton && gutter_settings.folds;
23563
23564 let right_padding = if shows_folds && show_line_numbers {
23565 ch_width * 4.0
23566 } else if shows_folds || (!is_singleton && show_line_numbers) {
23567 ch_width * 3.0
23568 } else if show_line_numbers {
23569 ch_width
23570 } else {
23571 px(0.)
23572 };
23573
23574 Some(GutterDimensions {
23575 left_padding,
23576 right_padding,
23577 width: line_gutter_width + left_padding + right_padding,
23578 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23579 git_blame_entries_width,
23580 })
23581 }
23582
23583 pub fn render_crease_toggle(
23584 &self,
23585 buffer_row: MultiBufferRow,
23586 row_contains_cursor: bool,
23587 editor: Entity<Editor>,
23588 window: &mut Window,
23589 cx: &mut App,
23590 ) -> Option<AnyElement> {
23591 let folded = self.is_line_folded(buffer_row);
23592 let mut is_foldable = false;
23593
23594 if let Some(crease) = self
23595 .crease_snapshot
23596 .query_row(buffer_row, self.buffer_snapshot())
23597 {
23598 is_foldable = true;
23599 match crease {
23600 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23601 if let Some(render_toggle) = render_toggle {
23602 let toggle_callback =
23603 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23604 if folded {
23605 editor.update(cx, |editor, cx| {
23606 editor.fold_at(buffer_row, window, cx)
23607 });
23608 } else {
23609 editor.update(cx, |editor, cx| {
23610 editor.unfold_at(buffer_row, window, cx)
23611 });
23612 }
23613 });
23614 return Some((render_toggle)(
23615 buffer_row,
23616 folded,
23617 toggle_callback,
23618 window,
23619 cx,
23620 ));
23621 }
23622 }
23623 }
23624 }
23625
23626 is_foldable |= self.starts_indent(buffer_row);
23627
23628 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23629 Some(
23630 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23631 .toggle_state(folded)
23632 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23633 if folded {
23634 this.unfold_at(buffer_row, window, cx);
23635 } else {
23636 this.fold_at(buffer_row, window, cx);
23637 }
23638 }))
23639 .into_any_element(),
23640 )
23641 } else {
23642 None
23643 }
23644 }
23645
23646 pub fn render_crease_trailer(
23647 &self,
23648 buffer_row: MultiBufferRow,
23649 window: &mut Window,
23650 cx: &mut App,
23651 ) -> Option<AnyElement> {
23652 let folded = self.is_line_folded(buffer_row);
23653 if let Crease::Inline { render_trailer, .. } = self
23654 .crease_snapshot
23655 .query_row(buffer_row, self.buffer_snapshot())?
23656 {
23657 let render_trailer = render_trailer.as_ref()?;
23658 Some(render_trailer(buffer_row, folded, window, cx))
23659 } else {
23660 None
23661 }
23662 }
23663}
23664
23665impl Deref for EditorSnapshot {
23666 type Target = DisplaySnapshot;
23667
23668 fn deref(&self) -> &Self::Target {
23669 &self.display_snapshot
23670 }
23671}
23672
23673#[derive(Clone, Debug, PartialEq, Eq)]
23674pub enum EditorEvent {
23675 InputIgnored {
23676 text: Arc<str>,
23677 },
23678 InputHandled {
23679 utf16_range_to_replace: Option<Range<isize>>,
23680 text: Arc<str>,
23681 },
23682 ExcerptsAdded {
23683 buffer: Entity<Buffer>,
23684 predecessor: ExcerptId,
23685 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23686 },
23687 ExcerptsRemoved {
23688 ids: Vec<ExcerptId>,
23689 removed_buffer_ids: Vec<BufferId>,
23690 },
23691 BufferFoldToggled {
23692 ids: Vec<ExcerptId>,
23693 folded: bool,
23694 },
23695 ExcerptsEdited {
23696 ids: Vec<ExcerptId>,
23697 },
23698 ExcerptsExpanded {
23699 ids: Vec<ExcerptId>,
23700 },
23701 BufferEdited,
23702 Edited {
23703 transaction_id: clock::Lamport,
23704 },
23705 Reparsed(BufferId),
23706 Focused,
23707 FocusedIn,
23708 Blurred,
23709 DirtyChanged,
23710 Saved,
23711 TitleChanged,
23712 SelectionsChanged {
23713 local: bool,
23714 },
23715 ScrollPositionChanged {
23716 local: bool,
23717 autoscroll: bool,
23718 },
23719 TransactionUndone {
23720 transaction_id: clock::Lamport,
23721 },
23722 TransactionBegun {
23723 transaction_id: clock::Lamport,
23724 },
23725 CursorShapeChanged,
23726 BreadcrumbsChanged,
23727 PushedToNavHistory {
23728 anchor: Anchor,
23729 is_deactivate: bool,
23730 },
23731}
23732
23733impl EventEmitter<EditorEvent> for Editor {}
23734
23735impl Focusable for Editor {
23736 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23737 self.focus_handle.clone()
23738 }
23739}
23740
23741impl Render for Editor {
23742 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23743 let settings = ThemeSettings::get_global(cx);
23744
23745 let mut text_style = match self.mode {
23746 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23747 color: cx.theme().colors().editor_foreground,
23748 font_family: settings.ui_font.family.clone(),
23749 font_features: settings.ui_font.features.clone(),
23750 font_fallbacks: settings.ui_font.fallbacks.clone(),
23751 font_size: rems(0.875).into(),
23752 font_weight: settings.ui_font.weight,
23753 line_height: relative(settings.buffer_line_height.value()),
23754 ..Default::default()
23755 },
23756 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23757 color: cx.theme().colors().editor_foreground,
23758 font_family: settings.buffer_font.family.clone(),
23759 font_features: settings.buffer_font.features.clone(),
23760 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23761 font_size: settings.buffer_font_size(cx).into(),
23762 font_weight: settings.buffer_font.weight,
23763 line_height: relative(settings.buffer_line_height.value()),
23764 ..Default::default()
23765 },
23766 };
23767 if let Some(text_style_refinement) = &self.text_style_refinement {
23768 text_style.refine(text_style_refinement)
23769 }
23770
23771 let background = match self.mode {
23772 EditorMode::SingleLine => cx.theme().system().transparent,
23773 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23774 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23775 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23776 };
23777
23778 EditorElement::new(
23779 &cx.entity(),
23780 EditorStyle {
23781 background,
23782 border: cx.theme().colors().border,
23783 local_player: cx.theme().players().local(),
23784 text: text_style,
23785 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23786 syntax: cx.theme().syntax().clone(),
23787 status: cx.theme().status().clone(),
23788 inlay_hints_style: make_inlay_hints_style(cx),
23789 edit_prediction_styles: make_suggestion_styles(cx),
23790 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23791 show_underlines: self.diagnostics_enabled(),
23792 },
23793 )
23794 }
23795}
23796
23797impl EntityInputHandler for Editor {
23798 fn text_for_range(
23799 &mut self,
23800 range_utf16: Range<usize>,
23801 adjusted_range: &mut Option<Range<usize>>,
23802 _: &mut Window,
23803 cx: &mut Context<Self>,
23804 ) -> Option<String> {
23805 let snapshot = self.buffer.read(cx).read(cx);
23806 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23807 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23808 if (start.0..end.0) != range_utf16 {
23809 adjusted_range.replace(start.0..end.0);
23810 }
23811 Some(snapshot.text_for_range(start..end).collect())
23812 }
23813
23814 fn selected_text_range(
23815 &mut self,
23816 ignore_disabled_input: bool,
23817 _: &mut Window,
23818 cx: &mut Context<Self>,
23819 ) -> Option<UTF16Selection> {
23820 // Prevent the IME menu from appearing when holding down an alphabetic key
23821 // while input is disabled.
23822 if !ignore_disabled_input && !self.input_enabled {
23823 return None;
23824 }
23825
23826 let selection = self
23827 .selections
23828 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23829 let range = selection.range();
23830
23831 Some(UTF16Selection {
23832 range: range.start.0..range.end.0,
23833 reversed: selection.reversed,
23834 })
23835 }
23836
23837 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23838 let snapshot = self.buffer.read(cx).read(cx);
23839 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23840 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23841 }
23842
23843 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23844 self.clear_highlights::<InputComposition>(cx);
23845 self.ime_transaction.take();
23846 }
23847
23848 fn replace_text_in_range(
23849 &mut self,
23850 range_utf16: Option<Range<usize>>,
23851 text: &str,
23852 window: &mut Window,
23853 cx: &mut Context<Self>,
23854 ) {
23855 if !self.input_enabled {
23856 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23857 return;
23858 }
23859
23860 self.transact(window, cx, |this, window, cx| {
23861 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23862 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23863 Some(this.selection_replacement_ranges(range_utf16, cx))
23864 } else {
23865 this.marked_text_ranges(cx)
23866 };
23867
23868 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23869 let newest_selection_id = this.selections.newest_anchor().id;
23870 this.selections
23871 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23872 .iter()
23873 .zip(ranges_to_replace.iter())
23874 .find_map(|(selection, range)| {
23875 if selection.id == newest_selection_id {
23876 Some(
23877 (range.start.0 as isize - selection.head().0 as isize)
23878 ..(range.end.0 as isize - selection.head().0 as isize),
23879 )
23880 } else {
23881 None
23882 }
23883 })
23884 });
23885
23886 cx.emit(EditorEvent::InputHandled {
23887 utf16_range_to_replace: range_to_replace,
23888 text: text.into(),
23889 });
23890
23891 if let Some(new_selected_ranges) = new_selected_ranges {
23892 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23893 selections.select_ranges(new_selected_ranges)
23894 });
23895 this.backspace(&Default::default(), window, cx);
23896 }
23897
23898 this.handle_input(text, window, cx);
23899 });
23900
23901 if let Some(transaction) = self.ime_transaction {
23902 self.buffer.update(cx, |buffer, cx| {
23903 buffer.group_until_transaction(transaction, cx);
23904 });
23905 }
23906
23907 self.unmark_text(window, cx);
23908 }
23909
23910 fn replace_and_mark_text_in_range(
23911 &mut self,
23912 range_utf16: Option<Range<usize>>,
23913 text: &str,
23914 new_selected_range_utf16: Option<Range<usize>>,
23915 window: &mut Window,
23916 cx: &mut Context<Self>,
23917 ) {
23918 if !self.input_enabled {
23919 return;
23920 }
23921
23922 let transaction = self.transact(window, cx, |this, window, cx| {
23923 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23924 let snapshot = this.buffer.read(cx).read(cx);
23925 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23926 for marked_range in &mut marked_ranges {
23927 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23928 marked_range.start.0 += relative_range_utf16.start;
23929 marked_range.start =
23930 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23931 marked_range.end =
23932 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23933 }
23934 }
23935 Some(marked_ranges)
23936 } else if let Some(range_utf16) = range_utf16 {
23937 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23938 Some(this.selection_replacement_ranges(range_utf16, cx))
23939 } else {
23940 None
23941 };
23942
23943 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23944 let newest_selection_id = this.selections.newest_anchor().id;
23945 this.selections
23946 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23947 .iter()
23948 .zip(ranges_to_replace.iter())
23949 .find_map(|(selection, range)| {
23950 if selection.id == newest_selection_id {
23951 Some(
23952 (range.start.0 as isize - selection.head().0 as isize)
23953 ..(range.end.0 as isize - selection.head().0 as isize),
23954 )
23955 } else {
23956 None
23957 }
23958 })
23959 });
23960
23961 cx.emit(EditorEvent::InputHandled {
23962 utf16_range_to_replace: range_to_replace,
23963 text: text.into(),
23964 });
23965
23966 if let Some(ranges) = ranges_to_replace {
23967 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23968 s.select_ranges(ranges)
23969 });
23970 }
23971
23972 let marked_ranges = {
23973 let snapshot = this.buffer.read(cx).read(cx);
23974 this.selections
23975 .disjoint_anchors_arc()
23976 .iter()
23977 .map(|selection| {
23978 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23979 })
23980 .collect::<Vec<_>>()
23981 };
23982
23983 if text.is_empty() {
23984 this.unmark_text(window, cx);
23985 } else {
23986 this.highlight_text::<InputComposition>(
23987 marked_ranges.clone(),
23988 HighlightStyle {
23989 underline: Some(UnderlineStyle {
23990 thickness: px(1.),
23991 color: None,
23992 wavy: false,
23993 }),
23994 ..Default::default()
23995 },
23996 cx,
23997 );
23998 }
23999
24000 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24001 let use_autoclose = this.use_autoclose;
24002 let use_auto_surround = this.use_auto_surround;
24003 this.set_use_autoclose(false);
24004 this.set_use_auto_surround(false);
24005 this.handle_input(text, window, cx);
24006 this.set_use_autoclose(use_autoclose);
24007 this.set_use_auto_surround(use_auto_surround);
24008
24009 if let Some(new_selected_range) = new_selected_range_utf16 {
24010 let snapshot = this.buffer.read(cx).read(cx);
24011 let new_selected_ranges = marked_ranges
24012 .into_iter()
24013 .map(|marked_range| {
24014 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24015 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24016 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24017 snapshot.clip_offset_utf16(new_start, Bias::Left)
24018 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24019 })
24020 .collect::<Vec<_>>();
24021
24022 drop(snapshot);
24023 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24024 selections.select_ranges(new_selected_ranges)
24025 });
24026 }
24027 });
24028
24029 self.ime_transaction = self.ime_transaction.or(transaction);
24030 if let Some(transaction) = self.ime_transaction {
24031 self.buffer.update(cx, |buffer, cx| {
24032 buffer.group_until_transaction(transaction, cx);
24033 });
24034 }
24035
24036 if self.text_highlights::<InputComposition>(cx).is_none() {
24037 self.ime_transaction.take();
24038 }
24039 }
24040
24041 fn bounds_for_range(
24042 &mut self,
24043 range_utf16: Range<usize>,
24044 element_bounds: gpui::Bounds<Pixels>,
24045 window: &mut Window,
24046 cx: &mut Context<Self>,
24047 ) -> Option<gpui::Bounds<Pixels>> {
24048 let text_layout_details = self.text_layout_details(window);
24049 let CharacterDimensions {
24050 em_width,
24051 em_advance,
24052 line_height,
24053 } = self.character_dimensions(window);
24054
24055 let snapshot = self.snapshot(window, cx);
24056 let scroll_position = snapshot.scroll_position();
24057 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24058
24059 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24060 let x = Pixels::from(
24061 ScrollOffset::from(
24062 snapshot.x_for_display_point(start, &text_layout_details)
24063 + self.gutter_dimensions.full_width(),
24064 ) - scroll_left,
24065 );
24066 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24067
24068 Some(Bounds {
24069 origin: element_bounds.origin + point(x, y),
24070 size: size(em_width, line_height),
24071 })
24072 }
24073
24074 fn character_index_for_point(
24075 &mut self,
24076 point: gpui::Point<Pixels>,
24077 _window: &mut Window,
24078 _cx: &mut Context<Self>,
24079 ) -> Option<usize> {
24080 let position_map = self.last_position_map.as_ref()?;
24081 if !position_map.text_hitbox.contains(&point) {
24082 return None;
24083 }
24084 let display_point = position_map.point_for_position(point).previous_valid;
24085 let anchor = position_map
24086 .snapshot
24087 .display_point_to_anchor(display_point, Bias::Left);
24088 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24089 Some(utf16_offset.0)
24090 }
24091}
24092
24093trait SelectionExt {
24094 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24095 fn spanned_rows(
24096 &self,
24097 include_end_if_at_line_start: bool,
24098 map: &DisplaySnapshot,
24099 ) -> Range<MultiBufferRow>;
24100}
24101
24102impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24103 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24104 let start = self
24105 .start
24106 .to_point(map.buffer_snapshot())
24107 .to_display_point(map);
24108 let end = self
24109 .end
24110 .to_point(map.buffer_snapshot())
24111 .to_display_point(map);
24112 if self.reversed {
24113 end..start
24114 } else {
24115 start..end
24116 }
24117 }
24118
24119 fn spanned_rows(
24120 &self,
24121 include_end_if_at_line_start: bool,
24122 map: &DisplaySnapshot,
24123 ) -> Range<MultiBufferRow> {
24124 let start = self.start.to_point(map.buffer_snapshot());
24125 let mut end = self.end.to_point(map.buffer_snapshot());
24126 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24127 end.row -= 1;
24128 }
24129
24130 let buffer_start = map.prev_line_boundary(start).0;
24131 let buffer_end = map.next_line_boundary(end).0;
24132 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24133 }
24134}
24135
24136impl<T: InvalidationRegion> InvalidationStack<T> {
24137 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24138 where
24139 S: Clone + ToOffset,
24140 {
24141 while let Some(region) = self.last() {
24142 let all_selections_inside_invalidation_ranges =
24143 if selections.len() == region.ranges().len() {
24144 selections
24145 .iter()
24146 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24147 .all(|(selection, invalidation_range)| {
24148 let head = selection.head().to_offset(buffer);
24149 invalidation_range.start <= head && invalidation_range.end >= head
24150 })
24151 } else {
24152 false
24153 };
24154
24155 if all_selections_inside_invalidation_ranges {
24156 break;
24157 } else {
24158 self.pop();
24159 }
24160 }
24161 }
24162}
24163
24164impl<T> Default for InvalidationStack<T> {
24165 fn default() -> Self {
24166 Self(Default::default())
24167 }
24168}
24169
24170impl<T> Deref for InvalidationStack<T> {
24171 type Target = Vec<T>;
24172
24173 fn deref(&self) -> &Self::Target {
24174 &self.0
24175 }
24176}
24177
24178impl<T> DerefMut for InvalidationStack<T> {
24179 fn deref_mut(&mut self) -> &mut Self::Target {
24180 &mut self.0
24181 }
24182}
24183
24184impl InvalidationRegion for SnippetState {
24185 fn ranges(&self) -> &[Range<Anchor>] {
24186 &self.ranges[self.active_index]
24187 }
24188}
24189
24190fn edit_prediction_edit_text(
24191 current_snapshot: &BufferSnapshot,
24192 edits: &[(Range<Anchor>, String)],
24193 edit_preview: &EditPreview,
24194 include_deletions: bool,
24195 cx: &App,
24196) -> HighlightedText {
24197 let edits = edits
24198 .iter()
24199 .map(|(anchor, text)| {
24200 (
24201 anchor.start.text_anchor..anchor.end.text_anchor,
24202 text.clone(),
24203 )
24204 })
24205 .collect::<Vec<_>>();
24206
24207 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24208}
24209
24210fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24211 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24212 // Just show the raw edit text with basic styling
24213 let mut text = String::new();
24214 let mut highlights = Vec::new();
24215
24216 let insertion_highlight_style = HighlightStyle {
24217 color: Some(cx.theme().colors().text),
24218 ..Default::default()
24219 };
24220
24221 for (_, edit_text) in edits {
24222 let start_offset = text.len();
24223 text.push_str(edit_text);
24224 let end_offset = text.len();
24225
24226 if start_offset < end_offset {
24227 highlights.push((start_offset..end_offset, insertion_highlight_style));
24228 }
24229 }
24230
24231 HighlightedText {
24232 text: text.into(),
24233 highlights,
24234 }
24235}
24236
24237pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24238 match severity {
24239 lsp::DiagnosticSeverity::ERROR => colors.error,
24240 lsp::DiagnosticSeverity::WARNING => colors.warning,
24241 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24242 lsp::DiagnosticSeverity::HINT => colors.info,
24243 _ => colors.ignored,
24244 }
24245}
24246
24247pub fn styled_runs_for_code_label<'a>(
24248 label: &'a CodeLabel,
24249 syntax_theme: &'a theme::SyntaxTheme,
24250) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24251 let fade_out = HighlightStyle {
24252 fade_out: Some(0.35),
24253 ..Default::default()
24254 };
24255
24256 let mut prev_end = label.filter_range.end;
24257 label
24258 .runs
24259 .iter()
24260 .enumerate()
24261 .flat_map(move |(ix, (range, highlight_id))| {
24262 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24263 style
24264 } else {
24265 return Default::default();
24266 };
24267 let muted_style = style.highlight(fade_out);
24268
24269 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24270 if range.start >= label.filter_range.end {
24271 if range.start > prev_end {
24272 runs.push((prev_end..range.start, fade_out));
24273 }
24274 runs.push((range.clone(), muted_style));
24275 } else if range.end <= label.filter_range.end {
24276 runs.push((range.clone(), style));
24277 } else {
24278 runs.push((range.start..label.filter_range.end, style));
24279 runs.push((label.filter_range.end..range.end, muted_style));
24280 }
24281 prev_end = cmp::max(prev_end, range.end);
24282
24283 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24284 runs.push((prev_end..label.text.len(), fade_out));
24285 }
24286
24287 runs
24288 })
24289}
24290
24291pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24292 let mut prev_index = 0;
24293 let mut prev_codepoint: Option<char> = None;
24294 text.char_indices()
24295 .chain([(text.len(), '\0')])
24296 .filter_map(move |(index, codepoint)| {
24297 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24298 let is_boundary = index == text.len()
24299 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24300 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24301 if is_boundary {
24302 let chunk = &text[prev_index..index];
24303 prev_index = index;
24304 Some(chunk)
24305 } else {
24306 None
24307 }
24308 })
24309}
24310
24311pub trait RangeToAnchorExt: Sized {
24312 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24313
24314 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24315 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24316 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24317 }
24318}
24319
24320impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24321 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24322 let start_offset = self.start.to_offset(snapshot);
24323 let end_offset = self.end.to_offset(snapshot);
24324 if start_offset == end_offset {
24325 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24326 } else {
24327 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24328 }
24329 }
24330}
24331
24332pub trait RowExt {
24333 fn as_f64(&self) -> f64;
24334
24335 fn next_row(&self) -> Self;
24336
24337 fn previous_row(&self) -> Self;
24338
24339 fn minus(&self, other: Self) -> u32;
24340}
24341
24342impl RowExt for DisplayRow {
24343 fn as_f64(&self) -> f64 {
24344 self.0 as _
24345 }
24346
24347 fn next_row(&self) -> Self {
24348 Self(self.0 + 1)
24349 }
24350
24351 fn previous_row(&self) -> Self {
24352 Self(self.0.saturating_sub(1))
24353 }
24354
24355 fn minus(&self, other: Self) -> u32 {
24356 self.0 - other.0
24357 }
24358}
24359
24360impl RowExt for MultiBufferRow {
24361 fn as_f64(&self) -> f64 {
24362 self.0 as _
24363 }
24364
24365 fn next_row(&self) -> Self {
24366 Self(self.0 + 1)
24367 }
24368
24369 fn previous_row(&self) -> Self {
24370 Self(self.0.saturating_sub(1))
24371 }
24372
24373 fn minus(&self, other: Self) -> u32 {
24374 self.0 - other.0
24375 }
24376}
24377
24378trait RowRangeExt {
24379 type Row;
24380
24381 fn len(&self) -> usize;
24382
24383 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24384}
24385
24386impl RowRangeExt for Range<MultiBufferRow> {
24387 type Row = MultiBufferRow;
24388
24389 fn len(&self) -> usize {
24390 (self.end.0 - self.start.0) as usize
24391 }
24392
24393 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24394 (self.start.0..self.end.0).map(MultiBufferRow)
24395 }
24396}
24397
24398impl RowRangeExt for Range<DisplayRow> {
24399 type Row = DisplayRow;
24400
24401 fn len(&self) -> usize {
24402 (self.end.0 - self.start.0) as usize
24403 }
24404
24405 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24406 (self.start.0..self.end.0).map(DisplayRow)
24407 }
24408}
24409
24410/// If select range has more than one line, we
24411/// just point the cursor to range.start.
24412fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24413 if range.start.row == range.end.row {
24414 range
24415 } else {
24416 range.start..range.start
24417 }
24418}
24419pub struct KillRing(ClipboardItem);
24420impl Global for KillRing {}
24421
24422const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24423
24424enum BreakpointPromptEditAction {
24425 Log,
24426 Condition,
24427 HitCondition,
24428}
24429
24430struct BreakpointPromptEditor {
24431 pub(crate) prompt: Entity<Editor>,
24432 editor: WeakEntity<Editor>,
24433 breakpoint_anchor: Anchor,
24434 breakpoint: Breakpoint,
24435 edit_action: BreakpointPromptEditAction,
24436 block_ids: HashSet<CustomBlockId>,
24437 editor_margins: Arc<Mutex<EditorMargins>>,
24438 _subscriptions: Vec<Subscription>,
24439}
24440
24441impl BreakpointPromptEditor {
24442 const MAX_LINES: u8 = 4;
24443
24444 fn new(
24445 editor: WeakEntity<Editor>,
24446 breakpoint_anchor: Anchor,
24447 breakpoint: Breakpoint,
24448 edit_action: BreakpointPromptEditAction,
24449 window: &mut Window,
24450 cx: &mut Context<Self>,
24451 ) -> Self {
24452 let base_text = match edit_action {
24453 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24454 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24455 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24456 }
24457 .map(|msg| msg.to_string())
24458 .unwrap_or_default();
24459
24460 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24461 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24462
24463 let prompt = cx.new(|cx| {
24464 let mut prompt = Editor::new(
24465 EditorMode::AutoHeight {
24466 min_lines: 1,
24467 max_lines: Some(Self::MAX_LINES as usize),
24468 },
24469 buffer,
24470 None,
24471 window,
24472 cx,
24473 );
24474 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24475 prompt.set_show_cursor_when_unfocused(false, cx);
24476 prompt.set_placeholder_text(
24477 match edit_action {
24478 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24479 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24480 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24481 },
24482 window,
24483 cx,
24484 );
24485
24486 prompt
24487 });
24488
24489 Self {
24490 prompt,
24491 editor,
24492 breakpoint_anchor,
24493 breakpoint,
24494 edit_action,
24495 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24496 block_ids: Default::default(),
24497 _subscriptions: vec![],
24498 }
24499 }
24500
24501 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24502 self.block_ids.extend(block_ids)
24503 }
24504
24505 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24506 if let Some(editor) = self.editor.upgrade() {
24507 let message = self
24508 .prompt
24509 .read(cx)
24510 .buffer
24511 .read(cx)
24512 .as_singleton()
24513 .expect("A multi buffer in breakpoint prompt isn't possible")
24514 .read(cx)
24515 .as_rope()
24516 .to_string();
24517
24518 editor.update(cx, |editor, cx| {
24519 editor.edit_breakpoint_at_anchor(
24520 self.breakpoint_anchor,
24521 self.breakpoint.clone(),
24522 match self.edit_action {
24523 BreakpointPromptEditAction::Log => {
24524 BreakpointEditAction::EditLogMessage(message.into())
24525 }
24526 BreakpointPromptEditAction::Condition => {
24527 BreakpointEditAction::EditCondition(message.into())
24528 }
24529 BreakpointPromptEditAction::HitCondition => {
24530 BreakpointEditAction::EditHitCondition(message.into())
24531 }
24532 },
24533 cx,
24534 );
24535
24536 editor.remove_blocks(self.block_ids.clone(), None, cx);
24537 cx.focus_self(window);
24538 });
24539 }
24540 }
24541
24542 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24543 self.editor
24544 .update(cx, |editor, cx| {
24545 editor.remove_blocks(self.block_ids.clone(), None, cx);
24546 window.focus(&editor.focus_handle);
24547 })
24548 .log_err();
24549 }
24550
24551 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24552 let settings = ThemeSettings::get_global(cx);
24553 let text_style = TextStyle {
24554 color: if self.prompt.read(cx).read_only(cx) {
24555 cx.theme().colors().text_disabled
24556 } else {
24557 cx.theme().colors().text
24558 },
24559 font_family: settings.buffer_font.family.clone(),
24560 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24561 font_size: settings.buffer_font_size(cx).into(),
24562 font_weight: settings.buffer_font.weight,
24563 line_height: relative(settings.buffer_line_height.value()),
24564 ..Default::default()
24565 };
24566 EditorElement::new(
24567 &self.prompt,
24568 EditorStyle {
24569 background: cx.theme().colors().editor_background,
24570 local_player: cx.theme().players().local(),
24571 text: text_style,
24572 ..Default::default()
24573 },
24574 )
24575 }
24576}
24577
24578impl Render for BreakpointPromptEditor {
24579 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24580 let editor_margins = *self.editor_margins.lock();
24581 let gutter_dimensions = editor_margins.gutter;
24582 h_flex()
24583 .key_context("Editor")
24584 .bg(cx.theme().colors().editor_background)
24585 .border_y_1()
24586 .border_color(cx.theme().status().info_border)
24587 .size_full()
24588 .py(window.line_height() / 2.5)
24589 .on_action(cx.listener(Self::confirm))
24590 .on_action(cx.listener(Self::cancel))
24591 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24592 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24593 }
24594}
24595
24596impl Focusable for BreakpointPromptEditor {
24597 fn focus_handle(&self, cx: &App) -> FocusHandle {
24598 self.prompt.focus_handle(cx)
24599 }
24600}
24601
24602fn all_edits_insertions_or_deletions(
24603 edits: &Vec<(Range<Anchor>, String)>,
24604 snapshot: &MultiBufferSnapshot,
24605) -> bool {
24606 let mut all_insertions = true;
24607 let mut all_deletions = true;
24608
24609 for (range, new_text) in edits.iter() {
24610 let range_is_empty = range.to_offset(snapshot).is_empty();
24611 let text_is_empty = new_text.is_empty();
24612
24613 if range_is_empty != text_is_empty {
24614 if range_is_empty {
24615 all_deletions = false;
24616 } else {
24617 all_insertions = false;
24618 }
24619 } else {
24620 return false;
24621 }
24622
24623 if !all_insertions && !all_deletions {
24624 return false;
24625 }
24626 }
24627 all_insertions || all_deletions
24628}
24629
24630struct MissingEditPredictionKeybindingTooltip;
24631
24632impl Render for MissingEditPredictionKeybindingTooltip {
24633 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24634 ui::tooltip_container(cx, |container, cx| {
24635 container
24636 .flex_shrink_0()
24637 .max_w_80()
24638 .min_h(rems_from_px(124.))
24639 .justify_between()
24640 .child(
24641 v_flex()
24642 .flex_1()
24643 .text_ui_sm(cx)
24644 .child(Label::new("Conflict with Accept Keybinding"))
24645 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24646 )
24647 .child(
24648 h_flex()
24649 .pb_1()
24650 .gap_1()
24651 .items_end()
24652 .w_full()
24653 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24654 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24655 }))
24656 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24657 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24658 })),
24659 )
24660 })
24661 }
24662}
24663
24664#[derive(Debug, Clone, Copy, PartialEq)]
24665pub struct LineHighlight {
24666 pub background: Background,
24667 pub border: Option<gpui::Hsla>,
24668 pub include_gutter: bool,
24669 pub type_id: Option<TypeId>,
24670}
24671
24672struct LineManipulationResult {
24673 pub new_text: String,
24674 pub line_count_before: usize,
24675 pub line_count_after: usize,
24676}
24677
24678fn render_diff_hunk_controls(
24679 row: u32,
24680 status: &DiffHunkStatus,
24681 hunk_range: Range<Anchor>,
24682 is_created_file: bool,
24683 line_height: Pixels,
24684 editor: &Entity<Editor>,
24685 _window: &mut Window,
24686 cx: &mut App,
24687) -> AnyElement {
24688 h_flex()
24689 .h(line_height)
24690 .mr_1()
24691 .gap_1()
24692 .px_0p5()
24693 .pb_1()
24694 .border_x_1()
24695 .border_b_1()
24696 .border_color(cx.theme().colors().border_variant)
24697 .rounded_b_lg()
24698 .bg(cx.theme().colors().editor_background)
24699 .gap_1()
24700 .block_mouse_except_scroll()
24701 .shadow_md()
24702 .child(if status.has_secondary_hunk() {
24703 Button::new(("stage", row as u64), "Stage")
24704 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24705 .tooltip({
24706 let focus_handle = editor.focus_handle(cx);
24707 move |_window, cx| {
24708 Tooltip::for_action_in(
24709 "Stage Hunk",
24710 &::git::ToggleStaged,
24711 &focus_handle,
24712 cx,
24713 )
24714 }
24715 })
24716 .on_click({
24717 let editor = editor.clone();
24718 move |_event, _window, cx| {
24719 editor.update(cx, |editor, cx| {
24720 editor.stage_or_unstage_diff_hunks(
24721 true,
24722 vec![hunk_range.start..hunk_range.start],
24723 cx,
24724 );
24725 });
24726 }
24727 })
24728 } else {
24729 Button::new(("unstage", row as u64), "Unstage")
24730 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24731 .tooltip({
24732 let focus_handle = editor.focus_handle(cx);
24733 move |_window, cx| {
24734 Tooltip::for_action_in(
24735 "Unstage Hunk",
24736 &::git::ToggleStaged,
24737 &focus_handle,
24738 cx,
24739 )
24740 }
24741 })
24742 .on_click({
24743 let editor = editor.clone();
24744 move |_event, _window, cx| {
24745 editor.update(cx, |editor, cx| {
24746 editor.stage_or_unstage_diff_hunks(
24747 false,
24748 vec![hunk_range.start..hunk_range.start],
24749 cx,
24750 );
24751 });
24752 }
24753 })
24754 })
24755 .child(
24756 Button::new(("restore", row as u64), "Restore")
24757 .tooltip({
24758 let focus_handle = editor.focus_handle(cx);
24759 move |_window, cx| {
24760 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24761 }
24762 })
24763 .on_click({
24764 let editor = editor.clone();
24765 move |_event, window, cx| {
24766 editor.update(cx, |editor, cx| {
24767 let snapshot = editor.snapshot(window, cx);
24768 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24769 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24770 });
24771 }
24772 })
24773 .disabled(is_created_file),
24774 )
24775 .when(
24776 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24777 |el| {
24778 el.child(
24779 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24780 .shape(IconButtonShape::Square)
24781 .icon_size(IconSize::Small)
24782 // .disabled(!has_multiple_hunks)
24783 .tooltip({
24784 let focus_handle = editor.focus_handle(cx);
24785 move |_window, cx| {
24786 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24787 }
24788 })
24789 .on_click({
24790 let editor = editor.clone();
24791 move |_event, window, cx| {
24792 editor.update(cx, |editor, cx| {
24793 let snapshot = editor.snapshot(window, cx);
24794 let position =
24795 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24796 editor.go_to_hunk_before_or_after_position(
24797 &snapshot,
24798 position,
24799 Direction::Next,
24800 window,
24801 cx,
24802 );
24803 editor.expand_selected_diff_hunks(cx);
24804 });
24805 }
24806 }),
24807 )
24808 .child(
24809 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24810 .shape(IconButtonShape::Square)
24811 .icon_size(IconSize::Small)
24812 // .disabled(!has_multiple_hunks)
24813 .tooltip({
24814 let focus_handle = editor.focus_handle(cx);
24815 move |_window, cx| {
24816 Tooltip::for_action_in(
24817 "Previous Hunk",
24818 &GoToPreviousHunk,
24819 &focus_handle,
24820 cx,
24821 )
24822 }
24823 })
24824 .on_click({
24825 let editor = editor.clone();
24826 move |_event, window, cx| {
24827 editor.update(cx, |editor, cx| {
24828 let snapshot = editor.snapshot(window, cx);
24829 let point =
24830 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24831 editor.go_to_hunk_before_or_after_position(
24832 &snapshot,
24833 point,
24834 Direction::Prev,
24835 window,
24836 cx,
24837 );
24838 editor.expand_selected_diff_hunks(cx);
24839 });
24840 }
24841 }),
24842 )
24843 },
24844 )
24845 .into_any_element()
24846}
24847
24848pub fn multibuffer_context_lines(cx: &App) -> u32 {
24849 EditorSettings::try_get(cx)
24850 .map(|settings| settings.excerpt_context_lines)
24851 .unwrap_or(2)
24852 .min(32)
24853}