1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{
167 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
168 update_settings_file,
169};
170use smallvec::{SmallVec, smallvec};
171use snippet::Snippet;
172use std::{
173 any::{Any, TypeId},
174 borrow::Cow,
175 cell::{OnceCell, RefCell},
176 cmp::{self, Ordering, Reverse},
177 iter::{self, Peekable},
178 mem,
179 num::NonZeroU32,
180 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 sync::Arc,
184 time::{Duration, Instant},
185};
186use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::{
207 code_context_menus::CompletionsMenuSource,
208 editor_settings::MultiCursorModifier,
209 hover_links::{find_url, find_url_from_range},
210 inlays::{
211 InlineValueCache,
212 inlay_hints::{LspInlayHintData, inlay_hint_settings},
213 },
214 scroll::{ScrollOffset, ScrollPixelOffset},
215 selections_collection::resolve_selections_wrapping_blocks,
216 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
217};
218
219pub const FILE_HEADER_HEIGHT: u32 = 2;
220pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268pub enum ActiveDebugLine {}
269pub enum DebugStackFrameLine {}
270enum DocumentHighlightRead {}
271enum DocumentHighlightWrite {}
272enum InputComposition {}
273pub enum PendingInput {}
274enum SelectedTextHighlight {}
275
276pub enum ConflictsOuter {}
277pub enum ConflictsOurs {}
278pub enum ConflictsTheirs {}
279pub enum ConflictsOursMarker {}
280pub enum ConflictsTheirsMarker {}
281
282#[derive(Debug, Copy, Clone, PartialEq, Eq)]
283pub enum Navigated {
284 Yes,
285 No,
286}
287
288impl Navigated {
289 pub fn from_bool(yes: bool) -> Navigated {
290 if yes { Navigated::Yes } else { Navigated::No }
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
295enum DisplayDiffHunk {
296 Folded {
297 display_row: DisplayRow,
298 },
299 Unfolded {
300 is_created_file: bool,
301 diff_base_byte_range: Range<usize>,
302 display_row_range: Range<DisplayRow>,
303 multi_buffer_range: Range<Anchor>,
304 status: DiffHunkStatus,
305 },
306}
307
308pub enum HideMouseCursorOrigin {
309 TypingAction,
310 MovementAction,
311}
312
313pub fn init_settings(cx: &mut App) {
314 EditorSettings::register(cx);
315}
316
317pub fn init(cx: &mut App) {
318 init_settings(cx);
319
320 cx.set_global(GlobalBlameRenderer(Arc::new(())));
321
322 workspace::register_project_item::<Editor>(cx);
323 workspace::FollowableViewRegistry::register::<Editor>(cx);
324 workspace::register_serializable_item::<Editor>(cx);
325
326 cx.observe_new(
327 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
328 workspace.register_action(Editor::new_file);
329 workspace.register_action(Editor::new_file_split);
330 workspace.register_action(Editor::new_file_vertical);
331 workspace.register_action(Editor::new_file_horizontal);
332 workspace.register_action(Editor::cancel_language_server_work);
333 workspace.register_action(Editor::toggle_focus);
334 },
335 )
336 .detach();
337
338 cx.on_action(move |_: &workspace::NewFile, cx| {
339 let app_state = workspace::AppState::global(cx);
340 if let Some(app_state) = app_state.upgrade() {
341 workspace::open_new(
342 Default::default(),
343 app_state,
344 cx,
345 |workspace, window, cx| {
346 Editor::new_file(workspace, &Default::default(), window, cx)
347 },
348 )
349 .detach();
350 }
351 });
352 cx.on_action(move |_: &workspace::NewWindow, cx| {
353 let app_state = workspace::AppState::global(cx);
354 if let Some(app_state) = app_state.upgrade() {
355 workspace::open_new(
356 Default::default(),
357 app_state,
358 cx,
359 |workspace, window, cx| {
360 cx.activate(true);
361 Editor::new_file(workspace, &Default::default(), window, cx)
362 },
363 )
364 .detach();
365 }
366 });
367}
368
369pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
370 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
371}
372
373pub trait DiagnosticRenderer {
374 fn render_group(
375 &self,
376 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
377 buffer_id: BufferId,
378 snapshot: EditorSnapshot,
379 editor: WeakEntity<Editor>,
380 cx: &mut App,
381 ) -> Vec<BlockProperties<Anchor>>;
382
383 fn render_hover(
384 &self,
385 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
386 range: Range<Point>,
387 buffer_id: BufferId,
388 cx: &mut App,
389 ) -> Option<Entity<markdown::Markdown>>;
390
391 fn open_link(
392 &self,
393 editor: &mut Editor,
394 link: SharedString,
395 window: &mut Window,
396 cx: &mut Context<Editor>,
397 );
398}
399
400pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
401
402impl GlobalDiagnosticRenderer {
403 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
404 cx.try_global::<Self>().map(|g| g.0.clone())
405 }
406}
407
408impl gpui::Global for GlobalDiagnosticRenderer {}
409pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
410 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
411}
412
413pub struct SearchWithinRange;
414
415trait InvalidationRegion {
416 fn ranges(&self) -> &[Range<Anchor>];
417}
418
419#[derive(Clone, Debug, PartialEq)]
420pub enum SelectPhase {
421 Begin {
422 position: DisplayPoint,
423 add: bool,
424 click_count: usize,
425 },
426 BeginColumnar {
427 position: DisplayPoint,
428 reset: bool,
429 mode: ColumnarMode,
430 goal_column: u32,
431 },
432 Extend {
433 position: DisplayPoint,
434 click_count: usize,
435 },
436 Update {
437 position: DisplayPoint,
438 goal_column: u32,
439 scroll_delta: gpui::Point<f32>,
440 },
441 End,
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum ColumnarMode {
446 FromMouse,
447 FromSelection,
448}
449
450#[derive(Clone, Debug)]
451pub enum SelectMode {
452 Character,
453 Word(Range<Anchor>),
454 Line(Range<Anchor>),
455 All,
456}
457
458#[derive(Clone, PartialEq, Eq, Debug)]
459pub enum EditorMode {
460 SingleLine,
461 AutoHeight {
462 min_lines: usize,
463 max_lines: Option<usize>,
464 },
465 Full {
466 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
467 scale_ui_elements_with_buffer_font_size: bool,
468 /// When set to `true`, the editor will render a background for the active line.
469 show_active_line_background: bool,
470 /// When set to `true`, the editor's height will be determined by its content.
471 sized_by_content: bool,
472 },
473 Minimap {
474 parent: WeakEntity<Editor>,
475 },
476}
477
478impl EditorMode {
479 pub fn full() -> Self {
480 Self::Full {
481 scale_ui_elements_with_buffer_font_size: true,
482 show_active_line_background: true,
483 sized_by_content: false,
484 }
485 }
486
487 #[inline]
488 pub fn is_full(&self) -> bool {
489 matches!(self, Self::Full { .. })
490 }
491
492 #[inline]
493 pub fn is_single_line(&self) -> bool {
494 matches!(self, Self::SingleLine { .. })
495 }
496
497 #[inline]
498 fn is_minimap(&self) -> bool {
499 matches!(self, Self::Minimap { .. })
500 }
501}
502
503#[derive(Copy, Clone, Debug)]
504pub enum SoftWrap {
505 /// Prefer not to wrap at all.
506 ///
507 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
508 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
509 GitDiff,
510 /// Prefer a single line generally, unless an overly long line is encountered.
511 None,
512 /// Soft wrap lines that exceed the editor width.
513 EditorWidth,
514 /// Soft wrap lines at the preferred line length.
515 Column(u32),
516 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
517 Bounded(u32),
518}
519
520#[derive(Clone)]
521pub struct EditorStyle {
522 pub background: Hsla,
523 pub border: Hsla,
524 pub local_player: PlayerColor,
525 pub text: TextStyle,
526 pub scrollbar_width: Pixels,
527 pub syntax: Arc<SyntaxTheme>,
528 pub status: StatusColors,
529 pub inlay_hints_style: HighlightStyle,
530 pub edit_prediction_styles: EditPredictionStyles,
531 pub unnecessary_code_fade: f32,
532 pub show_underlines: bool,
533}
534
535impl Default for EditorStyle {
536 fn default() -> Self {
537 Self {
538 background: Hsla::default(),
539 border: Hsla::default(),
540 local_player: PlayerColor::default(),
541 text: TextStyle::default(),
542 scrollbar_width: Pixels::default(),
543 syntax: Default::default(),
544 // HACK: Status colors don't have a real default.
545 // We should look into removing the status colors from the editor
546 // style and retrieve them directly from the theme.
547 status: StatusColors::dark(),
548 inlay_hints_style: HighlightStyle::default(),
549 edit_prediction_styles: EditPredictionStyles {
550 insertion: HighlightStyle::default(),
551 whitespace: HighlightStyle::default(),
552 },
553 unnecessary_code_fade: Default::default(),
554 show_underlines: true,
555 }
556 }
557}
558
559pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
560 let show_background = language_settings::language_settings(None, None, cx)
561 .inlay_hints
562 .show_background;
563
564 let mut style = cx.theme().syntax().get("hint");
565
566 if style.color.is_none() {
567 style.color = Some(cx.theme().status().hint);
568 }
569
570 if !show_background {
571 style.background_color = None;
572 return style;
573 }
574
575 if style.background_color.is_none() {
576 style.background_color = Some(cx.theme().status().hint_background);
577 }
578
579 style
580}
581
582pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
583 EditPredictionStyles {
584 insertion: HighlightStyle {
585 color: Some(cx.theme().status().predictive),
586 ..HighlightStyle::default()
587 },
588 whitespace: HighlightStyle {
589 background_color: Some(cx.theme().status().created_background),
590 ..HighlightStyle::default()
591 },
592 }
593}
594
595type CompletionId = usize;
596
597pub(crate) enum EditDisplayMode {
598 TabAccept,
599 DiffPopover,
600 Inline,
601}
602
603enum EditPrediction {
604 Edit {
605 edits: Vec<(Range<Anchor>, String)>,
606 edit_preview: Option<EditPreview>,
607 display_mode: EditDisplayMode,
608 snapshot: BufferSnapshot,
609 },
610 /// Move to a specific location in the active editor
611 MoveWithin {
612 target: Anchor,
613 snapshot: BufferSnapshot,
614 },
615 /// Move to a specific location in a different editor (not the active one)
616 MoveOutside {
617 target: language::Anchor,
618 snapshot: BufferSnapshot,
619 },
620}
621
622struct EditPredictionState {
623 inlay_ids: Vec<InlayId>,
624 completion: EditPrediction,
625 completion_id: Option<SharedString>,
626 invalidation_range: Option<Range<Anchor>>,
627}
628
629enum EditPredictionSettings {
630 Disabled,
631 Enabled {
632 show_in_menu: bool,
633 preview_requires_modifier: bool,
634 },
635}
636
637enum EditPredictionHighlight {}
638
639#[derive(Debug, Clone)]
640struct InlineDiagnostic {
641 message: SharedString,
642 group_id: usize,
643 is_primary: bool,
644 start: Point,
645 severity: lsp::DiagnosticSeverity,
646}
647
648pub enum MenuEditPredictionsPolicy {
649 Never,
650 ByProvider,
651}
652
653pub enum EditPredictionPreview {
654 /// Modifier is not pressed
655 Inactive { released_too_fast: bool },
656 /// Modifier pressed
657 Active {
658 since: Instant,
659 previous_scroll_position: Option<ScrollAnchor>,
660 },
661}
662
663impl EditPredictionPreview {
664 pub fn released_too_fast(&self) -> bool {
665 match self {
666 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
667 EditPredictionPreview::Active { .. } => false,
668 }
669 }
670
671 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
672 if let EditPredictionPreview::Active {
673 previous_scroll_position,
674 ..
675 } = self
676 {
677 *previous_scroll_position = scroll_position;
678 }
679 }
680}
681
682pub struct ContextMenuOptions {
683 pub min_entries_visible: usize,
684 pub max_entries_visible: usize,
685 pub placement: Option<ContextMenuPlacement>,
686}
687
688#[derive(Debug, Clone, PartialEq, Eq)]
689pub enum ContextMenuPlacement {
690 Above,
691 Below,
692}
693
694#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
695struct EditorActionId(usize);
696
697impl EditorActionId {
698 pub fn post_inc(&mut self) -> Self {
699 let answer = self.0;
700
701 *self = Self(answer + 1);
702
703 Self(answer)
704 }
705}
706
707// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
708// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
709
710type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
711type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
712
713#[derive(Default)]
714struct ScrollbarMarkerState {
715 scrollbar_size: Size<Pixels>,
716 dirty: bool,
717 markers: Arc<[PaintQuad]>,
718 pending_refresh: Option<Task<Result<()>>>,
719}
720
721impl ScrollbarMarkerState {
722 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
723 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
724 }
725}
726
727#[derive(Clone, Copy, PartialEq, Eq)]
728pub enum MinimapVisibility {
729 Disabled,
730 Enabled {
731 /// The configuration currently present in the users settings.
732 setting_configuration: bool,
733 /// Whether to override the currently set visibility from the users setting.
734 toggle_override: bool,
735 },
736}
737
738impl MinimapVisibility {
739 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
740 if mode.is_full() {
741 Self::Enabled {
742 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
743 toggle_override: false,
744 }
745 } else {
746 Self::Disabled
747 }
748 }
749
750 fn hidden(&self) -> Self {
751 match *self {
752 Self::Enabled {
753 setting_configuration,
754 ..
755 } => Self::Enabled {
756 setting_configuration,
757 toggle_override: setting_configuration,
758 },
759 Self::Disabled => Self::Disabled,
760 }
761 }
762
763 fn disabled(&self) -> bool {
764 matches!(*self, Self::Disabled)
765 }
766
767 fn settings_visibility(&self) -> bool {
768 match *self {
769 Self::Enabled {
770 setting_configuration,
771 ..
772 } => setting_configuration,
773 _ => false,
774 }
775 }
776
777 fn visible(&self) -> bool {
778 match *self {
779 Self::Enabled {
780 setting_configuration,
781 toggle_override,
782 } => setting_configuration ^ toggle_override,
783 _ => false,
784 }
785 }
786
787 fn toggle_visibility(&self) -> Self {
788 match *self {
789 Self::Enabled {
790 toggle_override,
791 setting_configuration,
792 } => Self::Enabled {
793 setting_configuration,
794 toggle_override: !toggle_override,
795 },
796 Self::Disabled => Self::Disabled,
797 }
798 }
799}
800
801#[derive(Clone, Debug)]
802struct RunnableTasks {
803 templates: Vec<(TaskSourceKind, TaskTemplate)>,
804 offset: multi_buffer::Anchor,
805 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
806 column: u32,
807 // Values of all named captures, including those starting with '_'
808 extra_variables: HashMap<String, String>,
809 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
810 context_range: Range<BufferOffset>,
811}
812
813impl RunnableTasks {
814 fn resolve<'a>(
815 &'a self,
816 cx: &'a task::TaskContext,
817 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
818 self.templates.iter().filter_map(|(kind, template)| {
819 template
820 .resolve_task(&kind.to_id_base(), cx)
821 .map(|task| (kind.clone(), task))
822 })
823 }
824}
825
826#[derive(Clone)]
827pub struct ResolvedTasks {
828 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
829 position: Anchor,
830}
831
832#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
833struct BufferOffset(usize);
834
835/// Addons allow storing per-editor state in other crates (e.g. Vim)
836pub trait Addon: 'static {
837 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
838
839 fn render_buffer_header_controls(
840 &self,
841 _: &ExcerptInfo,
842 _: &Window,
843 _: &App,
844 ) -> Option<AnyElement> {
845 None
846 }
847
848 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
849 None
850 }
851
852 fn to_any(&self) -> &dyn std::any::Any;
853
854 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
855 None
856 }
857}
858
859struct ChangeLocation {
860 current: Option<Vec<Anchor>>,
861 original: Vec<Anchor>,
862}
863impl ChangeLocation {
864 fn locations(&self) -> &[Anchor] {
865 self.current.as_ref().unwrap_or(&self.original)
866 }
867}
868
869/// A set of caret positions, registered when the editor was edited.
870pub struct ChangeList {
871 changes: Vec<ChangeLocation>,
872 /// Currently "selected" change.
873 position: Option<usize>,
874}
875
876impl ChangeList {
877 pub fn new() -> Self {
878 Self {
879 changes: Vec::new(),
880 position: None,
881 }
882 }
883
884 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
885 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
886 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
887 if self.changes.is_empty() {
888 return None;
889 }
890
891 let prev = self.position.unwrap_or(self.changes.len());
892 let next = if direction == Direction::Prev {
893 prev.saturating_sub(count)
894 } else {
895 (prev + count).min(self.changes.len() - 1)
896 };
897 self.position = Some(next);
898 self.changes.get(next).map(|change| change.locations())
899 }
900
901 /// Adds a new change to the list, resetting the change list position.
902 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
903 self.position.take();
904 if let Some(last) = self.changes.last_mut()
905 && group
906 {
907 last.current = Some(new_positions)
908 } else {
909 self.changes.push(ChangeLocation {
910 original: new_positions,
911 current: None,
912 });
913 }
914 }
915
916 pub fn last(&self) -> Option<&[Anchor]> {
917 self.changes.last().map(|change| change.locations())
918 }
919
920 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
921 self.changes.last().map(|change| change.original.as_slice())
922 }
923
924 pub fn invert_last_group(&mut self) {
925 if let Some(last) = self.changes.last_mut()
926 && let Some(current) = last.current.as_mut()
927 {
928 mem::swap(&mut last.original, current);
929 }
930 }
931}
932
933#[derive(Clone)]
934struct InlineBlamePopoverState {
935 scroll_handle: ScrollHandle,
936 commit_message: Option<ParsedCommitMessage>,
937 markdown: Entity<Markdown>,
938}
939
940struct InlineBlamePopover {
941 position: gpui::Point<Pixels>,
942 hide_task: Option<Task<()>>,
943 popover_bounds: Option<Bounds<Pixels>>,
944 popover_state: InlineBlamePopoverState,
945 keyboard_grace: bool,
946}
947
948enum SelectionDragState {
949 /// State when no drag related activity is detected.
950 None,
951 /// State when the mouse is down on a selection that is about to be dragged.
952 ReadyToDrag {
953 selection: Selection<Anchor>,
954 click_position: gpui::Point<Pixels>,
955 mouse_down_time: Instant,
956 },
957 /// State when the mouse is dragging the selection in the editor.
958 Dragging {
959 selection: Selection<Anchor>,
960 drop_cursor: Selection<Anchor>,
961 hide_drop_cursor: bool,
962 },
963}
964
965enum ColumnarSelectionState {
966 FromMouse {
967 selection_tail: Anchor,
968 display_point: Option<DisplayPoint>,
969 },
970 FromSelection {
971 selection_tail: Anchor,
972 },
973}
974
975/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
976/// a breakpoint on them.
977#[derive(Clone, Copy, Debug, PartialEq, Eq)]
978struct PhantomBreakpointIndicator {
979 display_row: DisplayRow,
980 /// There's a small debounce between hovering over the line and showing the indicator.
981 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
982 is_active: bool,
983 collides_with_existing_breakpoint: bool,
984}
985
986/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
987///
988/// See the [module level documentation](self) for more information.
989pub struct Editor {
990 focus_handle: FocusHandle,
991 last_focused_descendant: Option<WeakFocusHandle>,
992 /// The text buffer being edited
993 buffer: Entity<MultiBuffer>,
994 /// Map of how text in the buffer should be displayed.
995 /// Handles soft wraps, folds, fake inlay text insertions, etc.
996 pub display_map: Entity<DisplayMap>,
997 placeholder_display_map: Option<Entity<DisplayMap>>,
998 pub selections: SelectionsCollection,
999 pub scroll_manager: ScrollManager,
1000 /// When inline assist editors are linked, they all render cursors because
1001 /// typing enters text into each of them, even the ones that aren't focused.
1002 pub(crate) show_cursor_when_unfocused: bool,
1003 columnar_selection_state: Option<ColumnarSelectionState>,
1004 add_selections_state: Option<AddSelectionsState>,
1005 select_next_state: Option<SelectNextState>,
1006 select_prev_state: Option<SelectNextState>,
1007 selection_history: SelectionHistory,
1008 defer_selection_effects: bool,
1009 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1010 autoclose_regions: Vec<AutocloseRegion>,
1011 snippet_stack: InvalidationStack<SnippetState>,
1012 select_syntax_node_history: SelectSyntaxNodeHistory,
1013 ime_transaction: Option<TransactionId>,
1014 pub diagnostics_max_severity: DiagnosticSeverity,
1015 active_diagnostics: ActiveDiagnostic,
1016 show_inline_diagnostics: bool,
1017 inline_diagnostics_update: Task<()>,
1018 inline_diagnostics_enabled: bool,
1019 diagnostics_enabled: bool,
1020 word_completions_enabled: bool,
1021 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1022 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1023 hard_wrap: Option<usize>,
1024 project: Option<Entity<Project>>,
1025 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1026 completion_provider: Option<Rc<dyn CompletionProvider>>,
1027 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1028 blink_manager: Entity<BlinkManager>,
1029 show_cursor_names: bool,
1030 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1031 pub show_local_selections: bool,
1032 mode: EditorMode,
1033 show_breadcrumbs: bool,
1034 show_gutter: bool,
1035 show_scrollbars: ScrollbarAxes,
1036 minimap_visibility: MinimapVisibility,
1037 offset_content: bool,
1038 disable_expand_excerpt_buttons: bool,
1039 show_line_numbers: Option<bool>,
1040 use_relative_line_numbers: Option<bool>,
1041 show_git_diff_gutter: Option<bool>,
1042 show_code_actions: Option<bool>,
1043 show_runnables: Option<bool>,
1044 show_breakpoints: Option<bool>,
1045 show_wrap_guides: Option<bool>,
1046 show_indent_guides: Option<bool>,
1047 highlight_order: usize,
1048 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1049 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1050 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1051 scrollbar_marker_state: ScrollbarMarkerState,
1052 active_indent_guides_state: ActiveIndentGuidesState,
1053 nav_history: Option<ItemNavHistory>,
1054 context_menu: RefCell<Option<CodeContextMenu>>,
1055 context_menu_options: Option<ContextMenuOptions>,
1056 mouse_context_menu: Option<MouseContextMenu>,
1057 completion_tasks: Vec<(CompletionId, Task<()>)>,
1058 inline_blame_popover: Option<InlineBlamePopover>,
1059 inline_blame_popover_show_task: Option<Task<()>>,
1060 signature_help_state: SignatureHelpState,
1061 auto_signature_help: Option<bool>,
1062 find_all_references_task_sources: Vec<Anchor>,
1063 next_completion_id: CompletionId,
1064 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1065 code_actions_task: Option<Task<Result<()>>>,
1066 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1067 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1068 document_highlights_task: Option<Task<()>>,
1069 linked_editing_range_task: Option<Task<Option<()>>>,
1070 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1071 pending_rename: Option<RenameState>,
1072 searchable: bool,
1073 cursor_shape: CursorShape,
1074 current_line_highlight: Option<CurrentLineHighlight>,
1075 collapse_matches: bool,
1076 autoindent_mode: Option<AutoindentMode>,
1077 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1078 input_enabled: bool,
1079 use_modal_editing: bool,
1080 read_only: bool,
1081 leader_id: Option<CollaboratorId>,
1082 remote_id: Option<ViewId>,
1083 pub hover_state: HoverState,
1084 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1085 gutter_hovered: bool,
1086 hovered_link_state: Option<HoveredLinkState>,
1087 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1088 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1089 active_edit_prediction: Option<EditPredictionState>,
1090 /// Used to prevent flickering as the user types while the menu is open
1091 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1092 edit_prediction_settings: EditPredictionSettings,
1093 edit_predictions_hidden_for_vim_mode: bool,
1094 show_edit_predictions_override: Option<bool>,
1095 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1096 edit_prediction_preview: EditPredictionPreview,
1097 edit_prediction_indent_conflict: bool,
1098 edit_prediction_requires_modifier_in_indent_conflict: bool,
1099 next_inlay_id: usize,
1100 next_color_inlay_id: usize,
1101 _subscriptions: Vec<Subscription>,
1102 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1103 gutter_dimensions: GutterDimensions,
1104 style: Option<EditorStyle>,
1105 text_style_refinement: Option<TextStyleRefinement>,
1106 next_editor_action_id: EditorActionId,
1107 editor_actions: Rc<
1108 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1109 >,
1110 use_autoclose: bool,
1111 use_auto_surround: bool,
1112 auto_replace_emoji_shortcode: bool,
1113 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1114 show_git_blame_gutter: bool,
1115 show_git_blame_inline: bool,
1116 show_git_blame_inline_delay_task: Option<Task<()>>,
1117 git_blame_inline_enabled: bool,
1118 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1119 serialize_dirty_buffers: bool,
1120 show_selection_menu: Option<bool>,
1121 blame: Option<Entity<GitBlame>>,
1122 blame_subscription: Option<Subscription>,
1123 custom_context_menu: Option<
1124 Box<
1125 dyn 'static
1126 + Fn(
1127 &mut Self,
1128 DisplayPoint,
1129 &mut Window,
1130 &mut Context<Self>,
1131 ) -> Option<Entity<ui::ContextMenu>>,
1132 >,
1133 >,
1134 last_bounds: Option<Bounds<Pixels>>,
1135 last_position_map: Option<Rc<PositionMap>>,
1136 expect_bounds_change: Option<Bounds<Pixels>>,
1137 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1138 tasks_update_task: Option<Task<()>>,
1139 breakpoint_store: Option<Entity<BreakpointStore>>,
1140 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1141 hovered_diff_hunk_row: Option<DisplayRow>,
1142 pull_diagnostics_task: Task<()>,
1143 in_project_search: bool,
1144 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1145 breadcrumb_header: Option<String>,
1146 focused_block: Option<FocusedBlock>,
1147 next_scroll_position: NextScrollCursorCenterTopBottom,
1148 addons: HashMap<TypeId, Box<dyn Addon>>,
1149 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1150 load_diff_task: Option<Shared<Task<()>>>,
1151 /// Whether we are temporarily displaying a diff other than git's
1152 temporary_diff_override: bool,
1153 selection_mark_mode: bool,
1154 toggle_fold_multiple_buffers: Task<()>,
1155 _scroll_cursor_center_top_bottom_task: Task<()>,
1156 serialize_selections: Task<()>,
1157 serialize_folds: Task<()>,
1158 mouse_cursor_hidden: bool,
1159 minimap: Option<Entity<Self>>,
1160 hide_mouse_mode: HideMouseMode,
1161 pub change_list: ChangeList,
1162 inline_value_cache: InlineValueCache,
1163 selection_drag_state: SelectionDragState,
1164 colors: Option<LspColorData>,
1165 post_scroll_update: Task<()>,
1166 refresh_colors_task: Task<()>,
1167 inlay_hints: Option<LspInlayHintData>,
1168 folding_newlines: Task<()>,
1169 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1170}
1171
1172fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1173 if debounce_ms > 0 {
1174 Some(Duration::from_millis(debounce_ms))
1175 } else {
1176 None
1177 }
1178}
1179
1180#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1181enum NextScrollCursorCenterTopBottom {
1182 #[default]
1183 Center,
1184 Top,
1185 Bottom,
1186}
1187
1188impl NextScrollCursorCenterTopBottom {
1189 fn next(&self) -> Self {
1190 match self {
1191 Self::Center => Self::Top,
1192 Self::Top => Self::Bottom,
1193 Self::Bottom => Self::Center,
1194 }
1195 }
1196}
1197
1198#[derive(Clone)]
1199pub struct EditorSnapshot {
1200 pub mode: EditorMode,
1201 show_gutter: bool,
1202 show_line_numbers: Option<bool>,
1203 show_git_diff_gutter: Option<bool>,
1204 show_code_actions: Option<bool>,
1205 show_runnables: Option<bool>,
1206 show_breakpoints: Option<bool>,
1207 git_blame_gutter_max_author_length: Option<usize>,
1208 pub display_snapshot: DisplaySnapshot,
1209 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1210 is_focused: bool,
1211 scroll_anchor: ScrollAnchor,
1212 ongoing_scroll: OngoingScroll,
1213 current_line_highlight: CurrentLineHighlight,
1214 gutter_hovered: bool,
1215}
1216
1217#[derive(Default, Debug, Clone, Copy)]
1218pub struct GutterDimensions {
1219 pub left_padding: Pixels,
1220 pub right_padding: Pixels,
1221 pub width: Pixels,
1222 pub margin: Pixels,
1223 pub git_blame_entries_width: Option<Pixels>,
1224}
1225
1226impl GutterDimensions {
1227 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1228 Self {
1229 margin: Self::default_gutter_margin(font_id, font_size, cx),
1230 ..Default::default()
1231 }
1232 }
1233
1234 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1235 -cx.text_system().descent(font_id, font_size)
1236 }
1237 /// The full width of the space taken up by the gutter.
1238 pub fn full_width(&self) -> Pixels {
1239 self.margin + self.width
1240 }
1241
1242 /// The width of the space reserved for the fold indicators,
1243 /// use alongside 'justify_end' and `gutter_width` to
1244 /// right align content with the line numbers
1245 pub fn fold_area_width(&self) -> Pixels {
1246 self.margin + self.right_padding
1247 }
1248}
1249
1250struct CharacterDimensions {
1251 em_width: Pixels,
1252 em_advance: Pixels,
1253 line_height: Pixels,
1254}
1255
1256#[derive(Debug)]
1257pub struct RemoteSelection {
1258 pub replica_id: ReplicaId,
1259 pub selection: Selection<Anchor>,
1260 pub cursor_shape: CursorShape,
1261 pub collaborator_id: CollaboratorId,
1262 pub line_mode: bool,
1263 pub user_name: Option<SharedString>,
1264 pub color: PlayerColor,
1265}
1266
1267#[derive(Clone, Debug)]
1268struct SelectionHistoryEntry {
1269 selections: Arc<[Selection<Anchor>]>,
1270 select_next_state: Option<SelectNextState>,
1271 select_prev_state: Option<SelectNextState>,
1272 add_selections_state: Option<AddSelectionsState>,
1273}
1274
1275#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1276enum SelectionHistoryMode {
1277 Normal,
1278 Undoing,
1279 Redoing,
1280 Skipping,
1281}
1282
1283#[derive(Clone, PartialEq, Eq, Hash)]
1284struct HoveredCursor {
1285 replica_id: ReplicaId,
1286 selection_id: usize,
1287}
1288
1289impl Default for SelectionHistoryMode {
1290 fn default() -> Self {
1291 Self::Normal
1292 }
1293}
1294
1295#[derive(Debug)]
1296/// SelectionEffects controls the side-effects of updating the selection.
1297///
1298/// The default behaviour does "what you mostly want":
1299/// - it pushes to the nav history if the cursor moved by >10 lines
1300/// - it re-triggers completion requests
1301/// - it scrolls to fit
1302///
1303/// You might want to modify these behaviours. For example when doing a "jump"
1304/// like go to definition, we always want to add to nav history; but when scrolling
1305/// in vim mode we never do.
1306///
1307/// Similarly, you might want to disable scrolling if you don't want the viewport to
1308/// move.
1309#[derive(Clone)]
1310pub struct SelectionEffects {
1311 nav_history: Option<bool>,
1312 completions: bool,
1313 scroll: Option<Autoscroll>,
1314}
1315
1316impl Default for SelectionEffects {
1317 fn default() -> Self {
1318 Self {
1319 nav_history: None,
1320 completions: true,
1321 scroll: Some(Autoscroll::fit()),
1322 }
1323 }
1324}
1325impl SelectionEffects {
1326 pub fn scroll(scroll: Autoscroll) -> Self {
1327 Self {
1328 scroll: Some(scroll),
1329 ..Default::default()
1330 }
1331 }
1332
1333 pub fn no_scroll() -> Self {
1334 Self {
1335 scroll: None,
1336 ..Default::default()
1337 }
1338 }
1339
1340 pub fn completions(self, completions: bool) -> Self {
1341 Self {
1342 completions,
1343 ..self
1344 }
1345 }
1346
1347 pub fn nav_history(self, nav_history: bool) -> Self {
1348 Self {
1349 nav_history: Some(nav_history),
1350 ..self
1351 }
1352 }
1353}
1354
1355struct DeferredSelectionEffectsState {
1356 changed: bool,
1357 effects: SelectionEffects,
1358 old_cursor_position: Anchor,
1359 history_entry: SelectionHistoryEntry,
1360}
1361
1362#[derive(Default)]
1363struct SelectionHistory {
1364 #[allow(clippy::type_complexity)]
1365 selections_by_transaction:
1366 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1367 mode: SelectionHistoryMode,
1368 undo_stack: VecDeque<SelectionHistoryEntry>,
1369 redo_stack: VecDeque<SelectionHistoryEntry>,
1370}
1371
1372impl SelectionHistory {
1373 #[track_caller]
1374 fn insert_transaction(
1375 &mut self,
1376 transaction_id: TransactionId,
1377 selections: Arc<[Selection<Anchor>]>,
1378 ) {
1379 if selections.is_empty() {
1380 log::error!(
1381 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1382 std::panic::Location::caller()
1383 );
1384 return;
1385 }
1386 self.selections_by_transaction
1387 .insert(transaction_id, (selections, None));
1388 }
1389
1390 #[allow(clippy::type_complexity)]
1391 fn transaction(
1392 &self,
1393 transaction_id: TransactionId,
1394 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1395 self.selections_by_transaction.get(&transaction_id)
1396 }
1397
1398 #[allow(clippy::type_complexity)]
1399 fn transaction_mut(
1400 &mut self,
1401 transaction_id: TransactionId,
1402 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1403 self.selections_by_transaction.get_mut(&transaction_id)
1404 }
1405
1406 fn push(&mut self, entry: SelectionHistoryEntry) {
1407 if !entry.selections.is_empty() {
1408 match self.mode {
1409 SelectionHistoryMode::Normal => {
1410 self.push_undo(entry);
1411 self.redo_stack.clear();
1412 }
1413 SelectionHistoryMode::Undoing => self.push_redo(entry),
1414 SelectionHistoryMode::Redoing => self.push_undo(entry),
1415 SelectionHistoryMode::Skipping => {}
1416 }
1417 }
1418 }
1419
1420 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1421 if self
1422 .undo_stack
1423 .back()
1424 .is_none_or(|e| e.selections != entry.selections)
1425 {
1426 self.undo_stack.push_back(entry);
1427 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1428 self.undo_stack.pop_front();
1429 }
1430 }
1431 }
1432
1433 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1434 if self
1435 .redo_stack
1436 .back()
1437 .is_none_or(|e| e.selections != entry.selections)
1438 {
1439 self.redo_stack.push_back(entry);
1440 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1441 self.redo_stack.pop_front();
1442 }
1443 }
1444 }
1445}
1446
1447#[derive(Clone, Copy)]
1448pub struct RowHighlightOptions {
1449 pub autoscroll: bool,
1450 pub include_gutter: bool,
1451}
1452
1453impl Default for RowHighlightOptions {
1454 fn default() -> Self {
1455 Self {
1456 autoscroll: Default::default(),
1457 include_gutter: true,
1458 }
1459 }
1460}
1461
1462struct RowHighlight {
1463 index: usize,
1464 range: Range<Anchor>,
1465 color: Hsla,
1466 options: RowHighlightOptions,
1467 type_id: TypeId,
1468}
1469
1470#[derive(Clone, Debug)]
1471struct AddSelectionsState {
1472 groups: Vec<AddSelectionsGroup>,
1473}
1474
1475#[derive(Clone, Debug)]
1476struct AddSelectionsGroup {
1477 above: bool,
1478 stack: Vec<usize>,
1479}
1480
1481#[derive(Clone)]
1482struct SelectNextState {
1483 query: AhoCorasick,
1484 wordwise: bool,
1485 done: bool,
1486}
1487
1488impl std::fmt::Debug for SelectNextState {
1489 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1490 f.debug_struct(std::any::type_name::<Self>())
1491 .field("wordwise", &self.wordwise)
1492 .field("done", &self.done)
1493 .finish()
1494 }
1495}
1496
1497#[derive(Debug)]
1498struct AutocloseRegion {
1499 selection_id: usize,
1500 range: Range<Anchor>,
1501 pair: BracketPair,
1502}
1503
1504#[derive(Debug)]
1505struct SnippetState {
1506 ranges: Vec<Vec<Range<Anchor>>>,
1507 active_index: usize,
1508 choices: Vec<Option<Vec<String>>>,
1509}
1510
1511#[doc(hidden)]
1512pub struct RenameState {
1513 pub range: Range<Anchor>,
1514 pub old_name: Arc<str>,
1515 pub editor: Entity<Editor>,
1516 block_id: CustomBlockId,
1517}
1518
1519struct InvalidationStack<T>(Vec<T>);
1520
1521struct RegisteredEditPredictionProvider {
1522 provider: Arc<dyn EditPredictionProviderHandle>,
1523 _subscription: Subscription,
1524}
1525
1526#[derive(Debug, PartialEq, Eq)]
1527pub struct ActiveDiagnosticGroup {
1528 pub active_range: Range<Anchor>,
1529 pub active_message: String,
1530 pub group_id: usize,
1531 pub blocks: HashSet<CustomBlockId>,
1532}
1533
1534#[derive(Debug, PartialEq, Eq)]
1535
1536pub(crate) enum ActiveDiagnostic {
1537 None,
1538 All,
1539 Group(ActiveDiagnosticGroup),
1540}
1541
1542#[derive(Serialize, Deserialize, Clone, Debug)]
1543pub struct ClipboardSelection {
1544 /// The number of bytes in this selection.
1545 pub len: usize,
1546 /// Whether this was a full-line selection.
1547 pub is_entire_line: bool,
1548 /// The indentation of the first line when this content was originally copied.
1549 pub first_line_indent: u32,
1550}
1551
1552// selections, scroll behavior, was newest selection reversed
1553type SelectSyntaxNodeHistoryState = (
1554 Box<[Selection<usize>]>,
1555 SelectSyntaxNodeScrollBehavior,
1556 bool,
1557);
1558
1559#[derive(Default)]
1560struct SelectSyntaxNodeHistory {
1561 stack: Vec<SelectSyntaxNodeHistoryState>,
1562 // disable temporarily to allow changing selections without losing the stack
1563 pub disable_clearing: bool,
1564}
1565
1566impl SelectSyntaxNodeHistory {
1567 pub fn try_clear(&mut self) {
1568 if !self.disable_clearing {
1569 self.stack.clear();
1570 }
1571 }
1572
1573 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1574 self.stack.push(selection);
1575 }
1576
1577 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1578 self.stack.pop()
1579 }
1580}
1581
1582enum SelectSyntaxNodeScrollBehavior {
1583 CursorTop,
1584 FitSelection,
1585 CursorBottom,
1586}
1587
1588#[derive(Debug)]
1589pub(crate) struct NavigationData {
1590 cursor_anchor: Anchor,
1591 cursor_position: Point,
1592 scroll_anchor: ScrollAnchor,
1593 scroll_top_row: u32,
1594}
1595
1596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1597pub enum GotoDefinitionKind {
1598 Symbol,
1599 Declaration,
1600 Type,
1601 Implementation,
1602}
1603
1604pub enum FormatTarget {
1605 Buffers(HashSet<Entity<Buffer>>),
1606 Ranges(Vec<Range<MultiBufferPoint>>),
1607}
1608
1609pub(crate) struct FocusedBlock {
1610 id: BlockId,
1611 focus_handle: WeakFocusHandle,
1612}
1613
1614#[derive(Clone)]
1615enum JumpData {
1616 MultiBufferRow {
1617 row: MultiBufferRow,
1618 line_offset_from_top: u32,
1619 },
1620 MultiBufferPoint {
1621 excerpt_id: ExcerptId,
1622 position: Point,
1623 anchor: text::Anchor,
1624 line_offset_from_top: u32,
1625 },
1626}
1627
1628pub enum MultibufferSelectionMode {
1629 First,
1630 All,
1631}
1632
1633#[derive(Clone, Copy, Debug, Default)]
1634pub struct RewrapOptions {
1635 pub override_language_settings: bool,
1636 pub preserve_existing_whitespace: bool,
1637}
1638
1639impl Editor {
1640 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1641 let buffer = cx.new(|cx| Buffer::local("", cx));
1642 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1643 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1644 }
1645
1646 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1647 let buffer = cx.new(|cx| Buffer::local("", cx));
1648 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1649 Self::new(EditorMode::full(), buffer, None, window, cx)
1650 }
1651
1652 pub fn auto_height(
1653 min_lines: usize,
1654 max_lines: usize,
1655 window: &mut Window,
1656 cx: &mut Context<Self>,
1657 ) -> Self {
1658 let buffer = cx.new(|cx| Buffer::local("", cx));
1659 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1660 Self::new(
1661 EditorMode::AutoHeight {
1662 min_lines,
1663 max_lines: Some(max_lines),
1664 },
1665 buffer,
1666 None,
1667 window,
1668 cx,
1669 )
1670 }
1671
1672 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1673 /// The editor grows as tall as needed to fit its content.
1674 pub fn auto_height_unbounded(
1675 min_lines: usize,
1676 window: &mut Window,
1677 cx: &mut Context<Self>,
1678 ) -> Self {
1679 let buffer = cx.new(|cx| Buffer::local("", cx));
1680 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1681 Self::new(
1682 EditorMode::AutoHeight {
1683 min_lines,
1684 max_lines: None,
1685 },
1686 buffer,
1687 None,
1688 window,
1689 cx,
1690 )
1691 }
1692
1693 pub fn for_buffer(
1694 buffer: Entity<Buffer>,
1695 project: Option<Entity<Project>>,
1696 window: &mut Window,
1697 cx: &mut Context<Self>,
1698 ) -> Self {
1699 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1700 Self::new(EditorMode::full(), buffer, project, window, cx)
1701 }
1702
1703 pub fn for_multibuffer(
1704 buffer: Entity<MultiBuffer>,
1705 project: Option<Entity<Project>>,
1706 window: &mut Window,
1707 cx: &mut Context<Self>,
1708 ) -> Self {
1709 Self::new(EditorMode::full(), buffer, project, window, cx)
1710 }
1711
1712 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1713 let mut clone = Self::new(
1714 self.mode.clone(),
1715 self.buffer.clone(),
1716 self.project.clone(),
1717 window,
1718 cx,
1719 );
1720 self.display_map.update(cx, |display_map, cx| {
1721 let snapshot = display_map.snapshot(cx);
1722 clone.display_map.update(cx, |display_map, cx| {
1723 display_map.set_state(&snapshot, cx);
1724 });
1725 });
1726 clone.folds_did_change(cx);
1727 clone.selections.clone_state(&self.selections);
1728 clone.scroll_manager.clone_state(&self.scroll_manager);
1729 clone.searchable = self.searchable;
1730 clone.read_only = self.read_only;
1731 clone
1732 }
1733
1734 pub fn new(
1735 mode: EditorMode,
1736 buffer: Entity<MultiBuffer>,
1737 project: Option<Entity<Project>>,
1738 window: &mut Window,
1739 cx: &mut Context<Self>,
1740 ) -> Self {
1741 Editor::new_internal(mode, buffer, project, None, window, cx)
1742 }
1743
1744 fn new_internal(
1745 mode: EditorMode,
1746 multi_buffer: Entity<MultiBuffer>,
1747 project: Option<Entity<Project>>,
1748 display_map: Option<Entity<DisplayMap>>,
1749 window: &mut Window,
1750 cx: &mut Context<Self>,
1751 ) -> Self {
1752 debug_assert!(
1753 display_map.is_none() || mode.is_minimap(),
1754 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1755 );
1756
1757 let full_mode = mode.is_full();
1758 let is_minimap = mode.is_minimap();
1759 let diagnostics_max_severity = if full_mode {
1760 EditorSettings::get_global(cx)
1761 .diagnostics_max_severity
1762 .unwrap_or(DiagnosticSeverity::Hint)
1763 } else {
1764 DiagnosticSeverity::Off
1765 };
1766 let style = window.text_style();
1767 let font_size = style.font_size.to_pixels(window.rem_size());
1768 let editor = cx.entity().downgrade();
1769 let fold_placeholder = FoldPlaceholder {
1770 constrain_width: false,
1771 render: Arc::new(move |fold_id, fold_range, cx| {
1772 let editor = editor.clone();
1773 div()
1774 .id(fold_id)
1775 .bg(cx.theme().colors().ghost_element_background)
1776 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1777 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1778 .rounded_xs()
1779 .size_full()
1780 .cursor_pointer()
1781 .child("⋯")
1782 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1783 .on_click(move |_, _window, cx| {
1784 editor
1785 .update(cx, |editor, cx| {
1786 editor.unfold_ranges(
1787 &[fold_range.start..fold_range.end],
1788 true,
1789 false,
1790 cx,
1791 );
1792 cx.stop_propagation();
1793 })
1794 .ok();
1795 })
1796 .into_any()
1797 }),
1798 merge_adjacent: true,
1799 ..FoldPlaceholder::default()
1800 };
1801 let display_map = display_map.unwrap_or_else(|| {
1802 cx.new(|cx| {
1803 DisplayMap::new(
1804 multi_buffer.clone(),
1805 style.font(),
1806 font_size,
1807 None,
1808 FILE_HEADER_HEIGHT,
1809 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1810 fold_placeholder,
1811 diagnostics_max_severity,
1812 cx,
1813 )
1814 })
1815 });
1816
1817 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1818
1819 let blink_manager = cx.new(|cx| {
1820 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1821 if is_minimap {
1822 blink_manager.disable(cx);
1823 }
1824 blink_manager
1825 });
1826
1827 let soft_wrap_mode_override =
1828 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1829
1830 let mut project_subscriptions = Vec::new();
1831 if full_mode && let Some(project) = project.as_ref() {
1832 project_subscriptions.push(cx.subscribe_in(
1833 project,
1834 window,
1835 |editor, _, event, window, cx| match event {
1836 project::Event::RefreshCodeLens => {
1837 // we always query lens with actions, without storing them, always refreshing them
1838 }
1839 project::Event::RefreshInlayHints(server_id) => {
1840 editor.refresh_inlay_hints(
1841 InlayHintRefreshReason::RefreshRequested(*server_id),
1842 cx,
1843 );
1844 }
1845 project::Event::LanguageServerRemoved(..) => {
1846 if editor.tasks_update_task.is_none() {
1847 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1848 }
1849 editor.registered_buffers.clear();
1850 editor.register_visible_buffers(cx);
1851 }
1852 project::Event::LanguageServerAdded(..) => {
1853 if editor.tasks_update_task.is_none() {
1854 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1855 }
1856 }
1857 project::Event::SnippetEdit(id, snippet_edits) => {
1858 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1859 let focus_handle = editor.focus_handle(cx);
1860 if focus_handle.is_focused(window) {
1861 let snapshot = buffer.read(cx).snapshot();
1862 for (range, snippet) in snippet_edits {
1863 let editor_range =
1864 language::range_from_lsp(*range).to_offset(&snapshot);
1865 editor
1866 .insert_snippet(
1867 &[editor_range],
1868 snippet.clone(),
1869 window,
1870 cx,
1871 )
1872 .ok();
1873 }
1874 }
1875 }
1876 }
1877 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1878 let buffer_id = *buffer_id;
1879 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1880 editor.register_buffer(buffer_id, cx);
1881 editor.update_lsp_data(Some(buffer_id), window, cx);
1882 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1883 refresh_linked_ranges(editor, window, cx);
1884 editor.refresh_code_actions(window, cx);
1885 editor.refresh_document_highlights(cx);
1886 }
1887 }
1888
1889 project::Event::EntryRenamed(transaction) => {
1890 let Some(workspace) = editor.workspace() else {
1891 return;
1892 };
1893 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1894 else {
1895 return;
1896 };
1897 if active_editor.entity_id() == cx.entity_id() {
1898 let edited_buffers_already_open = {
1899 let other_editors: Vec<Entity<Editor>> = workspace
1900 .read(cx)
1901 .panes()
1902 .iter()
1903 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1904 .filter(|editor| editor.entity_id() != cx.entity_id())
1905 .collect();
1906
1907 transaction.0.keys().all(|buffer| {
1908 other_editors.iter().any(|editor| {
1909 let multi_buffer = editor.read(cx).buffer();
1910 multi_buffer.read(cx).is_singleton()
1911 && multi_buffer.read(cx).as_singleton().map_or(
1912 false,
1913 |singleton| {
1914 singleton.entity_id() == buffer.entity_id()
1915 },
1916 )
1917 })
1918 })
1919 };
1920
1921 if !edited_buffers_already_open {
1922 let workspace = workspace.downgrade();
1923 let transaction = transaction.clone();
1924 cx.defer_in(window, move |_, window, cx| {
1925 cx.spawn_in(window, async move |editor, cx| {
1926 Self::open_project_transaction(
1927 &editor,
1928 workspace,
1929 transaction,
1930 "Rename".to_string(),
1931 cx,
1932 )
1933 .await
1934 .ok()
1935 })
1936 .detach();
1937 });
1938 }
1939 }
1940 }
1941
1942 _ => {}
1943 },
1944 ));
1945 if let Some(task_inventory) = project
1946 .read(cx)
1947 .task_store()
1948 .read(cx)
1949 .task_inventory()
1950 .cloned()
1951 {
1952 project_subscriptions.push(cx.observe_in(
1953 &task_inventory,
1954 window,
1955 |editor, _, window, cx| {
1956 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1957 },
1958 ));
1959 };
1960
1961 project_subscriptions.push(cx.subscribe_in(
1962 &project.read(cx).breakpoint_store(),
1963 window,
1964 |editor, _, event, window, cx| match event {
1965 BreakpointStoreEvent::ClearDebugLines => {
1966 editor.clear_row_highlights::<ActiveDebugLine>();
1967 editor.refresh_inline_values(cx);
1968 }
1969 BreakpointStoreEvent::SetDebugLine => {
1970 if editor.go_to_active_debug_line(window, cx) {
1971 cx.stop_propagation();
1972 }
1973
1974 editor.refresh_inline_values(cx);
1975 }
1976 _ => {}
1977 },
1978 ));
1979 let git_store = project.read(cx).git_store().clone();
1980 let project = project.clone();
1981 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1982 if let GitStoreEvent::RepositoryAdded = event {
1983 this.load_diff_task = Some(
1984 update_uncommitted_diff_for_buffer(
1985 cx.entity(),
1986 &project,
1987 this.buffer.read(cx).all_buffers(),
1988 this.buffer.clone(),
1989 cx,
1990 )
1991 .shared(),
1992 );
1993 }
1994 }));
1995 }
1996
1997 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1998
1999 let inlay_hint_settings =
2000 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2001 let focus_handle = cx.focus_handle();
2002 if !is_minimap {
2003 cx.on_focus(&focus_handle, window, Self::handle_focus)
2004 .detach();
2005 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2006 .detach();
2007 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2008 .detach();
2009 cx.on_blur(&focus_handle, window, Self::handle_blur)
2010 .detach();
2011 cx.observe_pending_input(window, Self::observe_pending_input)
2012 .detach();
2013 }
2014
2015 let show_indent_guides =
2016 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2017 Some(false)
2018 } else {
2019 None
2020 };
2021
2022 let breakpoint_store = match (&mode, project.as_ref()) {
2023 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2024 _ => None,
2025 };
2026
2027 let mut code_action_providers = Vec::new();
2028 let mut load_uncommitted_diff = None;
2029 if let Some(project) = project.clone() {
2030 load_uncommitted_diff = Some(
2031 update_uncommitted_diff_for_buffer(
2032 cx.entity(),
2033 &project,
2034 multi_buffer.read(cx).all_buffers(),
2035 multi_buffer.clone(),
2036 cx,
2037 )
2038 .shared(),
2039 );
2040 code_action_providers.push(Rc::new(project) as Rc<_>);
2041 }
2042
2043 let mut editor = Self {
2044 focus_handle,
2045 show_cursor_when_unfocused: false,
2046 last_focused_descendant: None,
2047 buffer: multi_buffer.clone(),
2048 display_map: display_map.clone(),
2049 placeholder_display_map: None,
2050 selections,
2051 scroll_manager: ScrollManager::new(cx),
2052 columnar_selection_state: None,
2053 add_selections_state: None,
2054 select_next_state: None,
2055 select_prev_state: None,
2056 selection_history: SelectionHistory::default(),
2057 defer_selection_effects: false,
2058 deferred_selection_effects_state: None,
2059 autoclose_regions: Vec::new(),
2060 snippet_stack: InvalidationStack::default(),
2061 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2062 ime_transaction: None,
2063 active_diagnostics: ActiveDiagnostic::None,
2064 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2065 inline_diagnostics_update: Task::ready(()),
2066 inline_diagnostics: Vec::new(),
2067 soft_wrap_mode_override,
2068 diagnostics_max_severity,
2069 hard_wrap: None,
2070 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2071 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2072 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2073 project,
2074 blink_manager: blink_manager.clone(),
2075 show_local_selections: true,
2076 show_scrollbars: ScrollbarAxes {
2077 horizontal: full_mode,
2078 vertical: full_mode,
2079 },
2080 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2081 offset_content: !matches!(mode, EditorMode::SingleLine),
2082 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2083 show_gutter: full_mode,
2084 show_line_numbers: (!full_mode).then_some(false),
2085 use_relative_line_numbers: None,
2086 disable_expand_excerpt_buttons: !full_mode,
2087 show_git_diff_gutter: None,
2088 show_code_actions: None,
2089 show_runnables: None,
2090 show_breakpoints: None,
2091 show_wrap_guides: None,
2092 show_indent_guides,
2093 highlight_order: 0,
2094 highlighted_rows: HashMap::default(),
2095 background_highlights: HashMap::default(),
2096 gutter_highlights: HashMap::default(),
2097 scrollbar_marker_state: ScrollbarMarkerState::default(),
2098 active_indent_guides_state: ActiveIndentGuidesState::default(),
2099 nav_history: None,
2100 context_menu: RefCell::new(None),
2101 context_menu_options: None,
2102 mouse_context_menu: None,
2103 completion_tasks: Vec::new(),
2104 inline_blame_popover: None,
2105 inline_blame_popover_show_task: None,
2106 signature_help_state: SignatureHelpState::default(),
2107 auto_signature_help: None,
2108 find_all_references_task_sources: Vec::new(),
2109 next_completion_id: 0,
2110 next_inlay_id: 0,
2111 code_action_providers,
2112 available_code_actions: None,
2113 code_actions_task: None,
2114 quick_selection_highlight_task: None,
2115 debounced_selection_highlight_task: None,
2116 document_highlights_task: None,
2117 linked_editing_range_task: None,
2118 pending_rename: None,
2119 searchable: !is_minimap,
2120 cursor_shape: EditorSettings::get_global(cx)
2121 .cursor_shape
2122 .unwrap_or_default(),
2123 current_line_highlight: None,
2124 autoindent_mode: Some(AutoindentMode::EachLine),
2125 collapse_matches: false,
2126 workspace: None,
2127 input_enabled: !is_minimap,
2128 use_modal_editing: full_mode,
2129 read_only: is_minimap,
2130 use_autoclose: true,
2131 use_auto_surround: true,
2132 auto_replace_emoji_shortcode: false,
2133 jsx_tag_auto_close_enabled_in_any_buffer: false,
2134 leader_id: None,
2135 remote_id: None,
2136 hover_state: HoverState::default(),
2137 pending_mouse_down: None,
2138 hovered_link_state: None,
2139 edit_prediction_provider: None,
2140 active_edit_prediction: None,
2141 stale_edit_prediction_in_menu: None,
2142 edit_prediction_preview: EditPredictionPreview::Inactive {
2143 released_too_fast: false,
2144 },
2145 inline_diagnostics_enabled: full_mode,
2146 diagnostics_enabled: full_mode,
2147 word_completions_enabled: full_mode,
2148 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2149 gutter_hovered: false,
2150 pixel_position_of_newest_cursor: None,
2151 last_bounds: None,
2152 last_position_map: None,
2153 expect_bounds_change: None,
2154 gutter_dimensions: GutterDimensions::default(),
2155 style: None,
2156 show_cursor_names: false,
2157 hovered_cursors: HashMap::default(),
2158 next_editor_action_id: EditorActionId::default(),
2159 editor_actions: Rc::default(),
2160 edit_predictions_hidden_for_vim_mode: false,
2161 show_edit_predictions_override: None,
2162 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2163 edit_prediction_settings: EditPredictionSettings::Disabled,
2164 edit_prediction_indent_conflict: false,
2165 edit_prediction_requires_modifier_in_indent_conflict: true,
2166 custom_context_menu: None,
2167 show_git_blame_gutter: false,
2168 show_git_blame_inline: false,
2169 show_selection_menu: None,
2170 show_git_blame_inline_delay_task: None,
2171 git_blame_inline_enabled: full_mode
2172 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2173 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2174 serialize_dirty_buffers: !is_minimap
2175 && ProjectSettings::get_global(cx)
2176 .session
2177 .restore_unsaved_buffers,
2178 blame: None,
2179 blame_subscription: None,
2180 tasks: BTreeMap::default(),
2181
2182 breakpoint_store,
2183 gutter_breakpoint_indicator: (None, None),
2184 hovered_diff_hunk_row: None,
2185 _subscriptions: (!is_minimap)
2186 .then(|| {
2187 vec![
2188 cx.observe(&multi_buffer, Self::on_buffer_changed),
2189 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2190 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2191 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2192 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2193 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2194 cx.observe_window_activation(window, |editor, window, cx| {
2195 let active = window.is_window_active();
2196 editor.blink_manager.update(cx, |blink_manager, cx| {
2197 if active {
2198 blink_manager.enable(cx);
2199 } else {
2200 blink_manager.disable(cx);
2201 }
2202 });
2203 if active {
2204 editor.show_mouse_cursor(cx);
2205 }
2206 }),
2207 ]
2208 })
2209 .unwrap_or_default(),
2210 tasks_update_task: None,
2211 pull_diagnostics_task: Task::ready(()),
2212 colors: None,
2213 refresh_colors_task: Task::ready(()),
2214 inlay_hints: None,
2215 next_color_inlay_id: 0,
2216 post_scroll_update: Task::ready(()),
2217 linked_edit_ranges: Default::default(),
2218 in_project_search: false,
2219 previous_search_ranges: None,
2220 breadcrumb_header: None,
2221 focused_block: None,
2222 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2223 addons: HashMap::default(),
2224 registered_buffers: HashMap::default(),
2225 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2226 selection_mark_mode: false,
2227 toggle_fold_multiple_buffers: Task::ready(()),
2228 serialize_selections: Task::ready(()),
2229 serialize_folds: Task::ready(()),
2230 text_style_refinement: None,
2231 load_diff_task: load_uncommitted_diff,
2232 temporary_diff_override: false,
2233 mouse_cursor_hidden: false,
2234 minimap: None,
2235 hide_mouse_mode: EditorSettings::get_global(cx)
2236 .hide_mouse
2237 .unwrap_or_default(),
2238 change_list: ChangeList::new(),
2239 mode,
2240 selection_drag_state: SelectionDragState::None,
2241 folding_newlines: Task::ready(()),
2242 lookup_key: None,
2243 };
2244
2245 if is_minimap {
2246 return editor;
2247 }
2248
2249 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2250 editor
2251 ._subscriptions
2252 .push(cx.observe(breakpoints, |_, _, cx| {
2253 cx.notify();
2254 }));
2255 }
2256 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2257 editor._subscriptions.extend(project_subscriptions);
2258
2259 editor._subscriptions.push(cx.subscribe_in(
2260 &cx.entity(),
2261 window,
2262 |editor, _, e: &EditorEvent, window, cx| match e {
2263 EditorEvent::ScrollPositionChanged { local, .. } => {
2264 if *local {
2265 let new_anchor = editor.scroll_manager.anchor();
2266 let snapshot = editor.snapshot(window, cx);
2267 editor.update_restoration_data(cx, move |data| {
2268 data.scroll_position = (
2269 new_anchor.top_row(snapshot.buffer_snapshot()),
2270 new_anchor.offset,
2271 );
2272 });
2273 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2274 editor.inline_blame_popover.take();
2275 }
2276 }
2277 EditorEvent::Edited { .. } => {
2278 if !vim_enabled(cx) {
2279 let display_map = editor.display_snapshot(cx);
2280 let selections = editor.selections.all_adjusted_display(&display_map);
2281 let pop_state = editor
2282 .change_list
2283 .last()
2284 .map(|previous| {
2285 previous.len() == selections.len()
2286 && previous.iter().enumerate().all(|(ix, p)| {
2287 p.to_display_point(&display_map).row()
2288 == selections[ix].head().row()
2289 })
2290 })
2291 .unwrap_or(false);
2292 let new_positions = selections
2293 .into_iter()
2294 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2295 .collect();
2296 editor
2297 .change_list
2298 .push_to_change_list(pop_state, new_positions);
2299 }
2300 }
2301 _ => (),
2302 },
2303 ));
2304
2305 if let Some(dap_store) = editor
2306 .project
2307 .as_ref()
2308 .map(|project| project.read(cx).dap_store())
2309 {
2310 let weak_editor = cx.weak_entity();
2311
2312 editor
2313 ._subscriptions
2314 .push(
2315 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2316 let session_entity = cx.entity();
2317 weak_editor
2318 .update(cx, |editor, cx| {
2319 editor._subscriptions.push(
2320 cx.subscribe(&session_entity, Self::on_debug_session_event),
2321 );
2322 })
2323 .ok();
2324 }),
2325 );
2326
2327 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2328 editor
2329 ._subscriptions
2330 .push(cx.subscribe(&session, Self::on_debug_session_event));
2331 }
2332 }
2333
2334 // skip adding the initial selection to selection history
2335 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2336 editor.end_selection(window, cx);
2337 editor.selection_history.mode = SelectionHistoryMode::Normal;
2338
2339 editor.scroll_manager.show_scrollbars(window, cx);
2340 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2341
2342 if full_mode {
2343 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2344 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2345
2346 if editor.git_blame_inline_enabled {
2347 editor.start_git_blame_inline(false, window, cx);
2348 }
2349
2350 editor.go_to_active_debug_line(window, cx);
2351
2352 editor.minimap =
2353 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2354 editor.colors = Some(LspColorData::new(cx));
2355 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2356
2357 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2358 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2359 }
2360 editor.update_lsp_data(None, window, cx);
2361 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2362 }
2363
2364 editor
2365 }
2366
2367 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2368 self.selections.display_map(cx)
2369 }
2370
2371 pub fn deploy_mouse_context_menu(
2372 &mut self,
2373 position: gpui::Point<Pixels>,
2374 context_menu: Entity<ContextMenu>,
2375 window: &mut Window,
2376 cx: &mut Context<Self>,
2377 ) {
2378 self.mouse_context_menu = Some(MouseContextMenu::new(
2379 self,
2380 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2381 context_menu,
2382 window,
2383 cx,
2384 ));
2385 }
2386
2387 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2388 self.mouse_context_menu
2389 .as_ref()
2390 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2391 }
2392
2393 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2394 if self
2395 .selections
2396 .pending_anchor()
2397 .is_some_and(|pending_selection| {
2398 let snapshot = self.buffer().read(cx).snapshot(cx);
2399 pending_selection.range().includes(range, &snapshot)
2400 })
2401 {
2402 return true;
2403 }
2404
2405 self.selections
2406 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2407 .into_iter()
2408 .any(|selection| {
2409 // This is needed to cover a corner case, if we just check for an existing
2410 // selection in the fold range, having a cursor at the start of the fold
2411 // marks it as selected. Non-empty selections don't cause this.
2412 let length = selection.end - selection.start;
2413 length > 0
2414 })
2415 }
2416
2417 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2418 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2419 }
2420
2421 fn key_context_internal(
2422 &self,
2423 has_active_edit_prediction: bool,
2424 window: &mut Window,
2425 cx: &mut App,
2426 ) -> KeyContext {
2427 let mut key_context = KeyContext::new_with_defaults();
2428 key_context.add("Editor");
2429 let mode = match self.mode {
2430 EditorMode::SingleLine => "single_line",
2431 EditorMode::AutoHeight { .. } => "auto_height",
2432 EditorMode::Minimap { .. } => "minimap",
2433 EditorMode::Full { .. } => "full",
2434 };
2435
2436 if EditorSettings::jupyter_enabled(cx) {
2437 key_context.add("jupyter");
2438 }
2439
2440 key_context.set("mode", mode);
2441 if self.pending_rename.is_some() {
2442 key_context.add("renaming");
2443 }
2444
2445 match self.context_menu.borrow().as_ref() {
2446 Some(CodeContextMenu::Completions(menu)) => {
2447 if menu.visible() {
2448 key_context.add("menu");
2449 key_context.add("showing_completions");
2450 }
2451 }
2452 Some(CodeContextMenu::CodeActions(menu)) => {
2453 if menu.visible() {
2454 key_context.add("menu");
2455 key_context.add("showing_code_actions")
2456 }
2457 }
2458 None => {}
2459 }
2460
2461 if self.signature_help_state.has_multiple_signatures() {
2462 key_context.add("showing_signature_help");
2463 }
2464
2465 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2466 if !self.focus_handle(cx).contains_focused(window, cx)
2467 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2468 {
2469 for addon in self.addons.values() {
2470 addon.extend_key_context(&mut key_context, cx)
2471 }
2472 }
2473
2474 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2475 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2476 Some(
2477 file.full_path(cx)
2478 .extension()?
2479 .to_string_lossy()
2480 .into_owned(),
2481 )
2482 }) {
2483 key_context.set("extension", extension);
2484 }
2485 } else {
2486 key_context.add("multibuffer");
2487 }
2488
2489 if has_active_edit_prediction {
2490 if self.edit_prediction_in_conflict() {
2491 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2492 } else {
2493 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2494 key_context.add("copilot_suggestion");
2495 }
2496 }
2497
2498 if self.selection_mark_mode {
2499 key_context.add("selection_mode");
2500 }
2501
2502 let disjoint = self.selections.disjoint_anchors();
2503 let snapshot = self.snapshot(window, cx);
2504 let snapshot = snapshot.buffer_snapshot();
2505 if self.mode == EditorMode::SingleLine
2506 && let [selection] = disjoint
2507 && selection.start == selection.end
2508 && selection.end.to_offset(snapshot) == snapshot.len()
2509 {
2510 key_context.add("end_of_input");
2511 }
2512
2513 key_context
2514 }
2515
2516 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2517 self.last_bounds.as_ref()
2518 }
2519
2520 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2521 if self.mouse_cursor_hidden {
2522 self.mouse_cursor_hidden = false;
2523 cx.notify();
2524 }
2525 }
2526
2527 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2528 let hide_mouse_cursor = match origin {
2529 HideMouseCursorOrigin::TypingAction => {
2530 matches!(
2531 self.hide_mouse_mode,
2532 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2533 )
2534 }
2535 HideMouseCursorOrigin::MovementAction => {
2536 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2537 }
2538 };
2539 if self.mouse_cursor_hidden != hide_mouse_cursor {
2540 self.mouse_cursor_hidden = hide_mouse_cursor;
2541 cx.notify();
2542 }
2543 }
2544
2545 pub fn edit_prediction_in_conflict(&self) -> bool {
2546 if !self.show_edit_predictions_in_menu() {
2547 return false;
2548 }
2549
2550 let showing_completions = self
2551 .context_menu
2552 .borrow()
2553 .as_ref()
2554 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2555
2556 showing_completions
2557 || self.edit_prediction_requires_modifier()
2558 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2559 // bindings to insert tab characters.
2560 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2561 }
2562
2563 pub fn accept_edit_prediction_keybind(
2564 &self,
2565 accept_partial: bool,
2566 window: &mut Window,
2567 cx: &mut App,
2568 ) -> AcceptEditPredictionBinding {
2569 let key_context = self.key_context_internal(true, window, cx);
2570 let in_conflict = self.edit_prediction_in_conflict();
2571
2572 let bindings = if accept_partial {
2573 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2574 } else {
2575 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2576 };
2577
2578 // TODO: if the binding contains multiple keystrokes, display all of them, not
2579 // just the first one.
2580 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2581 !in_conflict
2582 || binding
2583 .keystrokes()
2584 .first()
2585 .is_some_and(|keystroke| keystroke.modifiers().modified())
2586 }))
2587 }
2588
2589 pub fn new_file(
2590 workspace: &mut Workspace,
2591 _: &workspace::NewFile,
2592 window: &mut Window,
2593 cx: &mut Context<Workspace>,
2594 ) {
2595 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2596 "Failed to create buffer",
2597 window,
2598 cx,
2599 |e, _, _| match e.error_code() {
2600 ErrorCode::RemoteUpgradeRequired => Some(format!(
2601 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2602 e.error_tag("required").unwrap_or("the latest version")
2603 )),
2604 _ => None,
2605 },
2606 );
2607 }
2608
2609 pub fn new_in_workspace(
2610 workspace: &mut Workspace,
2611 window: &mut Window,
2612 cx: &mut Context<Workspace>,
2613 ) -> Task<Result<Entity<Editor>>> {
2614 let project = workspace.project().clone();
2615 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2616
2617 cx.spawn_in(window, async move |workspace, cx| {
2618 let buffer = create.await?;
2619 workspace.update_in(cx, |workspace, window, cx| {
2620 let editor =
2621 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2622 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2623 editor
2624 })
2625 })
2626 }
2627
2628 fn new_file_vertical(
2629 workspace: &mut Workspace,
2630 _: &workspace::NewFileSplitVertical,
2631 window: &mut Window,
2632 cx: &mut Context<Workspace>,
2633 ) {
2634 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2635 }
2636
2637 fn new_file_horizontal(
2638 workspace: &mut Workspace,
2639 _: &workspace::NewFileSplitHorizontal,
2640 window: &mut Window,
2641 cx: &mut Context<Workspace>,
2642 ) {
2643 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2644 }
2645
2646 fn new_file_split(
2647 workspace: &mut Workspace,
2648 action: &workspace::NewFileSplit,
2649 window: &mut Window,
2650 cx: &mut Context<Workspace>,
2651 ) {
2652 Self::new_file_in_direction(workspace, action.0, window, cx)
2653 }
2654
2655 fn new_file_in_direction(
2656 workspace: &mut Workspace,
2657 direction: SplitDirection,
2658 window: &mut Window,
2659 cx: &mut Context<Workspace>,
2660 ) {
2661 let project = workspace.project().clone();
2662 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2663
2664 cx.spawn_in(window, async move |workspace, cx| {
2665 let buffer = create.await?;
2666 workspace.update_in(cx, move |workspace, window, cx| {
2667 workspace.split_item(
2668 direction,
2669 Box::new(
2670 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2671 ),
2672 window,
2673 cx,
2674 )
2675 })?;
2676 anyhow::Ok(())
2677 })
2678 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2679 match e.error_code() {
2680 ErrorCode::RemoteUpgradeRequired => Some(format!(
2681 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2682 e.error_tag("required").unwrap_or("the latest version")
2683 )),
2684 _ => None,
2685 }
2686 });
2687 }
2688
2689 pub fn leader_id(&self) -> Option<CollaboratorId> {
2690 self.leader_id
2691 }
2692
2693 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2694 &self.buffer
2695 }
2696
2697 pub fn project(&self) -> Option<&Entity<Project>> {
2698 self.project.as_ref()
2699 }
2700
2701 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2702 self.workspace.as_ref()?.0.upgrade()
2703 }
2704
2705 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2706 self.buffer().read(cx).title(cx)
2707 }
2708
2709 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2710 let git_blame_gutter_max_author_length = self
2711 .render_git_blame_gutter(cx)
2712 .then(|| {
2713 if let Some(blame) = self.blame.as_ref() {
2714 let max_author_length =
2715 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2716 Some(max_author_length)
2717 } else {
2718 None
2719 }
2720 })
2721 .flatten();
2722
2723 EditorSnapshot {
2724 mode: self.mode.clone(),
2725 show_gutter: self.show_gutter,
2726 show_line_numbers: self.show_line_numbers,
2727 show_git_diff_gutter: self.show_git_diff_gutter,
2728 show_code_actions: self.show_code_actions,
2729 show_runnables: self.show_runnables,
2730 show_breakpoints: self.show_breakpoints,
2731 git_blame_gutter_max_author_length,
2732 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2733 placeholder_display_snapshot: self
2734 .placeholder_display_map
2735 .as_ref()
2736 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2737 scroll_anchor: self.scroll_manager.anchor(),
2738 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2739 is_focused: self.focus_handle.is_focused(window),
2740 current_line_highlight: self
2741 .current_line_highlight
2742 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2743 gutter_hovered: self.gutter_hovered,
2744 }
2745 }
2746
2747 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2748 self.buffer.read(cx).language_at(point, cx)
2749 }
2750
2751 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2752 self.buffer.read(cx).read(cx).file_at(point).cloned()
2753 }
2754
2755 pub fn active_excerpt(
2756 &self,
2757 cx: &App,
2758 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2759 self.buffer
2760 .read(cx)
2761 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2762 }
2763
2764 pub fn mode(&self) -> &EditorMode {
2765 &self.mode
2766 }
2767
2768 pub fn set_mode(&mut self, mode: EditorMode) {
2769 self.mode = mode;
2770 }
2771
2772 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2773 self.collaboration_hub.as_deref()
2774 }
2775
2776 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2777 self.collaboration_hub = Some(hub);
2778 }
2779
2780 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2781 self.in_project_search = in_project_search;
2782 }
2783
2784 pub fn set_custom_context_menu(
2785 &mut self,
2786 f: impl 'static
2787 + Fn(
2788 &mut Self,
2789 DisplayPoint,
2790 &mut Window,
2791 &mut Context<Self>,
2792 ) -> Option<Entity<ui::ContextMenu>>,
2793 ) {
2794 self.custom_context_menu = Some(Box::new(f))
2795 }
2796
2797 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2798 self.completion_provider = provider;
2799 }
2800
2801 #[cfg(any(test, feature = "test-support"))]
2802 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2803 self.completion_provider.clone()
2804 }
2805
2806 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2807 self.semantics_provider.clone()
2808 }
2809
2810 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2811 self.semantics_provider = provider;
2812 }
2813
2814 pub fn set_edit_prediction_provider<T>(
2815 &mut self,
2816 provider: Option<Entity<T>>,
2817 window: &mut Window,
2818 cx: &mut Context<Self>,
2819 ) where
2820 T: EditPredictionProvider,
2821 {
2822 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2823 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2824 if this.focus_handle.is_focused(window) {
2825 this.update_visible_edit_prediction(window, cx);
2826 }
2827 }),
2828 provider: Arc::new(provider),
2829 });
2830 self.update_edit_prediction_settings(cx);
2831 self.refresh_edit_prediction(false, false, window, cx);
2832 }
2833
2834 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2835 self.placeholder_display_map
2836 .as_ref()
2837 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2838 }
2839
2840 pub fn set_placeholder_text(
2841 &mut self,
2842 placeholder_text: &str,
2843 window: &mut Window,
2844 cx: &mut Context<Self>,
2845 ) {
2846 let multibuffer = cx
2847 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2848
2849 let style = window.text_style();
2850
2851 self.placeholder_display_map = Some(cx.new(|cx| {
2852 DisplayMap::new(
2853 multibuffer,
2854 style.font(),
2855 style.font_size.to_pixels(window.rem_size()),
2856 None,
2857 FILE_HEADER_HEIGHT,
2858 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2859 Default::default(),
2860 DiagnosticSeverity::Off,
2861 cx,
2862 )
2863 }));
2864 cx.notify();
2865 }
2866
2867 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2868 self.cursor_shape = cursor_shape;
2869
2870 // Disrupt blink for immediate user feedback that the cursor shape has changed
2871 self.blink_manager.update(cx, BlinkManager::show_cursor);
2872
2873 cx.notify();
2874 }
2875
2876 pub fn set_current_line_highlight(
2877 &mut self,
2878 current_line_highlight: Option<CurrentLineHighlight>,
2879 ) {
2880 self.current_line_highlight = current_line_highlight;
2881 }
2882
2883 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2884 self.collapse_matches = collapse_matches;
2885 }
2886
2887 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2888 if self.collapse_matches {
2889 return range.start..range.start;
2890 }
2891 range.clone()
2892 }
2893
2894 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2895 if self.display_map.read(cx).clip_at_line_ends != clip {
2896 self.display_map
2897 .update(cx, |map, _| map.clip_at_line_ends = clip);
2898 }
2899 }
2900
2901 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2902 self.input_enabled = input_enabled;
2903 }
2904
2905 pub fn set_edit_predictions_hidden_for_vim_mode(
2906 &mut self,
2907 hidden: bool,
2908 window: &mut Window,
2909 cx: &mut Context<Self>,
2910 ) {
2911 if hidden != self.edit_predictions_hidden_for_vim_mode {
2912 self.edit_predictions_hidden_for_vim_mode = hidden;
2913 if hidden {
2914 self.update_visible_edit_prediction(window, cx);
2915 } else {
2916 self.refresh_edit_prediction(true, false, window, cx);
2917 }
2918 }
2919 }
2920
2921 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2922 self.menu_edit_predictions_policy = value;
2923 }
2924
2925 pub fn set_autoindent(&mut self, autoindent: bool) {
2926 if autoindent {
2927 self.autoindent_mode = Some(AutoindentMode::EachLine);
2928 } else {
2929 self.autoindent_mode = None;
2930 }
2931 }
2932
2933 pub fn read_only(&self, cx: &App) -> bool {
2934 self.read_only || self.buffer.read(cx).read_only()
2935 }
2936
2937 pub fn set_read_only(&mut self, read_only: bool) {
2938 self.read_only = read_only;
2939 }
2940
2941 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2942 self.use_autoclose = autoclose;
2943 }
2944
2945 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2946 self.use_auto_surround = auto_surround;
2947 }
2948
2949 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2950 self.auto_replace_emoji_shortcode = auto_replace;
2951 }
2952
2953 pub fn toggle_edit_predictions(
2954 &mut self,
2955 _: &ToggleEditPrediction,
2956 window: &mut Window,
2957 cx: &mut Context<Self>,
2958 ) {
2959 if self.show_edit_predictions_override.is_some() {
2960 self.set_show_edit_predictions(None, window, cx);
2961 } else {
2962 let show_edit_predictions = !self.edit_predictions_enabled();
2963 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2964 }
2965 }
2966
2967 pub fn set_show_edit_predictions(
2968 &mut self,
2969 show_edit_predictions: Option<bool>,
2970 window: &mut Window,
2971 cx: &mut Context<Self>,
2972 ) {
2973 self.show_edit_predictions_override = show_edit_predictions;
2974 self.update_edit_prediction_settings(cx);
2975
2976 if let Some(false) = show_edit_predictions {
2977 self.discard_edit_prediction(false, cx);
2978 } else {
2979 self.refresh_edit_prediction(false, true, window, cx);
2980 }
2981 }
2982
2983 fn edit_predictions_disabled_in_scope(
2984 &self,
2985 buffer: &Entity<Buffer>,
2986 buffer_position: language::Anchor,
2987 cx: &App,
2988 ) -> bool {
2989 let snapshot = buffer.read(cx).snapshot();
2990 let settings = snapshot.settings_at(buffer_position, cx);
2991
2992 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2993 return false;
2994 };
2995
2996 scope.override_name().is_some_and(|scope_name| {
2997 settings
2998 .edit_predictions_disabled_in
2999 .iter()
3000 .any(|s| s == scope_name)
3001 })
3002 }
3003
3004 pub fn set_use_modal_editing(&mut self, to: bool) {
3005 self.use_modal_editing = to;
3006 }
3007
3008 pub fn use_modal_editing(&self) -> bool {
3009 self.use_modal_editing
3010 }
3011
3012 fn selections_did_change(
3013 &mut self,
3014 local: bool,
3015 old_cursor_position: &Anchor,
3016 effects: SelectionEffects,
3017 window: &mut Window,
3018 cx: &mut Context<Self>,
3019 ) {
3020 window.invalidate_character_coordinates();
3021
3022 // Copy selections to primary selection buffer
3023 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3024 if local {
3025 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3026 let buffer_handle = self.buffer.read(cx).read(cx);
3027
3028 let mut text = String::new();
3029 for (index, selection) in selections.iter().enumerate() {
3030 let text_for_selection = buffer_handle
3031 .text_for_range(selection.start..selection.end)
3032 .collect::<String>();
3033
3034 text.push_str(&text_for_selection);
3035 if index != selections.len() - 1 {
3036 text.push('\n');
3037 }
3038 }
3039
3040 if !text.is_empty() {
3041 cx.write_to_primary(ClipboardItem::new_string(text));
3042 }
3043 }
3044
3045 let selection_anchors = self.selections.disjoint_anchors_arc();
3046
3047 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3048 self.buffer.update(cx, |buffer, cx| {
3049 buffer.set_active_selections(
3050 &selection_anchors,
3051 self.selections.line_mode(),
3052 self.cursor_shape,
3053 cx,
3054 )
3055 });
3056 }
3057 let display_map = self
3058 .display_map
3059 .update(cx, |display_map, cx| display_map.snapshot(cx));
3060 let buffer = display_map.buffer_snapshot();
3061 if self.selections.count() == 1 {
3062 self.add_selections_state = None;
3063 }
3064 self.select_next_state = None;
3065 self.select_prev_state = None;
3066 self.select_syntax_node_history.try_clear();
3067 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3068 self.snippet_stack.invalidate(&selection_anchors, buffer);
3069 self.take_rename(false, window, cx);
3070
3071 let newest_selection = self.selections.newest_anchor();
3072 let new_cursor_position = newest_selection.head();
3073 let selection_start = newest_selection.start;
3074
3075 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3076 self.push_to_nav_history(
3077 *old_cursor_position,
3078 Some(new_cursor_position.to_point(buffer)),
3079 false,
3080 effects.nav_history == Some(true),
3081 cx,
3082 );
3083 }
3084
3085 if local {
3086 if let Some(buffer_id) = new_cursor_position.buffer_id {
3087 self.register_buffer(buffer_id, cx);
3088 }
3089
3090 let mut context_menu = self.context_menu.borrow_mut();
3091 let completion_menu = match context_menu.as_ref() {
3092 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3093 Some(CodeContextMenu::CodeActions(_)) => {
3094 *context_menu = None;
3095 None
3096 }
3097 None => None,
3098 };
3099 let completion_position = completion_menu.map(|menu| menu.initial_position);
3100 drop(context_menu);
3101
3102 if effects.completions
3103 && let Some(completion_position) = completion_position
3104 {
3105 let start_offset = selection_start.to_offset(buffer);
3106 let position_matches = start_offset == completion_position.to_offset(buffer);
3107 let continue_showing = if position_matches {
3108 if self.snippet_stack.is_empty() {
3109 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3110 == Some(CharKind::Word)
3111 } else {
3112 // Snippet choices can be shown even when the cursor is in whitespace.
3113 // Dismissing the menu with actions like backspace is handled by
3114 // invalidation regions.
3115 true
3116 }
3117 } else {
3118 false
3119 };
3120
3121 if continue_showing {
3122 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3123 } else {
3124 self.hide_context_menu(window, cx);
3125 }
3126 }
3127
3128 hide_hover(self, cx);
3129
3130 if old_cursor_position.to_display_point(&display_map).row()
3131 != new_cursor_position.to_display_point(&display_map).row()
3132 {
3133 self.available_code_actions.take();
3134 }
3135 self.refresh_code_actions(window, cx);
3136 self.refresh_document_highlights(cx);
3137 refresh_linked_ranges(self, window, cx);
3138
3139 self.refresh_selected_text_highlights(false, window, cx);
3140 self.refresh_matching_bracket_highlights(window, cx);
3141 self.update_visible_edit_prediction(window, cx);
3142 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3143 self.inline_blame_popover.take();
3144 if self.git_blame_inline_enabled {
3145 self.start_inline_blame_timer(window, cx);
3146 }
3147 }
3148
3149 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3150 cx.emit(EditorEvent::SelectionsChanged { local });
3151
3152 let selections = &self.selections.disjoint_anchors_arc();
3153 if selections.len() == 1 {
3154 cx.emit(SearchEvent::ActiveMatchChanged)
3155 }
3156 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3157 let inmemory_selections = selections
3158 .iter()
3159 .map(|s| {
3160 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3161 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3162 })
3163 .collect();
3164 self.update_restoration_data(cx, |data| {
3165 data.selections = inmemory_selections;
3166 });
3167
3168 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3169 && let Some(workspace_id) =
3170 self.workspace.as_ref().and_then(|workspace| workspace.1)
3171 {
3172 let snapshot = self.buffer().read(cx).snapshot(cx);
3173 let selections = selections.clone();
3174 let background_executor = cx.background_executor().clone();
3175 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3176 self.serialize_selections = cx.background_spawn(async move {
3177 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3178 let db_selections = selections
3179 .iter()
3180 .map(|selection| {
3181 (
3182 selection.start.to_offset(&snapshot),
3183 selection.end.to_offset(&snapshot),
3184 )
3185 })
3186 .collect();
3187
3188 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3189 .await
3190 .with_context(|| {
3191 format!(
3192 "persisting editor selections for editor {editor_id}, \
3193 workspace {workspace_id:?}"
3194 )
3195 })
3196 .log_err();
3197 });
3198 }
3199 }
3200
3201 cx.notify();
3202 }
3203
3204 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3205 use text::ToOffset as _;
3206 use text::ToPoint as _;
3207
3208 if self.mode.is_minimap()
3209 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3210 {
3211 return;
3212 }
3213
3214 if !self.buffer().read(cx).is_singleton() {
3215 return;
3216 }
3217
3218 let display_snapshot = self
3219 .display_map
3220 .update(cx, |display_map, cx| display_map.snapshot(cx));
3221 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3222 return;
3223 };
3224 let inmemory_folds = display_snapshot
3225 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3226 .map(|fold| {
3227 fold.range.start.text_anchor.to_point(&snapshot)
3228 ..fold.range.end.text_anchor.to_point(&snapshot)
3229 })
3230 .collect();
3231 self.update_restoration_data(cx, |data| {
3232 data.folds = inmemory_folds;
3233 });
3234
3235 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3236 return;
3237 };
3238 let background_executor = cx.background_executor().clone();
3239 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3240 let db_folds = display_snapshot
3241 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3242 .map(|fold| {
3243 (
3244 fold.range.start.text_anchor.to_offset(&snapshot),
3245 fold.range.end.text_anchor.to_offset(&snapshot),
3246 )
3247 })
3248 .collect();
3249 self.serialize_folds = cx.background_spawn(async move {
3250 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3251 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3252 .await
3253 .with_context(|| {
3254 format!(
3255 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3256 )
3257 })
3258 .log_err();
3259 });
3260 }
3261
3262 pub fn sync_selections(
3263 &mut self,
3264 other: Entity<Editor>,
3265 cx: &mut Context<Self>,
3266 ) -> gpui::Subscription {
3267 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3268 if !other_selections.is_empty() {
3269 self.selections.change_with(cx, |selections| {
3270 selections.select_anchors(other_selections);
3271 });
3272 }
3273
3274 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3275 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3276 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3277 if other_selections.is_empty() {
3278 return;
3279 }
3280 this.selections.change_with(cx, |selections| {
3281 selections.select_anchors(other_selections);
3282 });
3283 }
3284 });
3285
3286 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3287 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3288 let these_selections = this.selections.disjoint_anchors().to_vec();
3289 if these_selections.is_empty() {
3290 return;
3291 }
3292 other.update(cx, |other_editor, cx| {
3293 other_editor.selections.change_with(cx, |selections| {
3294 selections.select_anchors(these_selections);
3295 })
3296 });
3297 }
3298 });
3299
3300 Subscription::join(other_subscription, this_subscription)
3301 }
3302
3303 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3304 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3305 /// effects of selection change occur at the end of the transaction.
3306 pub fn change_selections<R>(
3307 &mut self,
3308 effects: SelectionEffects,
3309 window: &mut Window,
3310 cx: &mut Context<Self>,
3311 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3312 ) -> R {
3313 if let Some(state) = &mut self.deferred_selection_effects_state {
3314 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3315 state.effects.completions = effects.completions;
3316 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3317 let (changed, result) = self.selections.change_with(cx, change);
3318 state.changed |= changed;
3319 return result;
3320 }
3321 let mut state = DeferredSelectionEffectsState {
3322 changed: false,
3323 effects,
3324 old_cursor_position: self.selections.newest_anchor().head(),
3325 history_entry: SelectionHistoryEntry {
3326 selections: self.selections.disjoint_anchors_arc(),
3327 select_next_state: self.select_next_state.clone(),
3328 select_prev_state: self.select_prev_state.clone(),
3329 add_selections_state: self.add_selections_state.clone(),
3330 },
3331 };
3332 let (changed, result) = self.selections.change_with(cx, change);
3333 state.changed = state.changed || changed;
3334 if self.defer_selection_effects {
3335 self.deferred_selection_effects_state = Some(state);
3336 } else {
3337 self.apply_selection_effects(state, window, cx);
3338 }
3339 result
3340 }
3341
3342 /// Defers the effects of selection change, so that the effects of multiple calls to
3343 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3344 /// to selection history and the state of popovers based on selection position aren't
3345 /// erroneously updated.
3346 pub fn with_selection_effects_deferred<R>(
3347 &mut self,
3348 window: &mut Window,
3349 cx: &mut Context<Self>,
3350 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3351 ) -> R {
3352 let already_deferred = self.defer_selection_effects;
3353 self.defer_selection_effects = true;
3354 let result = update(self, window, cx);
3355 if !already_deferred {
3356 self.defer_selection_effects = false;
3357 if let Some(state) = self.deferred_selection_effects_state.take() {
3358 self.apply_selection_effects(state, window, cx);
3359 }
3360 }
3361 result
3362 }
3363
3364 fn apply_selection_effects(
3365 &mut self,
3366 state: DeferredSelectionEffectsState,
3367 window: &mut Window,
3368 cx: &mut Context<Self>,
3369 ) {
3370 if state.changed {
3371 self.selection_history.push(state.history_entry);
3372
3373 if let Some(autoscroll) = state.effects.scroll {
3374 self.request_autoscroll(autoscroll, cx);
3375 }
3376
3377 let old_cursor_position = &state.old_cursor_position;
3378
3379 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3380
3381 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3382 self.show_signature_help(&ShowSignatureHelp, window, cx);
3383 }
3384 }
3385 }
3386
3387 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3388 where
3389 I: IntoIterator<Item = (Range<S>, T)>,
3390 S: ToOffset,
3391 T: Into<Arc<str>>,
3392 {
3393 if self.read_only(cx) {
3394 return;
3395 }
3396
3397 self.buffer
3398 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3399 }
3400
3401 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3402 where
3403 I: IntoIterator<Item = (Range<S>, T)>,
3404 S: ToOffset,
3405 T: Into<Arc<str>>,
3406 {
3407 if self.read_only(cx) {
3408 return;
3409 }
3410
3411 self.buffer.update(cx, |buffer, cx| {
3412 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3413 });
3414 }
3415
3416 pub fn edit_with_block_indent<I, S, T>(
3417 &mut self,
3418 edits: I,
3419 original_indent_columns: Vec<Option<u32>>,
3420 cx: &mut Context<Self>,
3421 ) where
3422 I: IntoIterator<Item = (Range<S>, T)>,
3423 S: ToOffset,
3424 T: Into<Arc<str>>,
3425 {
3426 if self.read_only(cx) {
3427 return;
3428 }
3429
3430 self.buffer.update(cx, |buffer, cx| {
3431 buffer.edit(
3432 edits,
3433 Some(AutoindentMode::Block {
3434 original_indent_columns,
3435 }),
3436 cx,
3437 )
3438 });
3439 }
3440
3441 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3442 self.hide_context_menu(window, cx);
3443
3444 match phase {
3445 SelectPhase::Begin {
3446 position,
3447 add,
3448 click_count,
3449 } => self.begin_selection(position, add, click_count, window, cx),
3450 SelectPhase::BeginColumnar {
3451 position,
3452 goal_column,
3453 reset,
3454 mode,
3455 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3456 SelectPhase::Extend {
3457 position,
3458 click_count,
3459 } => self.extend_selection(position, click_count, window, cx),
3460 SelectPhase::Update {
3461 position,
3462 goal_column,
3463 scroll_delta,
3464 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3465 SelectPhase::End => self.end_selection(window, cx),
3466 }
3467 }
3468
3469 fn extend_selection(
3470 &mut self,
3471 position: DisplayPoint,
3472 click_count: usize,
3473 window: &mut Window,
3474 cx: &mut Context<Self>,
3475 ) {
3476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3477 let tail = self.selections.newest::<usize>(&display_map).tail();
3478 let click_count = click_count.max(match self.selections.select_mode() {
3479 SelectMode::Character => 1,
3480 SelectMode::Word(_) => 2,
3481 SelectMode::Line(_) => 3,
3482 SelectMode::All => 4,
3483 });
3484 self.begin_selection(position, false, click_count, window, cx);
3485
3486 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3487
3488 let current_selection = match self.selections.select_mode() {
3489 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3490 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3491 };
3492
3493 let mut pending_selection = self
3494 .selections
3495 .pending_anchor()
3496 .cloned()
3497 .expect("extend_selection not called with pending selection");
3498
3499 if pending_selection
3500 .start
3501 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3502 == Ordering::Greater
3503 {
3504 pending_selection.start = current_selection.start;
3505 }
3506 if pending_selection
3507 .end
3508 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3509 == Ordering::Less
3510 {
3511 pending_selection.end = current_selection.end;
3512 pending_selection.reversed = true;
3513 }
3514
3515 let mut pending_mode = self.selections.pending_mode().unwrap();
3516 match &mut pending_mode {
3517 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3518 _ => {}
3519 }
3520
3521 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3522 SelectionEffects::scroll(Autoscroll::fit())
3523 } else {
3524 SelectionEffects::no_scroll()
3525 };
3526
3527 self.change_selections(effects, window, cx, |s| {
3528 s.set_pending(pending_selection.clone(), pending_mode);
3529 s.set_is_extending(true);
3530 });
3531 }
3532
3533 fn begin_selection(
3534 &mut self,
3535 position: DisplayPoint,
3536 add: bool,
3537 click_count: usize,
3538 window: &mut Window,
3539 cx: &mut Context<Self>,
3540 ) {
3541 if !self.focus_handle.is_focused(window) {
3542 self.last_focused_descendant = None;
3543 window.focus(&self.focus_handle);
3544 }
3545
3546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3547 let buffer = display_map.buffer_snapshot();
3548 let position = display_map.clip_point(position, Bias::Left);
3549
3550 let start;
3551 let end;
3552 let mode;
3553 let mut auto_scroll;
3554 match click_count {
3555 1 => {
3556 start = buffer.anchor_before(position.to_point(&display_map));
3557 end = start;
3558 mode = SelectMode::Character;
3559 auto_scroll = true;
3560 }
3561 2 => {
3562 let position = display_map
3563 .clip_point(position, Bias::Left)
3564 .to_offset(&display_map, Bias::Left);
3565 let (range, _) = buffer.surrounding_word(position, None);
3566 start = buffer.anchor_before(range.start);
3567 end = buffer.anchor_before(range.end);
3568 mode = SelectMode::Word(start..end);
3569 auto_scroll = true;
3570 }
3571 3 => {
3572 let position = display_map
3573 .clip_point(position, Bias::Left)
3574 .to_point(&display_map);
3575 let line_start = display_map.prev_line_boundary(position).0;
3576 let next_line_start = buffer.clip_point(
3577 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3578 Bias::Left,
3579 );
3580 start = buffer.anchor_before(line_start);
3581 end = buffer.anchor_before(next_line_start);
3582 mode = SelectMode::Line(start..end);
3583 auto_scroll = true;
3584 }
3585 _ => {
3586 start = buffer.anchor_before(0);
3587 end = buffer.anchor_before(buffer.len());
3588 mode = SelectMode::All;
3589 auto_scroll = false;
3590 }
3591 }
3592 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3593
3594 let point_to_delete: Option<usize> = {
3595 let selected_points: Vec<Selection<Point>> =
3596 self.selections.disjoint_in_range(start..end, &display_map);
3597
3598 if !add || click_count > 1 {
3599 None
3600 } else if !selected_points.is_empty() {
3601 Some(selected_points[0].id)
3602 } else {
3603 let clicked_point_already_selected =
3604 self.selections.disjoint_anchors().iter().find(|selection| {
3605 selection.start.to_point(buffer) == start.to_point(buffer)
3606 || selection.end.to_point(buffer) == end.to_point(buffer)
3607 });
3608
3609 clicked_point_already_selected.map(|selection| selection.id)
3610 }
3611 };
3612
3613 let selections_count = self.selections.count();
3614 let effects = if auto_scroll {
3615 SelectionEffects::default()
3616 } else {
3617 SelectionEffects::no_scroll()
3618 };
3619
3620 self.change_selections(effects, window, cx, |s| {
3621 if let Some(point_to_delete) = point_to_delete {
3622 s.delete(point_to_delete);
3623
3624 if selections_count == 1 {
3625 s.set_pending_anchor_range(start..end, mode);
3626 }
3627 } else {
3628 if !add {
3629 s.clear_disjoint();
3630 }
3631
3632 s.set_pending_anchor_range(start..end, mode);
3633 }
3634 });
3635 }
3636
3637 fn begin_columnar_selection(
3638 &mut self,
3639 position: DisplayPoint,
3640 goal_column: u32,
3641 reset: bool,
3642 mode: ColumnarMode,
3643 window: &mut Window,
3644 cx: &mut Context<Self>,
3645 ) {
3646 if !self.focus_handle.is_focused(window) {
3647 self.last_focused_descendant = None;
3648 window.focus(&self.focus_handle);
3649 }
3650
3651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3652
3653 if reset {
3654 let pointer_position = display_map
3655 .buffer_snapshot()
3656 .anchor_before(position.to_point(&display_map));
3657
3658 self.change_selections(
3659 SelectionEffects::scroll(Autoscroll::newest()),
3660 window,
3661 cx,
3662 |s| {
3663 s.clear_disjoint();
3664 s.set_pending_anchor_range(
3665 pointer_position..pointer_position,
3666 SelectMode::Character,
3667 );
3668 },
3669 );
3670 };
3671
3672 let tail = self.selections.newest::<Point>(&display_map).tail();
3673 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3674 self.columnar_selection_state = match mode {
3675 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3676 selection_tail: selection_anchor,
3677 display_point: if reset {
3678 if position.column() != goal_column {
3679 Some(DisplayPoint::new(position.row(), goal_column))
3680 } else {
3681 None
3682 }
3683 } else {
3684 None
3685 },
3686 }),
3687 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3688 selection_tail: selection_anchor,
3689 }),
3690 };
3691
3692 if !reset {
3693 self.select_columns(position, goal_column, &display_map, window, cx);
3694 }
3695 }
3696
3697 fn update_selection(
3698 &mut self,
3699 position: DisplayPoint,
3700 goal_column: u32,
3701 scroll_delta: gpui::Point<f32>,
3702 window: &mut Window,
3703 cx: &mut Context<Self>,
3704 ) {
3705 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3706
3707 if self.columnar_selection_state.is_some() {
3708 self.select_columns(position, goal_column, &display_map, window, cx);
3709 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3710 let buffer = display_map.buffer_snapshot();
3711 let head;
3712 let tail;
3713 let mode = self.selections.pending_mode().unwrap();
3714 match &mode {
3715 SelectMode::Character => {
3716 head = position.to_point(&display_map);
3717 tail = pending.tail().to_point(buffer);
3718 }
3719 SelectMode::Word(original_range) => {
3720 let offset = display_map
3721 .clip_point(position, Bias::Left)
3722 .to_offset(&display_map, Bias::Left);
3723 let original_range = original_range.to_offset(buffer);
3724
3725 let head_offset = if buffer.is_inside_word(offset, None)
3726 || original_range.contains(&offset)
3727 {
3728 let (word_range, _) = buffer.surrounding_word(offset, None);
3729 if word_range.start < original_range.start {
3730 word_range.start
3731 } else {
3732 word_range.end
3733 }
3734 } else {
3735 offset
3736 };
3737
3738 head = head_offset.to_point(buffer);
3739 if head_offset <= original_range.start {
3740 tail = original_range.end.to_point(buffer);
3741 } else {
3742 tail = original_range.start.to_point(buffer);
3743 }
3744 }
3745 SelectMode::Line(original_range) => {
3746 let original_range = original_range.to_point(display_map.buffer_snapshot());
3747
3748 let position = display_map
3749 .clip_point(position, Bias::Left)
3750 .to_point(&display_map);
3751 let line_start = display_map.prev_line_boundary(position).0;
3752 let next_line_start = buffer.clip_point(
3753 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3754 Bias::Left,
3755 );
3756
3757 if line_start < original_range.start {
3758 head = line_start
3759 } else {
3760 head = next_line_start
3761 }
3762
3763 if head <= original_range.start {
3764 tail = original_range.end;
3765 } else {
3766 tail = original_range.start;
3767 }
3768 }
3769 SelectMode::All => {
3770 return;
3771 }
3772 };
3773
3774 if head < tail {
3775 pending.start = buffer.anchor_before(head);
3776 pending.end = buffer.anchor_before(tail);
3777 pending.reversed = true;
3778 } else {
3779 pending.start = buffer.anchor_before(tail);
3780 pending.end = buffer.anchor_before(head);
3781 pending.reversed = false;
3782 }
3783
3784 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3785 s.set_pending(pending.clone(), mode);
3786 });
3787 } else {
3788 log::error!("update_selection dispatched with no pending selection");
3789 return;
3790 }
3791
3792 self.apply_scroll_delta(scroll_delta, window, cx);
3793 cx.notify();
3794 }
3795
3796 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3797 self.columnar_selection_state.take();
3798 if let Some(pending_mode) = self.selections.pending_mode() {
3799 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3800 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3801 s.select(selections);
3802 s.clear_pending();
3803 if s.is_extending() {
3804 s.set_is_extending(false);
3805 } else {
3806 s.set_select_mode(pending_mode);
3807 }
3808 });
3809 }
3810 }
3811
3812 fn select_columns(
3813 &mut self,
3814 head: DisplayPoint,
3815 goal_column: u32,
3816 display_map: &DisplaySnapshot,
3817 window: &mut Window,
3818 cx: &mut Context<Self>,
3819 ) {
3820 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3821 return;
3822 };
3823
3824 let tail = match columnar_state {
3825 ColumnarSelectionState::FromMouse {
3826 selection_tail,
3827 display_point,
3828 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3829 ColumnarSelectionState::FromSelection { selection_tail } => {
3830 selection_tail.to_display_point(display_map)
3831 }
3832 };
3833
3834 let start_row = cmp::min(tail.row(), head.row());
3835 let end_row = cmp::max(tail.row(), head.row());
3836 let start_column = cmp::min(tail.column(), goal_column);
3837 let end_column = cmp::max(tail.column(), goal_column);
3838 let reversed = start_column < tail.column();
3839
3840 let selection_ranges = (start_row.0..=end_row.0)
3841 .map(DisplayRow)
3842 .filter_map(|row| {
3843 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3844 || start_column <= display_map.line_len(row))
3845 && !display_map.is_block_line(row)
3846 {
3847 let start = display_map
3848 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3849 .to_point(display_map);
3850 let end = display_map
3851 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3852 .to_point(display_map);
3853 if reversed {
3854 Some(end..start)
3855 } else {
3856 Some(start..end)
3857 }
3858 } else {
3859 None
3860 }
3861 })
3862 .collect::<Vec<_>>();
3863 if selection_ranges.is_empty() {
3864 return;
3865 }
3866
3867 let ranges = match columnar_state {
3868 ColumnarSelectionState::FromMouse { .. } => {
3869 let mut non_empty_ranges = selection_ranges
3870 .iter()
3871 .filter(|selection_range| selection_range.start != selection_range.end)
3872 .peekable();
3873 if non_empty_ranges.peek().is_some() {
3874 non_empty_ranges.cloned().collect()
3875 } else {
3876 selection_ranges
3877 }
3878 }
3879 _ => selection_ranges,
3880 };
3881
3882 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3883 s.select_ranges(ranges);
3884 });
3885 cx.notify();
3886 }
3887
3888 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3889 self.selections
3890 .all_adjusted(snapshot)
3891 .iter()
3892 .any(|selection| !selection.is_empty())
3893 }
3894
3895 pub fn has_pending_nonempty_selection(&self) -> bool {
3896 let pending_nonempty_selection = match self.selections.pending_anchor() {
3897 Some(Selection { start, end, .. }) => start != end,
3898 None => false,
3899 };
3900
3901 pending_nonempty_selection
3902 || (self.columnar_selection_state.is_some()
3903 && self.selections.disjoint_anchors().len() > 1)
3904 }
3905
3906 pub fn has_pending_selection(&self) -> bool {
3907 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3908 }
3909
3910 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3911 self.selection_mark_mode = false;
3912 self.selection_drag_state = SelectionDragState::None;
3913
3914 if self.clear_expanded_diff_hunks(cx) {
3915 cx.notify();
3916 return;
3917 }
3918 if self.dismiss_menus_and_popups(true, window, cx) {
3919 return;
3920 }
3921
3922 if self.mode.is_full()
3923 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3924 {
3925 return;
3926 }
3927
3928 cx.propagate();
3929 }
3930
3931 pub fn dismiss_menus_and_popups(
3932 &mut self,
3933 is_user_requested: bool,
3934 window: &mut Window,
3935 cx: &mut Context<Self>,
3936 ) -> bool {
3937 if self.take_rename(false, window, cx).is_some() {
3938 return true;
3939 }
3940
3941 if self.hide_blame_popover(true, cx) {
3942 return true;
3943 }
3944
3945 if hide_hover(self, cx) {
3946 return true;
3947 }
3948
3949 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3950 return true;
3951 }
3952
3953 if self.hide_context_menu(window, cx).is_some() {
3954 return true;
3955 }
3956
3957 if self.mouse_context_menu.take().is_some() {
3958 return true;
3959 }
3960
3961 if is_user_requested && self.discard_edit_prediction(true, cx) {
3962 return true;
3963 }
3964
3965 if self.snippet_stack.pop().is_some() {
3966 return true;
3967 }
3968
3969 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3970 self.dismiss_diagnostics(cx);
3971 return true;
3972 }
3973
3974 false
3975 }
3976
3977 fn linked_editing_ranges_for(
3978 &self,
3979 selection: Range<text::Anchor>,
3980 cx: &App,
3981 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3982 if self.linked_edit_ranges.is_empty() {
3983 return None;
3984 }
3985 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3986 selection.end.buffer_id.and_then(|end_buffer_id| {
3987 if selection.start.buffer_id != Some(end_buffer_id) {
3988 return None;
3989 }
3990 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3991 let snapshot = buffer.read(cx).snapshot();
3992 self.linked_edit_ranges
3993 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3994 .map(|ranges| (ranges, snapshot, buffer))
3995 })?;
3996 use text::ToOffset as TO;
3997 // find offset from the start of current range to current cursor position
3998 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3999
4000 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4001 let start_difference = start_offset - start_byte_offset;
4002 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4003 let end_difference = end_offset - start_byte_offset;
4004 // Current range has associated linked ranges.
4005 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4006 for range in linked_ranges.iter() {
4007 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4008 let end_offset = start_offset + end_difference;
4009 let start_offset = start_offset + start_difference;
4010 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4011 continue;
4012 }
4013 if self.selections.disjoint_anchor_ranges().any(|s| {
4014 if s.start.buffer_id != selection.start.buffer_id
4015 || s.end.buffer_id != selection.end.buffer_id
4016 {
4017 return false;
4018 }
4019 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4020 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4021 }) {
4022 continue;
4023 }
4024 let start = buffer_snapshot.anchor_after(start_offset);
4025 let end = buffer_snapshot.anchor_after(end_offset);
4026 linked_edits
4027 .entry(buffer.clone())
4028 .or_default()
4029 .push(start..end);
4030 }
4031 Some(linked_edits)
4032 }
4033
4034 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4035 let text: Arc<str> = text.into();
4036
4037 if self.read_only(cx) {
4038 return;
4039 }
4040
4041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4042
4043 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4044 let mut bracket_inserted = false;
4045 let mut edits = Vec::new();
4046 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4047 let mut new_selections = Vec::with_capacity(selections.len());
4048 let mut new_autoclose_regions = Vec::new();
4049 let snapshot = self.buffer.read(cx).read(cx);
4050 let mut clear_linked_edit_ranges = false;
4051
4052 for (selection, autoclose_region) in
4053 self.selections_with_autoclose_regions(selections, &snapshot)
4054 {
4055 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4056 // Determine if the inserted text matches the opening or closing
4057 // bracket of any of this language's bracket pairs.
4058 let mut bracket_pair = None;
4059 let mut is_bracket_pair_start = false;
4060 let mut is_bracket_pair_end = false;
4061 if !text.is_empty() {
4062 let mut bracket_pair_matching_end = None;
4063 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4064 // and they are removing the character that triggered IME popup.
4065 for (pair, enabled) in scope.brackets() {
4066 if !pair.close && !pair.surround {
4067 continue;
4068 }
4069
4070 if enabled && pair.start.ends_with(text.as_ref()) {
4071 let prefix_len = pair.start.len() - text.len();
4072 let preceding_text_matches_prefix = prefix_len == 0
4073 || (selection.start.column >= (prefix_len as u32)
4074 && snapshot.contains_str_at(
4075 Point::new(
4076 selection.start.row,
4077 selection.start.column - (prefix_len as u32),
4078 ),
4079 &pair.start[..prefix_len],
4080 ));
4081 if preceding_text_matches_prefix {
4082 bracket_pair = Some(pair.clone());
4083 is_bracket_pair_start = true;
4084 break;
4085 }
4086 }
4087 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4088 {
4089 // take first bracket pair matching end, but don't break in case a later bracket
4090 // pair matches start
4091 bracket_pair_matching_end = Some(pair.clone());
4092 }
4093 }
4094 if let Some(end) = bracket_pair_matching_end
4095 && bracket_pair.is_none()
4096 {
4097 bracket_pair = Some(end);
4098 is_bracket_pair_end = true;
4099 }
4100 }
4101
4102 if let Some(bracket_pair) = bracket_pair {
4103 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4104 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4105 let auto_surround =
4106 self.use_auto_surround && snapshot_settings.use_auto_surround;
4107 if selection.is_empty() {
4108 if is_bracket_pair_start {
4109 // If the inserted text is a suffix of an opening bracket and the
4110 // selection is preceded by the rest of the opening bracket, then
4111 // insert the closing bracket.
4112 let following_text_allows_autoclose = snapshot
4113 .chars_at(selection.start)
4114 .next()
4115 .is_none_or(|c| scope.should_autoclose_before(c));
4116
4117 let preceding_text_allows_autoclose = selection.start.column == 0
4118 || snapshot
4119 .reversed_chars_at(selection.start)
4120 .next()
4121 .is_none_or(|c| {
4122 bracket_pair.start != bracket_pair.end
4123 || !snapshot
4124 .char_classifier_at(selection.start)
4125 .is_word(c)
4126 });
4127
4128 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4129 && bracket_pair.start.len() == 1
4130 {
4131 let target = bracket_pair.start.chars().next().unwrap();
4132 let current_line_count = snapshot
4133 .reversed_chars_at(selection.start)
4134 .take_while(|&c| c != '\n')
4135 .filter(|&c| c == target)
4136 .count();
4137 current_line_count % 2 == 1
4138 } else {
4139 false
4140 };
4141
4142 if autoclose
4143 && bracket_pair.close
4144 && following_text_allows_autoclose
4145 && preceding_text_allows_autoclose
4146 && !is_closing_quote
4147 {
4148 let anchor = snapshot.anchor_before(selection.end);
4149 new_selections.push((selection.map(|_| anchor), text.len()));
4150 new_autoclose_regions.push((
4151 anchor,
4152 text.len(),
4153 selection.id,
4154 bracket_pair.clone(),
4155 ));
4156 edits.push((
4157 selection.range(),
4158 format!("{}{}", text, bracket_pair.end).into(),
4159 ));
4160 bracket_inserted = true;
4161 continue;
4162 }
4163 }
4164
4165 if let Some(region) = autoclose_region {
4166 // If the selection is followed by an auto-inserted closing bracket,
4167 // then don't insert that closing bracket again; just move the selection
4168 // past the closing bracket.
4169 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4170 && text.as_ref() == region.pair.end.as_str()
4171 && snapshot.contains_str_at(region.range.end, text.as_ref());
4172 if should_skip {
4173 let anchor = snapshot.anchor_after(selection.end);
4174 new_selections
4175 .push((selection.map(|_| anchor), region.pair.end.len()));
4176 continue;
4177 }
4178 }
4179
4180 let always_treat_brackets_as_autoclosed = snapshot
4181 .language_settings_at(selection.start, cx)
4182 .always_treat_brackets_as_autoclosed;
4183 if always_treat_brackets_as_autoclosed
4184 && is_bracket_pair_end
4185 && snapshot.contains_str_at(selection.end, text.as_ref())
4186 {
4187 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4188 // and the inserted text is a closing bracket and the selection is followed
4189 // by the closing bracket then move the selection past the closing bracket.
4190 let anchor = snapshot.anchor_after(selection.end);
4191 new_selections.push((selection.map(|_| anchor), text.len()));
4192 continue;
4193 }
4194 }
4195 // If an opening bracket is 1 character long and is typed while
4196 // text is selected, then surround that text with the bracket pair.
4197 else if auto_surround
4198 && bracket_pair.surround
4199 && is_bracket_pair_start
4200 && bracket_pair.start.chars().count() == 1
4201 {
4202 edits.push((selection.start..selection.start, text.clone()));
4203 edits.push((
4204 selection.end..selection.end,
4205 bracket_pair.end.as_str().into(),
4206 ));
4207 bracket_inserted = true;
4208 new_selections.push((
4209 Selection {
4210 id: selection.id,
4211 start: snapshot.anchor_after(selection.start),
4212 end: snapshot.anchor_before(selection.end),
4213 reversed: selection.reversed,
4214 goal: selection.goal,
4215 },
4216 0,
4217 ));
4218 continue;
4219 }
4220 }
4221 }
4222
4223 if self.auto_replace_emoji_shortcode
4224 && selection.is_empty()
4225 && text.as_ref().ends_with(':')
4226 && let Some(possible_emoji_short_code) =
4227 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4228 && !possible_emoji_short_code.is_empty()
4229 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4230 {
4231 let emoji_shortcode_start = Point::new(
4232 selection.start.row,
4233 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4234 );
4235
4236 // Remove shortcode from buffer
4237 edits.push((
4238 emoji_shortcode_start..selection.start,
4239 "".to_string().into(),
4240 ));
4241 new_selections.push((
4242 Selection {
4243 id: selection.id,
4244 start: snapshot.anchor_after(emoji_shortcode_start),
4245 end: snapshot.anchor_before(selection.start),
4246 reversed: selection.reversed,
4247 goal: selection.goal,
4248 },
4249 0,
4250 ));
4251
4252 // Insert emoji
4253 let selection_start_anchor = snapshot.anchor_after(selection.start);
4254 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4255 edits.push((selection.start..selection.end, emoji.to_string().into()));
4256
4257 continue;
4258 }
4259
4260 // If not handling any auto-close operation, then just replace the selected
4261 // text with the given input and move the selection to the end of the
4262 // newly inserted text.
4263 let anchor = snapshot.anchor_after(selection.end);
4264 if !self.linked_edit_ranges.is_empty() {
4265 let start_anchor = snapshot.anchor_before(selection.start);
4266
4267 let is_word_char = text.chars().next().is_none_or(|char| {
4268 let classifier = snapshot
4269 .char_classifier_at(start_anchor.to_offset(&snapshot))
4270 .scope_context(Some(CharScopeContext::LinkedEdit));
4271 classifier.is_word(char)
4272 });
4273
4274 if is_word_char {
4275 if let Some(ranges) = self
4276 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4277 {
4278 for (buffer, edits) in ranges {
4279 linked_edits
4280 .entry(buffer.clone())
4281 .or_default()
4282 .extend(edits.into_iter().map(|range| (range, text.clone())));
4283 }
4284 }
4285 } else {
4286 clear_linked_edit_ranges = true;
4287 }
4288 }
4289
4290 new_selections.push((selection.map(|_| anchor), 0));
4291 edits.push((selection.start..selection.end, text.clone()));
4292 }
4293
4294 drop(snapshot);
4295
4296 self.transact(window, cx, |this, window, cx| {
4297 if clear_linked_edit_ranges {
4298 this.linked_edit_ranges.clear();
4299 }
4300 let initial_buffer_versions =
4301 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4302
4303 this.buffer.update(cx, |buffer, cx| {
4304 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4305 });
4306 for (buffer, edits) in linked_edits {
4307 buffer.update(cx, |buffer, cx| {
4308 let snapshot = buffer.snapshot();
4309 let edits = edits
4310 .into_iter()
4311 .map(|(range, text)| {
4312 use text::ToPoint as TP;
4313 let end_point = TP::to_point(&range.end, &snapshot);
4314 let start_point = TP::to_point(&range.start, &snapshot);
4315 (start_point..end_point, text)
4316 })
4317 .sorted_by_key(|(range, _)| range.start);
4318 buffer.edit(edits, None, cx);
4319 })
4320 }
4321 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4322 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4323 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4324 let new_selections =
4325 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4326 .zip(new_selection_deltas)
4327 .map(|(selection, delta)| Selection {
4328 id: selection.id,
4329 start: selection.start + delta,
4330 end: selection.end + delta,
4331 reversed: selection.reversed,
4332 goal: SelectionGoal::None,
4333 })
4334 .collect::<Vec<_>>();
4335
4336 let mut i = 0;
4337 for (position, delta, selection_id, pair) in new_autoclose_regions {
4338 let position = position.to_offset(map.buffer_snapshot()) + delta;
4339 let start = map.buffer_snapshot().anchor_before(position);
4340 let end = map.buffer_snapshot().anchor_after(position);
4341 while let Some(existing_state) = this.autoclose_regions.get(i) {
4342 match existing_state
4343 .range
4344 .start
4345 .cmp(&start, map.buffer_snapshot())
4346 {
4347 Ordering::Less => i += 1,
4348 Ordering::Greater => break,
4349 Ordering::Equal => {
4350 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4351 Ordering::Less => i += 1,
4352 Ordering::Equal => break,
4353 Ordering::Greater => break,
4354 }
4355 }
4356 }
4357 }
4358 this.autoclose_regions.insert(
4359 i,
4360 AutocloseRegion {
4361 selection_id,
4362 range: start..end,
4363 pair,
4364 },
4365 );
4366 }
4367
4368 let had_active_edit_prediction = this.has_active_edit_prediction();
4369 this.change_selections(
4370 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4371 window,
4372 cx,
4373 |s| s.select(new_selections),
4374 );
4375
4376 if !bracket_inserted
4377 && let Some(on_type_format_task) =
4378 this.trigger_on_type_formatting(text.to_string(), window, cx)
4379 {
4380 on_type_format_task.detach_and_log_err(cx);
4381 }
4382
4383 let editor_settings = EditorSettings::get_global(cx);
4384 if bracket_inserted
4385 && (editor_settings.auto_signature_help
4386 || editor_settings.show_signature_help_after_edits)
4387 {
4388 this.show_signature_help(&ShowSignatureHelp, window, cx);
4389 }
4390
4391 let trigger_in_words =
4392 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4393 if this.hard_wrap.is_some() {
4394 let latest: Range<Point> = this.selections.newest(&map).range();
4395 if latest.is_empty()
4396 && this
4397 .buffer()
4398 .read(cx)
4399 .snapshot(cx)
4400 .line_len(MultiBufferRow(latest.start.row))
4401 == latest.start.column
4402 {
4403 this.rewrap_impl(
4404 RewrapOptions {
4405 override_language_settings: true,
4406 preserve_existing_whitespace: true,
4407 },
4408 cx,
4409 )
4410 }
4411 }
4412 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4413 refresh_linked_ranges(this, window, cx);
4414 this.refresh_edit_prediction(true, false, window, cx);
4415 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4416 });
4417 }
4418
4419 fn find_possible_emoji_shortcode_at_position(
4420 snapshot: &MultiBufferSnapshot,
4421 position: Point,
4422 ) -> Option<String> {
4423 let mut chars = Vec::new();
4424 let mut found_colon = false;
4425 for char in snapshot.reversed_chars_at(position).take(100) {
4426 // Found a possible emoji shortcode in the middle of the buffer
4427 if found_colon {
4428 if char.is_whitespace() {
4429 chars.reverse();
4430 return Some(chars.iter().collect());
4431 }
4432 // If the previous character is not a whitespace, we are in the middle of a word
4433 // and we only want to complete the shortcode if the word is made up of other emojis
4434 let mut containing_word = String::new();
4435 for ch in snapshot
4436 .reversed_chars_at(position)
4437 .skip(chars.len() + 1)
4438 .take(100)
4439 {
4440 if ch.is_whitespace() {
4441 break;
4442 }
4443 containing_word.push(ch);
4444 }
4445 let containing_word = containing_word.chars().rev().collect::<String>();
4446 if util::word_consists_of_emojis(containing_word.as_str()) {
4447 chars.reverse();
4448 return Some(chars.iter().collect());
4449 }
4450 }
4451
4452 if char.is_whitespace() || !char.is_ascii() {
4453 return None;
4454 }
4455 if char == ':' {
4456 found_colon = true;
4457 } else {
4458 chars.push(char);
4459 }
4460 }
4461 // Found a possible emoji shortcode at the beginning of the buffer
4462 chars.reverse();
4463 Some(chars.iter().collect())
4464 }
4465
4466 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4467 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4468 self.transact(window, cx, |this, window, cx| {
4469 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4470 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4471 let multi_buffer = this.buffer.read(cx);
4472 let buffer = multi_buffer.snapshot(cx);
4473 selections
4474 .iter()
4475 .map(|selection| {
4476 let start_point = selection.start.to_point(&buffer);
4477 let mut existing_indent =
4478 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4479 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4480 let start = selection.start;
4481 let end = selection.end;
4482 let selection_is_empty = start == end;
4483 let language_scope = buffer.language_scope_at(start);
4484 let (
4485 comment_delimiter,
4486 doc_delimiter,
4487 insert_extra_newline,
4488 indent_on_newline,
4489 indent_on_extra_newline,
4490 ) = if let Some(language) = &language_scope {
4491 let mut insert_extra_newline =
4492 insert_extra_newline_brackets(&buffer, start..end, language)
4493 || insert_extra_newline_tree_sitter(&buffer, start..end);
4494
4495 // Comment extension on newline is allowed only for cursor selections
4496 let comment_delimiter = maybe!({
4497 if !selection_is_empty {
4498 return None;
4499 }
4500
4501 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4502 return None;
4503 }
4504
4505 let delimiters = language.line_comment_prefixes();
4506 let max_len_of_delimiter =
4507 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4508 let (snapshot, range) =
4509 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4510
4511 let num_of_whitespaces = snapshot
4512 .chars_for_range(range.clone())
4513 .take_while(|c| c.is_whitespace())
4514 .count();
4515 let comment_candidate = snapshot
4516 .chars_for_range(range.clone())
4517 .skip(num_of_whitespaces)
4518 .take(max_len_of_delimiter)
4519 .collect::<String>();
4520 let (delimiter, trimmed_len) = delimiters
4521 .iter()
4522 .filter_map(|delimiter| {
4523 let prefix = delimiter.trim_end();
4524 if comment_candidate.starts_with(prefix) {
4525 Some((delimiter, prefix.len()))
4526 } else {
4527 None
4528 }
4529 })
4530 .max_by_key(|(_, len)| *len)?;
4531
4532 if let Some(BlockCommentConfig {
4533 start: block_start, ..
4534 }) = language.block_comment()
4535 {
4536 let block_start_trimmed = block_start.trim_end();
4537 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4538 let line_content = snapshot
4539 .chars_for_range(range)
4540 .skip(num_of_whitespaces)
4541 .take(block_start_trimmed.len())
4542 .collect::<String>();
4543
4544 if line_content.starts_with(block_start_trimmed) {
4545 return None;
4546 }
4547 }
4548 }
4549
4550 let cursor_is_placed_after_comment_marker =
4551 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4552 if cursor_is_placed_after_comment_marker {
4553 Some(delimiter.clone())
4554 } else {
4555 None
4556 }
4557 });
4558
4559 let mut indent_on_newline = IndentSize::spaces(0);
4560 let mut indent_on_extra_newline = IndentSize::spaces(0);
4561
4562 let doc_delimiter = maybe!({
4563 if !selection_is_empty {
4564 return None;
4565 }
4566
4567 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4568 return None;
4569 }
4570
4571 let BlockCommentConfig {
4572 start: start_tag,
4573 end: end_tag,
4574 prefix: delimiter,
4575 tab_size: len,
4576 } = language.documentation_comment()?;
4577 let is_within_block_comment = buffer
4578 .language_scope_at(start_point)
4579 .is_some_and(|scope| scope.override_name() == Some("comment"));
4580 if !is_within_block_comment {
4581 return None;
4582 }
4583
4584 let (snapshot, range) =
4585 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4586
4587 let num_of_whitespaces = snapshot
4588 .chars_for_range(range.clone())
4589 .take_while(|c| c.is_whitespace())
4590 .count();
4591
4592 // 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.
4593 let column = start_point.column;
4594 let cursor_is_after_start_tag = {
4595 let start_tag_len = start_tag.len();
4596 let start_tag_line = snapshot
4597 .chars_for_range(range.clone())
4598 .skip(num_of_whitespaces)
4599 .take(start_tag_len)
4600 .collect::<String>();
4601 if start_tag_line.starts_with(start_tag.as_ref()) {
4602 num_of_whitespaces + start_tag_len <= column as usize
4603 } else {
4604 false
4605 }
4606 };
4607
4608 let cursor_is_after_delimiter = {
4609 let delimiter_trim = delimiter.trim_end();
4610 let delimiter_line = snapshot
4611 .chars_for_range(range.clone())
4612 .skip(num_of_whitespaces)
4613 .take(delimiter_trim.len())
4614 .collect::<String>();
4615 if delimiter_line.starts_with(delimiter_trim) {
4616 num_of_whitespaces + delimiter_trim.len() <= column as usize
4617 } else {
4618 false
4619 }
4620 };
4621
4622 let cursor_is_before_end_tag_if_exists = {
4623 let mut char_position = 0u32;
4624 let mut end_tag_offset = None;
4625
4626 'outer: for chunk in snapshot.text_for_range(range) {
4627 if let Some(byte_pos) = chunk.find(&**end_tag) {
4628 let chars_before_match =
4629 chunk[..byte_pos].chars().count() as u32;
4630 end_tag_offset =
4631 Some(char_position + chars_before_match);
4632 break 'outer;
4633 }
4634 char_position += chunk.chars().count() as u32;
4635 }
4636
4637 if let Some(end_tag_offset) = end_tag_offset {
4638 let cursor_is_before_end_tag = column <= end_tag_offset;
4639 if cursor_is_after_start_tag {
4640 if cursor_is_before_end_tag {
4641 insert_extra_newline = true;
4642 }
4643 let cursor_is_at_start_of_end_tag =
4644 column == end_tag_offset;
4645 if cursor_is_at_start_of_end_tag {
4646 indent_on_extra_newline.len = *len;
4647 }
4648 }
4649 cursor_is_before_end_tag
4650 } else {
4651 true
4652 }
4653 };
4654
4655 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4656 && cursor_is_before_end_tag_if_exists
4657 {
4658 if cursor_is_after_start_tag {
4659 indent_on_newline.len = *len;
4660 }
4661 Some(delimiter.clone())
4662 } else {
4663 None
4664 }
4665 });
4666
4667 (
4668 comment_delimiter,
4669 doc_delimiter,
4670 insert_extra_newline,
4671 indent_on_newline,
4672 indent_on_extra_newline,
4673 )
4674 } else {
4675 (
4676 None,
4677 None,
4678 false,
4679 IndentSize::default(),
4680 IndentSize::default(),
4681 )
4682 };
4683
4684 let prevent_auto_indent = doc_delimiter.is_some();
4685 let delimiter = comment_delimiter.or(doc_delimiter);
4686
4687 let capacity_for_delimiter =
4688 delimiter.as_deref().map(str::len).unwrap_or_default();
4689 let mut new_text = String::with_capacity(
4690 1 + capacity_for_delimiter
4691 + existing_indent.len as usize
4692 + indent_on_newline.len as usize
4693 + indent_on_extra_newline.len as usize,
4694 );
4695 new_text.push('\n');
4696 new_text.extend(existing_indent.chars());
4697 new_text.extend(indent_on_newline.chars());
4698
4699 if let Some(delimiter) = &delimiter {
4700 new_text.push_str(delimiter);
4701 }
4702
4703 if insert_extra_newline {
4704 new_text.push('\n');
4705 new_text.extend(existing_indent.chars());
4706 new_text.extend(indent_on_extra_newline.chars());
4707 }
4708
4709 let anchor = buffer.anchor_after(end);
4710 let new_selection = selection.map(|_| anchor);
4711 (
4712 ((start..end, new_text), prevent_auto_indent),
4713 (insert_extra_newline, new_selection),
4714 )
4715 })
4716 .unzip()
4717 };
4718
4719 let mut auto_indent_edits = Vec::new();
4720 let mut edits = Vec::new();
4721 for (edit, prevent_auto_indent) in edits_with_flags {
4722 if prevent_auto_indent {
4723 edits.push(edit);
4724 } else {
4725 auto_indent_edits.push(edit);
4726 }
4727 }
4728 if !edits.is_empty() {
4729 this.edit(edits, cx);
4730 }
4731 if !auto_indent_edits.is_empty() {
4732 this.edit_with_autoindent(auto_indent_edits, cx);
4733 }
4734
4735 let buffer = this.buffer.read(cx).snapshot(cx);
4736 let new_selections = selection_info
4737 .into_iter()
4738 .map(|(extra_newline_inserted, new_selection)| {
4739 let mut cursor = new_selection.end.to_point(&buffer);
4740 if extra_newline_inserted {
4741 cursor.row -= 1;
4742 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4743 }
4744 new_selection.map(|_| cursor)
4745 })
4746 .collect();
4747
4748 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4749 this.refresh_edit_prediction(true, false, window, cx);
4750 });
4751 }
4752
4753 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4755
4756 let buffer = self.buffer.read(cx);
4757 let snapshot = buffer.snapshot(cx);
4758
4759 let mut edits = Vec::new();
4760 let mut rows = Vec::new();
4761
4762 for (rows_inserted, selection) in self
4763 .selections
4764 .all_adjusted(&self.display_snapshot(cx))
4765 .into_iter()
4766 .enumerate()
4767 {
4768 let cursor = selection.head();
4769 let row = cursor.row;
4770
4771 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4772
4773 let newline = "\n".to_string();
4774 edits.push((start_of_line..start_of_line, newline));
4775
4776 rows.push(row + rows_inserted as u32);
4777 }
4778
4779 self.transact(window, cx, |editor, window, cx| {
4780 editor.edit(edits, cx);
4781
4782 editor.change_selections(Default::default(), window, cx, |s| {
4783 let mut index = 0;
4784 s.move_cursors_with(|map, _, _| {
4785 let row = rows[index];
4786 index += 1;
4787
4788 let point = Point::new(row, 0);
4789 let boundary = map.next_line_boundary(point).1;
4790 let clipped = map.clip_point(boundary, Bias::Left);
4791
4792 (clipped, SelectionGoal::None)
4793 });
4794 });
4795
4796 let mut indent_edits = Vec::new();
4797 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4798 for row in rows {
4799 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4800 for (row, indent) in indents {
4801 if indent.len == 0 {
4802 continue;
4803 }
4804
4805 let text = match indent.kind {
4806 IndentKind::Space => " ".repeat(indent.len as usize),
4807 IndentKind::Tab => "\t".repeat(indent.len as usize),
4808 };
4809 let point = Point::new(row.0, 0);
4810 indent_edits.push((point..point, text));
4811 }
4812 }
4813 editor.edit(indent_edits, cx);
4814 });
4815 }
4816
4817 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4819
4820 let buffer = self.buffer.read(cx);
4821 let snapshot = buffer.snapshot(cx);
4822
4823 let mut edits = Vec::new();
4824 let mut rows = Vec::new();
4825 let mut rows_inserted = 0;
4826
4827 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4828 let cursor = selection.head();
4829 let row = cursor.row;
4830
4831 let point = Point::new(row + 1, 0);
4832 let start_of_line = snapshot.clip_point(point, Bias::Left);
4833
4834 let newline = "\n".to_string();
4835 edits.push((start_of_line..start_of_line, newline));
4836
4837 rows_inserted += 1;
4838 rows.push(row + rows_inserted);
4839 }
4840
4841 self.transact(window, cx, |editor, window, cx| {
4842 editor.edit(edits, cx);
4843
4844 editor.change_selections(Default::default(), window, cx, |s| {
4845 let mut index = 0;
4846 s.move_cursors_with(|map, _, _| {
4847 let row = rows[index];
4848 index += 1;
4849
4850 let point = Point::new(row, 0);
4851 let boundary = map.next_line_boundary(point).1;
4852 let clipped = map.clip_point(boundary, Bias::Left);
4853
4854 (clipped, SelectionGoal::None)
4855 });
4856 });
4857
4858 let mut indent_edits = Vec::new();
4859 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4860 for row in rows {
4861 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4862 for (row, indent) in indents {
4863 if indent.len == 0 {
4864 continue;
4865 }
4866
4867 let text = match indent.kind {
4868 IndentKind::Space => " ".repeat(indent.len as usize),
4869 IndentKind::Tab => "\t".repeat(indent.len as usize),
4870 };
4871 let point = Point::new(row.0, 0);
4872 indent_edits.push((point..point, text));
4873 }
4874 }
4875 editor.edit(indent_edits, cx);
4876 });
4877 }
4878
4879 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4880 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4881 original_indent_columns: Vec::new(),
4882 });
4883 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4884 }
4885
4886 fn insert_with_autoindent_mode(
4887 &mut self,
4888 text: &str,
4889 autoindent_mode: Option<AutoindentMode>,
4890 window: &mut Window,
4891 cx: &mut Context<Self>,
4892 ) {
4893 if self.read_only(cx) {
4894 return;
4895 }
4896
4897 let text: Arc<str> = text.into();
4898 self.transact(window, cx, |this, window, cx| {
4899 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4900 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4901 let anchors = {
4902 let snapshot = buffer.read(cx);
4903 old_selections
4904 .iter()
4905 .map(|s| {
4906 let anchor = snapshot.anchor_after(s.head());
4907 s.map(|_| anchor)
4908 })
4909 .collect::<Vec<_>>()
4910 };
4911 buffer.edit(
4912 old_selections
4913 .iter()
4914 .map(|s| (s.start..s.end, text.clone())),
4915 autoindent_mode,
4916 cx,
4917 );
4918 anchors
4919 });
4920
4921 this.change_selections(Default::default(), window, cx, |s| {
4922 s.select_anchors(selection_anchors);
4923 });
4924
4925 cx.notify();
4926 });
4927 }
4928
4929 fn trigger_completion_on_input(
4930 &mut self,
4931 text: &str,
4932 trigger_in_words: bool,
4933 window: &mut Window,
4934 cx: &mut Context<Self>,
4935 ) {
4936 let completions_source = self
4937 .context_menu
4938 .borrow()
4939 .as_ref()
4940 .and_then(|menu| match menu {
4941 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4942 CodeContextMenu::CodeActions(_) => None,
4943 });
4944
4945 match completions_source {
4946 Some(CompletionsMenuSource::Words { .. }) => {
4947 self.open_or_update_completions_menu(
4948 Some(CompletionsMenuSource::Words {
4949 ignore_threshold: false,
4950 }),
4951 None,
4952 window,
4953 cx,
4954 );
4955 }
4956 Some(CompletionsMenuSource::Normal)
4957 | Some(CompletionsMenuSource::SnippetChoices)
4958 | None
4959 if self.is_completion_trigger(
4960 text,
4961 trigger_in_words,
4962 completions_source.is_some(),
4963 cx,
4964 ) =>
4965 {
4966 self.show_completions(
4967 &ShowCompletions {
4968 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4969 },
4970 window,
4971 cx,
4972 )
4973 }
4974 _ => {
4975 self.hide_context_menu(window, cx);
4976 }
4977 }
4978 }
4979
4980 fn is_completion_trigger(
4981 &self,
4982 text: &str,
4983 trigger_in_words: bool,
4984 menu_is_open: bool,
4985 cx: &mut Context<Self>,
4986 ) -> bool {
4987 let position = self.selections.newest_anchor().head();
4988 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4989 return false;
4990 };
4991
4992 if let Some(completion_provider) = &self.completion_provider {
4993 completion_provider.is_completion_trigger(
4994 &buffer,
4995 position.text_anchor,
4996 text,
4997 trigger_in_words,
4998 menu_is_open,
4999 cx,
5000 )
5001 } else {
5002 false
5003 }
5004 }
5005
5006 /// If any empty selections is touching the start of its innermost containing autoclose
5007 /// region, expand it to select the brackets.
5008 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5009 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5010 let buffer = self.buffer.read(cx).read(cx);
5011 let new_selections = self
5012 .selections_with_autoclose_regions(selections, &buffer)
5013 .map(|(mut selection, region)| {
5014 if !selection.is_empty() {
5015 return selection;
5016 }
5017
5018 if let Some(region) = region {
5019 let mut range = region.range.to_offset(&buffer);
5020 if selection.start == range.start && range.start >= region.pair.start.len() {
5021 range.start -= region.pair.start.len();
5022 if buffer.contains_str_at(range.start, ®ion.pair.start)
5023 && buffer.contains_str_at(range.end, ®ion.pair.end)
5024 {
5025 range.end += region.pair.end.len();
5026 selection.start = range.start;
5027 selection.end = range.end;
5028
5029 return selection;
5030 }
5031 }
5032 }
5033
5034 let always_treat_brackets_as_autoclosed = buffer
5035 .language_settings_at(selection.start, cx)
5036 .always_treat_brackets_as_autoclosed;
5037
5038 if !always_treat_brackets_as_autoclosed {
5039 return selection;
5040 }
5041
5042 if let Some(scope) = buffer.language_scope_at(selection.start) {
5043 for (pair, enabled) in scope.brackets() {
5044 if !enabled || !pair.close {
5045 continue;
5046 }
5047
5048 if buffer.contains_str_at(selection.start, &pair.end) {
5049 let pair_start_len = pair.start.len();
5050 if buffer.contains_str_at(
5051 selection.start.saturating_sub(pair_start_len),
5052 &pair.start,
5053 ) {
5054 selection.start -= pair_start_len;
5055 selection.end += pair.end.len();
5056
5057 return selection;
5058 }
5059 }
5060 }
5061 }
5062
5063 selection
5064 })
5065 .collect();
5066
5067 drop(buffer);
5068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5069 selections.select(new_selections)
5070 });
5071 }
5072
5073 /// Iterate the given selections, and for each one, find the smallest surrounding
5074 /// autoclose region. This uses the ordering of the selections and the autoclose
5075 /// regions to avoid repeated comparisons.
5076 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5077 &'a self,
5078 selections: impl IntoIterator<Item = Selection<D>>,
5079 buffer: &'a MultiBufferSnapshot,
5080 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5081 let mut i = 0;
5082 let mut regions = self.autoclose_regions.as_slice();
5083 selections.into_iter().map(move |selection| {
5084 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5085
5086 let mut enclosing = None;
5087 while let Some(pair_state) = regions.get(i) {
5088 if pair_state.range.end.to_offset(buffer) < range.start {
5089 regions = ®ions[i + 1..];
5090 i = 0;
5091 } else if pair_state.range.start.to_offset(buffer) > range.end {
5092 break;
5093 } else {
5094 if pair_state.selection_id == selection.id {
5095 enclosing = Some(pair_state);
5096 }
5097 i += 1;
5098 }
5099 }
5100
5101 (selection, enclosing)
5102 })
5103 }
5104
5105 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5106 fn invalidate_autoclose_regions(
5107 &mut self,
5108 mut selections: &[Selection<Anchor>],
5109 buffer: &MultiBufferSnapshot,
5110 ) {
5111 self.autoclose_regions.retain(|state| {
5112 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5113 return false;
5114 }
5115
5116 let mut i = 0;
5117 while let Some(selection) = selections.get(i) {
5118 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5119 selections = &selections[1..];
5120 continue;
5121 }
5122 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5123 break;
5124 }
5125 if selection.id == state.selection_id {
5126 return true;
5127 } else {
5128 i += 1;
5129 }
5130 }
5131 false
5132 });
5133 }
5134
5135 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5136 let offset = position.to_offset(buffer);
5137 let (word_range, kind) =
5138 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5139 if offset > word_range.start && kind == Some(CharKind::Word) {
5140 Some(
5141 buffer
5142 .text_for_range(word_range.start..offset)
5143 .collect::<String>(),
5144 )
5145 } else {
5146 None
5147 }
5148 }
5149
5150 pub fn visible_excerpts(
5151 &self,
5152 cx: &mut Context<Editor>,
5153 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5154 let Some(project) = self.project() else {
5155 return HashMap::default();
5156 };
5157 let project = project.read(cx);
5158 let multi_buffer = self.buffer().read(cx);
5159 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5160 let multi_buffer_visible_start = self
5161 .scroll_manager
5162 .anchor()
5163 .anchor
5164 .to_point(&multi_buffer_snapshot);
5165 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5166 multi_buffer_visible_start
5167 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5168 Bias::Left,
5169 );
5170 multi_buffer_snapshot
5171 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5172 .into_iter()
5173 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5174 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5175 let buffer_file = project::File::from_dyn(buffer.file())?;
5176 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5177 let worktree_entry = buffer_worktree
5178 .read(cx)
5179 .entry_for_id(buffer_file.project_entry_id()?)?;
5180 if worktree_entry.is_ignored {
5181 None
5182 } else {
5183 Some((
5184 excerpt_id,
5185 (
5186 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5187 buffer.version().clone(),
5188 excerpt_visible_range,
5189 ),
5190 ))
5191 }
5192 })
5193 .collect()
5194 }
5195
5196 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5197 TextLayoutDetails {
5198 text_system: window.text_system().clone(),
5199 editor_style: self.style.clone().unwrap(),
5200 rem_size: window.rem_size(),
5201 scroll_anchor: self.scroll_manager.anchor(),
5202 visible_rows: self.visible_line_count(),
5203 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5204 }
5205 }
5206
5207 fn trigger_on_type_formatting(
5208 &self,
5209 input: String,
5210 window: &mut Window,
5211 cx: &mut Context<Self>,
5212 ) -> Option<Task<Result<()>>> {
5213 if input.len() != 1 {
5214 return None;
5215 }
5216
5217 let project = self.project()?;
5218 let position = self.selections.newest_anchor().head();
5219 let (buffer, buffer_position) = self
5220 .buffer
5221 .read(cx)
5222 .text_anchor_for_position(position, cx)?;
5223
5224 let settings = language_settings::language_settings(
5225 buffer
5226 .read(cx)
5227 .language_at(buffer_position)
5228 .map(|l| l.name()),
5229 buffer.read(cx).file(),
5230 cx,
5231 );
5232 if !settings.use_on_type_format {
5233 return None;
5234 }
5235
5236 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5237 // hence we do LSP request & edit on host side only — add formats to host's history.
5238 let push_to_lsp_host_history = true;
5239 // If this is not the host, append its history with new edits.
5240 let push_to_client_history = project.read(cx).is_via_collab();
5241
5242 let on_type_formatting = project.update(cx, |project, cx| {
5243 project.on_type_format(
5244 buffer.clone(),
5245 buffer_position,
5246 input,
5247 push_to_lsp_host_history,
5248 cx,
5249 )
5250 });
5251 Some(cx.spawn_in(window, async move |editor, cx| {
5252 if let Some(transaction) = on_type_formatting.await? {
5253 if push_to_client_history {
5254 buffer
5255 .update(cx, |buffer, _| {
5256 buffer.push_transaction(transaction, Instant::now());
5257 buffer.finalize_last_transaction();
5258 })
5259 .ok();
5260 }
5261 editor.update(cx, |editor, cx| {
5262 editor.refresh_document_highlights(cx);
5263 })?;
5264 }
5265 Ok(())
5266 }))
5267 }
5268
5269 pub fn show_word_completions(
5270 &mut self,
5271 _: &ShowWordCompletions,
5272 window: &mut Window,
5273 cx: &mut Context<Self>,
5274 ) {
5275 self.open_or_update_completions_menu(
5276 Some(CompletionsMenuSource::Words {
5277 ignore_threshold: true,
5278 }),
5279 None,
5280 window,
5281 cx,
5282 );
5283 }
5284
5285 pub fn show_completions(
5286 &mut self,
5287 options: &ShowCompletions,
5288 window: &mut Window,
5289 cx: &mut Context<Self>,
5290 ) {
5291 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5292 }
5293
5294 fn open_or_update_completions_menu(
5295 &mut self,
5296 requested_source: Option<CompletionsMenuSource>,
5297 trigger: Option<&str>,
5298 window: &mut Window,
5299 cx: &mut Context<Self>,
5300 ) {
5301 if self.pending_rename.is_some() {
5302 return;
5303 }
5304
5305 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5306
5307 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5308 // inserted and selected. To handle that case, the start of the selection is used so that
5309 // the menu starts with all choices.
5310 let position = self
5311 .selections
5312 .newest_anchor()
5313 .start
5314 .bias_right(&multibuffer_snapshot);
5315 if position.diff_base_anchor.is_some() {
5316 return;
5317 }
5318 let buffer_position = multibuffer_snapshot.anchor_before(position);
5319 let Some(buffer) = buffer_position
5320 .buffer_id
5321 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5322 else {
5323 return;
5324 };
5325 let buffer_snapshot = buffer.read(cx).snapshot();
5326
5327 let query: Option<Arc<String>> =
5328 Self::completion_query(&multibuffer_snapshot, buffer_position)
5329 .map(|query| query.into());
5330
5331 drop(multibuffer_snapshot);
5332
5333 // Hide the current completions menu when query is empty. Without this, cached
5334 // completions from before the trigger char may be reused (#32774).
5335 if query.is_none() {
5336 let menu_is_open = matches!(
5337 self.context_menu.borrow().as_ref(),
5338 Some(CodeContextMenu::Completions(_))
5339 );
5340 if menu_is_open {
5341 self.hide_context_menu(window, cx);
5342 }
5343 }
5344
5345 let mut ignore_word_threshold = false;
5346 let provider = match requested_source {
5347 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5348 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5349 ignore_word_threshold = ignore_threshold;
5350 None
5351 }
5352 Some(CompletionsMenuSource::SnippetChoices) => {
5353 log::error!("bug: SnippetChoices requested_source is not handled");
5354 None
5355 }
5356 };
5357
5358 let sort_completions = provider
5359 .as_ref()
5360 .is_some_and(|provider| provider.sort_completions());
5361
5362 let filter_completions = provider
5363 .as_ref()
5364 .is_none_or(|provider| provider.filter_completions());
5365
5366 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5367 if filter_completions {
5368 menu.filter(query.clone(), provider.clone(), window, cx);
5369 }
5370 // When `is_incomplete` is false, no need to re-query completions when the current query
5371 // is a suffix of the initial query.
5372 if !menu.is_incomplete {
5373 // If the new query is a suffix of the old query (typing more characters) and
5374 // the previous result was complete, the existing completions can be filtered.
5375 //
5376 // Note that this is always true for snippet completions.
5377 let query_matches = match (&menu.initial_query, &query) {
5378 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5379 (None, _) => true,
5380 _ => false,
5381 };
5382 if query_matches {
5383 let position_matches = if menu.initial_position == position {
5384 true
5385 } else {
5386 let snapshot = self.buffer.read(cx).read(cx);
5387 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5388 };
5389 if position_matches {
5390 return;
5391 }
5392 }
5393 }
5394 };
5395
5396 let trigger_kind = match trigger {
5397 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5398 CompletionTriggerKind::TRIGGER_CHARACTER
5399 }
5400 _ => CompletionTriggerKind::INVOKED,
5401 };
5402 let completion_context = CompletionContext {
5403 trigger_character: trigger.and_then(|trigger| {
5404 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5405 Some(String::from(trigger))
5406 } else {
5407 None
5408 }
5409 }),
5410 trigger_kind,
5411 };
5412
5413 let Anchor {
5414 excerpt_id: buffer_excerpt_id,
5415 text_anchor: buffer_position,
5416 ..
5417 } = buffer_position;
5418
5419 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5420 buffer_snapshot.surrounding_word(buffer_position, None)
5421 {
5422 let word_to_exclude = buffer_snapshot
5423 .text_for_range(word_range.clone())
5424 .collect::<String>();
5425 (
5426 buffer_snapshot.anchor_before(word_range.start)
5427 ..buffer_snapshot.anchor_after(buffer_position),
5428 Some(word_to_exclude),
5429 )
5430 } else {
5431 (buffer_position..buffer_position, None)
5432 };
5433
5434 let language = buffer_snapshot
5435 .language_at(buffer_position)
5436 .map(|language| language.name());
5437
5438 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5439 .completions
5440 .clone();
5441
5442 let show_completion_documentation = buffer_snapshot
5443 .settings_at(buffer_position, cx)
5444 .show_completion_documentation;
5445
5446 // The document can be large, so stay in reasonable bounds when searching for words,
5447 // otherwise completion pop-up might be slow to appear.
5448 const WORD_LOOKUP_ROWS: u32 = 5_000;
5449 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5450 let min_word_search = buffer_snapshot.clip_point(
5451 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5452 Bias::Left,
5453 );
5454 let max_word_search = buffer_snapshot.clip_point(
5455 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5456 Bias::Right,
5457 );
5458 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5459 ..buffer_snapshot.point_to_offset(max_word_search);
5460
5461 let skip_digits = query
5462 .as_ref()
5463 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5464
5465 let omit_word_completions = !self.word_completions_enabled
5466 || (!ignore_word_threshold
5467 && match &query {
5468 Some(query) => query.chars().count() < completion_settings.words_min_length,
5469 None => completion_settings.words_min_length != 0,
5470 });
5471
5472 let (mut words, provider_responses) = match &provider {
5473 Some(provider) => {
5474 let provider_responses = provider.completions(
5475 buffer_excerpt_id,
5476 &buffer,
5477 buffer_position,
5478 completion_context,
5479 window,
5480 cx,
5481 );
5482
5483 let words = match (omit_word_completions, completion_settings.words) {
5484 (true, _) | (_, WordsCompletionMode::Disabled) => {
5485 Task::ready(BTreeMap::default())
5486 }
5487 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5488 .background_spawn(async move {
5489 buffer_snapshot.words_in_range(WordsQuery {
5490 fuzzy_contents: None,
5491 range: word_search_range,
5492 skip_digits,
5493 })
5494 }),
5495 };
5496
5497 (words, provider_responses)
5498 }
5499 None => {
5500 let words = if omit_word_completions {
5501 Task::ready(BTreeMap::default())
5502 } else {
5503 cx.background_spawn(async move {
5504 buffer_snapshot.words_in_range(WordsQuery {
5505 fuzzy_contents: None,
5506 range: word_search_range,
5507 skip_digits,
5508 })
5509 })
5510 };
5511 (words, Task::ready(Ok(Vec::new())))
5512 }
5513 };
5514
5515 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5516
5517 let id = post_inc(&mut self.next_completion_id);
5518 let task = cx.spawn_in(window, async move |editor, cx| {
5519 let Ok(()) = editor.update(cx, |this, _| {
5520 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5521 }) else {
5522 return;
5523 };
5524
5525 // TODO: Ideally completions from different sources would be selectively re-queried, so
5526 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5527 let mut completions = Vec::new();
5528 let mut is_incomplete = false;
5529 let mut display_options: Option<CompletionDisplayOptions> = None;
5530 if let Some(provider_responses) = provider_responses.await.log_err()
5531 && !provider_responses.is_empty()
5532 {
5533 for response in provider_responses {
5534 completions.extend(response.completions);
5535 is_incomplete = is_incomplete || response.is_incomplete;
5536 match display_options.as_mut() {
5537 None => {
5538 display_options = Some(response.display_options);
5539 }
5540 Some(options) => options.merge(&response.display_options),
5541 }
5542 }
5543 if completion_settings.words == WordsCompletionMode::Fallback {
5544 words = Task::ready(BTreeMap::default());
5545 }
5546 }
5547 let display_options = display_options.unwrap_or_default();
5548
5549 let mut words = words.await;
5550 if let Some(word_to_exclude) = &word_to_exclude {
5551 words.remove(word_to_exclude);
5552 }
5553 for lsp_completion in &completions {
5554 words.remove(&lsp_completion.new_text);
5555 }
5556 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5557 replace_range: word_replace_range.clone(),
5558 new_text: word.clone(),
5559 label: CodeLabel::plain(word, None),
5560 icon_path: None,
5561 documentation: None,
5562 source: CompletionSource::BufferWord {
5563 word_range,
5564 resolved: false,
5565 },
5566 insert_text_mode: Some(InsertTextMode::AS_IS),
5567 confirm: None,
5568 }));
5569
5570 let menu = if completions.is_empty() {
5571 None
5572 } else {
5573 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5574 let languages = editor
5575 .workspace
5576 .as_ref()
5577 .and_then(|(workspace, _)| workspace.upgrade())
5578 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5579 let menu = CompletionsMenu::new(
5580 id,
5581 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5582 sort_completions,
5583 show_completion_documentation,
5584 position,
5585 query.clone(),
5586 is_incomplete,
5587 buffer.clone(),
5588 completions.into(),
5589 display_options,
5590 snippet_sort_order,
5591 languages,
5592 language,
5593 cx,
5594 );
5595
5596 let query = if filter_completions { query } else { None };
5597 let matches_task = if let Some(query) = query {
5598 menu.do_async_filtering(query, cx)
5599 } else {
5600 Task::ready(menu.unfiltered_matches())
5601 };
5602 (menu, matches_task)
5603 }) else {
5604 return;
5605 };
5606
5607 let matches = matches_task.await;
5608
5609 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5610 // Newer menu already set, so exit.
5611 if let Some(CodeContextMenu::Completions(prev_menu)) =
5612 editor.context_menu.borrow().as_ref()
5613 && prev_menu.id > id
5614 {
5615 return;
5616 };
5617
5618 // Only valid to take prev_menu because it the new menu is immediately set
5619 // below, or the menu is hidden.
5620 if let Some(CodeContextMenu::Completions(prev_menu)) =
5621 editor.context_menu.borrow_mut().take()
5622 {
5623 let position_matches =
5624 if prev_menu.initial_position == menu.initial_position {
5625 true
5626 } else {
5627 let snapshot = editor.buffer.read(cx).read(cx);
5628 prev_menu.initial_position.to_offset(&snapshot)
5629 == menu.initial_position.to_offset(&snapshot)
5630 };
5631 if position_matches {
5632 // Preserve markdown cache before `set_filter_results` because it will
5633 // try to populate the documentation cache.
5634 menu.preserve_markdown_cache(prev_menu);
5635 }
5636 };
5637
5638 menu.set_filter_results(matches, provider, window, cx);
5639 }) else {
5640 return;
5641 };
5642
5643 menu.visible().then_some(menu)
5644 };
5645
5646 editor
5647 .update_in(cx, |editor, window, cx| {
5648 if editor.focus_handle.is_focused(window)
5649 && let Some(menu) = menu
5650 {
5651 *editor.context_menu.borrow_mut() =
5652 Some(CodeContextMenu::Completions(menu));
5653
5654 crate::hover_popover::hide_hover(editor, cx);
5655 if editor.show_edit_predictions_in_menu() {
5656 editor.update_visible_edit_prediction(window, cx);
5657 } else {
5658 editor.discard_edit_prediction(false, cx);
5659 }
5660
5661 cx.notify();
5662 return;
5663 }
5664
5665 if editor.completion_tasks.len() <= 1 {
5666 // If there are no more completion tasks and the last menu was empty, we should hide it.
5667 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5668 // If it was already hidden and we don't show edit predictions in the menu,
5669 // we should also show the edit prediction when available.
5670 if was_hidden && editor.show_edit_predictions_in_menu() {
5671 editor.update_visible_edit_prediction(window, cx);
5672 }
5673 }
5674 })
5675 .ok();
5676 });
5677
5678 self.completion_tasks.push((id, task));
5679 }
5680
5681 #[cfg(feature = "test-support")]
5682 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5683 let menu = self.context_menu.borrow();
5684 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5685 let completions = menu.completions.borrow();
5686 Some(completions.to_vec())
5687 } else {
5688 None
5689 }
5690 }
5691
5692 pub fn with_completions_menu_matching_id<R>(
5693 &self,
5694 id: CompletionId,
5695 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5696 ) -> R {
5697 let mut context_menu = self.context_menu.borrow_mut();
5698 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5699 return f(None);
5700 };
5701 if completions_menu.id != id {
5702 return f(None);
5703 }
5704 f(Some(completions_menu))
5705 }
5706
5707 pub fn confirm_completion(
5708 &mut self,
5709 action: &ConfirmCompletion,
5710 window: &mut Window,
5711 cx: &mut Context<Self>,
5712 ) -> Option<Task<Result<()>>> {
5713 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5714 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5715 }
5716
5717 pub fn confirm_completion_insert(
5718 &mut self,
5719 _: &ConfirmCompletionInsert,
5720 window: &mut Window,
5721 cx: &mut Context<Self>,
5722 ) -> Option<Task<Result<()>>> {
5723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5724 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5725 }
5726
5727 pub fn confirm_completion_replace(
5728 &mut self,
5729 _: &ConfirmCompletionReplace,
5730 window: &mut Window,
5731 cx: &mut Context<Self>,
5732 ) -> Option<Task<Result<()>>> {
5733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5734 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5735 }
5736
5737 pub fn compose_completion(
5738 &mut self,
5739 action: &ComposeCompletion,
5740 window: &mut Window,
5741 cx: &mut Context<Self>,
5742 ) -> Option<Task<Result<()>>> {
5743 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5744 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5745 }
5746
5747 fn do_completion(
5748 &mut self,
5749 item_ix: Option<usize>,
5750 intent: CompletionIntent,
5751 window: &mut Window,
5752 cx: &mut Context<Editor>,
5753 ) -> Option<Task<Result<()>>> {
5754 use language::ToOffset as _;
5755
5756 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5757 else {
5758 return None;
5759 };
5760
5761 let candidate_id = {
5762 let entries = completions_menu.entries.borrow();
5763 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5764 if self.show_edit_predictions_in_menu() {
5765 self.discard_edit_prediction(true, cx);
5766 }
5767 mat.candidate_id
5768 };
5769
5770 let completion = completions_menu
5771 .completions
5772 .borrow()
5773 .get(candidate_id)?
5774 .clone();
5775 cx.stop_propagation();
5776
5777 let buffer_handle = completions_menu.buffer.clone();
5778
5779 let CompletionEdit {
5780 new_text,
5781 snippet,
5782 replace_range,
5783 } = process_completion_for_edit(
5784 &completion,
5785 intent,
5786 &buffer_handle,
5787 &completions_menu.initial_position.text_anchor,
5788 cx,
5789 );
5790
5791 let buffer = buffer_handle.read(cx);
5792 let snapshot = self.buffer.read(cx).snapshot(cx);
5793 let newest_anchor = self.selections.newest_anchor();
5794 let replace_range_multibuffer = {
5795 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5796 excerpt.map_range_from_buffer(replace_range.clone())
5797 };
5798 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5799 return None;
5800 }
5801
5802 let old_text = buffer
5803 .text_for_range(replace_range.clone())
5804 .collect::<String>();
5805 let lookbehind = newest_anchor
5806 .start
5807 .text_anchor
5808 .to_offset(buffer)
5809 .saturating_sub(replace_range.start);
5810 let lookahead = replace_range
5811 .end
5812 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5813 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5814 let suffix = &old_text[lookbehind.min(old_text.len())..];
5815
5816 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5817 let mut ranges = Vec::new();
5818 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5819
5820 for selection in &selections {
5821 let range = if selection.id == newest_anchor.id {
5822 replace_range_multibuffer.clone()
5823 } else {
5824 let mut range = selection.range();
5825
5826 // if prefix is present, don't duplicate it
5827 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5828 range.start = range.start.saturating_sub(lookbehind);
5829
5830 // if suffix is also present, mimic the newest cursor and replace it
5831 if selection.id != newest_anchor.id
5832 && snapshot.contains_str_at(range.end, suffix)
5833 {
5834 range.end += lookahead;
5835 }
5836 }
5837 range
5838 };
5839
5840 ranges.push(range.clone());
5841
5842 if !self.linked_edit_ranges.is_empty() {
5843 let start_anchor = snapshot.anchor_before(range.start);
5844 let end_anchor = snapshot.anchor_after(range.end);
5845 if let Some(ranges) = self
5846 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5847 {
5848 for (buffer, edits) in ranges {
5849 linked_edits
5850 .entry(buffer.clone())
5851 .or_default()
5852 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5853 }
5854 }
5855 }
5856 }
5857
5858 let common_prefix_len = old_text
5859 .chars()
5860 .zip(new_text.chars())
5861 .take_while(|(a, b)| a == b)
5862 .map(|(a, _)| a.len_utf8())
5863 .sum::<usize>();
5864
5865 cx.emit(EditorEvent::InputHandled {
5866 utf16_range_to_replace: None,
5867 text: new_text[common_prefix_len..].into(),
5868 });
5869
5870 self.transact(window, cx, |editor, window, cx| {
5871 if let Some(mut snippet) = snippet {
5872 snippet.text = new_text.to_string();
5873 editor
5874 .insert_snippet(&ranges, snippet, window, cx)
5875 .log_err();
5876 } else {
5877 editor.buffer.update(cx, |multi_buffer, cx| {
5878 let auto_indent = match completion.insert_text_mode {
5879 Some(InsertTextMode::AS_IS) => None,
5880 _ => editor.autoindent_mode.clone(),
5881 };
5882 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5883 multi_buffer.edit(edits, auto_indent, cx);
5884 });
5885 }
5886 for (buffer, edits) in linked_edits {
5887 buffer.update(cx, |buffer, cx| {
5888 let snapshot = buffer.snapshot();
5889 let edits = edits
5890 .into_iter()
5891 .map(|(range, text)| {
5892 use text::ToPoint as TP;
5893 let end_point = TP::to_point(&range.end, &snapshot);
5894 let start_point = TP::to_point(&range.start, &snapshot);
5895 (start_point..end_point, text)
5896 })
5897 .sorted_by_key(|(range, _)| range.start);
5898 buffer.edit(edits, None, cx);
5899 })
5900 }
5901
5902 editor.refresh_edit_prediction(true, false, window, cx);
5903 });
5904 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5905
5906 let show_new_completions_on_confirm = completion
5907 .confirm
5908 .as_ref()
5909 .is_some_and(|confirm| confirm(intent, window, cx));
5910 if show_new_completions_on_confirm {
5911 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5912 }
5913
5914 let provider = self.completion_provider.as_ref()?;
5915 drop(completion);
5916 let apply_edits = provider.apply_additional_edits_for_completion(
5917 buffer_handle,
5918 completions_menu.completions.clone(),
5919 candidate_id,
5920 true,
5921 cx,
5922 );
5923
5924 let editor_settings = EditorSettings::get_global(cx);
5925 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5926 // After the code completion is finished, users often want to know what signatures are needed.
5927 // so we should automatically call signature_help
5928 self.show_signature_help(&ShowSignatureHelp, window, cx);
5929 }
5930
5931 Some(cx.foreground_executor().spawn(async move {
5932 apply_edits.await?;
5933 Ok(())
5934 }))
5935 }
5936
5937 pub fn toggle_code_actions(
5938 &mut self,
5939 action: &ToggleCodeActions,
5940 window: &mut Window,
5941 cx: &mut Context<Self>,
5942 ) {
5943 let quick_launch = action.quick_launch;
5944 let mut context_menu = self.context_menu.borrow_mut();
5945 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5946 if code_actions.deployed_from == action.deployed_from {
5947 // Toggle if we're selecting the same one
5948 *context_menu = None;
5949 cx.notify();
5950 return;
5951 } else {
5952 // Otherwise, clear it and start a new one
5953 *context_menu = None;
5954 cx.notify();
5955 }
5956 }
5957 drop(context_menu);
5958 let snapshot = self.snapshot(window, cx);
5959 let deployed_from = action.deployed_from.clone();
5960 let action = action.clone();
5961 self.completion_tasks.clear();
5962 self.discard_edit_prediction(false, cx);
5963
5964 let multibuffer_point = match &action.deployed_from {
5965 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5966 DisplayPoint::new(*row, 0).to_point(&snapshot)
5967 }
5968 _ => self
5969 .selections
5970 .newest::<Point>(&snapshot.display_snapshot)
5971 .head(),
5972 };
5973 let Some((buffer, buffer_row)) = snapshot
5974 .buffer_snapshot()
5975 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5976 .and_then(|(buffer_snapshot, range)| {
5977 self.buffer()
5978 .read(cx)
5979 .buffer(buffer_snapshot.remote_id())
5980 .map(|buffer| (buffer, range.start.row))
5981 })
5982 else {
5983 return;
5984 };
5985 let buffer_id = buffer.read(cx).remote_id();
5986 let tasks = self
5987 .tasks
5988 .get(&(buffer_id, buffer_row))
5989 .map(|t| Arc::new(t.to_owned()));
5990
5991 if !self.focus_handle.is_focused(window) {
5992 return;
5993 }
5994 let project = self.project.clone();
5995
5996 let code_actions_task = match deployed_from {
5997 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5998 _ => self.code_actions(buffer_row, window, cx),
5999 };
6000
6001 let runnable_task = match deployed_from {
6002 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6003 _ => {
6004 let mut task_context_task = Task::ready(None);
6005 if let Some(tasks) = &tasks
6006 && let Some(project) = project
6007 {
6008 task_context_task =
6009 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6010 }
6011
6012 cx.spawn_in(window, {
6013 let buffer = buffer.clone();
6014 async move |editor, cx| {
6015 let task_context = task_context_task.await;
6016
6017 let resolved_tasks =
6018 tasks
6019 .zip(task_context.clone())
6020 .map(|(tasks, task_context)| ResolvedTasks {
6021 templates: tasks.resolve(&task_context).collect(),
6022 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6023 multibuffer_point.row,
6024 tasks.column,
6025 )),
6026 });
6027 let debug_scenarios = editor
6028 .update(cx, |editor, cx| {
6029 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6030 })?
6031 .await;
6032 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6033 }
6034 })
6035 }
6036 };
6037
6038 cx.spawn_in(window, async move |editor, cx| {
6039 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6040 let code_actions = code_actions_task.await;
6041 let spawn_straight_away = quick_launch
6042 && resolved_tasks
6043 .as_ref()
6044 .is_some_and(|tasks| tasks.templates.len() == 1)
6045 && code_actions
6046 .as_ref()
6047 .is_none_or(|actions| actions.is_empty())
6048 && debug_scenarios.is_empty();
6049
6050 editor.update_in(cx, |editor, window, cx| {
6051 crate::hover_popover::hide_hover(editor, cx);
6052 let actions = CodeActionContents::new(
6053 resolved_tasks,
6054 code_actions,
6055 debug_scenarios,
6056 task_context.unwrap_or_default(),
6057 );
6058
6059 // Don't show the menu if there are no actions available
6060 if actions.is_empty() {
6061 cx.notify();
6062 return Task::ready(Ok(()));
6063 }
6064
6065 *editor.context_menu.borrow_mut() =
6066 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6067 buffer,
6068 actions,
6069 selected_item: Default::default(),
6070 scroll_handle: UniformListScrollHandle::default(),
6071 deployed_from,
6072 }));
6073 cx.notify();
6074 if spawn_straight_away
6075 && let Some(task) = editor.confirm_code_action(
6076 &ConfirmCodeAction { item_ix: Some(0) },
6077 window,
6078 cx,
6079 )
6080 {
6081 return task;
6082 }
6083
6084 Task::ready(Ok(()))
6085 })
6086 })
6087 .detach_and_log_err(cx);
6088 }
6089
6090 fn debug_scenarios(
6091 &mut self,
6092 resolved_tasks: &Option<ResolvedTasks>,
6093 buffer: &Entity<Buffer>,
6094 cx: &mut App,
6095 ) -> Task<Vec<task::DebugScenario>> {
6096 maybe!({
6097 let project = self.project()?;
6098 let dap_store = project.read(cx).dap_store();
6099 let mut scenarios = vec![];
6100 let resolved_tasks = resolved_tasks.as_ref()?;
6101 let buffer = buffer.read(cx);
6102 let language = buffer.language()?;
6103 let file = buffer.file();
6104 let debug_adapter = language_settings(language.name().into(), file, cx)
6105 .debuggers
6106 .first()
6107 .map(SharedString::from)
6108 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6109
6110 dap_store.update(cx, |dap_store, cx| {
6111 for (_, task) in &resolved_tasks.templates {
6112 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6113 task.original_task().clone(),
6114 debug_adapter.clone().into(),
6115 task.display_label().to_owned().into(),
6116 cx,
6117 );
6118 scenarios.push(maybe_scenario);
6119 }
6120 });
6121 Some(cx.background_spawn(async move {
6122 futures::future::join_all(scenarios)
6123 .await
6124 .into_iter()
6125 .flatten()
6126 .collect::<Vec<_>>()
6127 }))
6128 })
6129 .unwrap_or_else(|| Task::ready(vec![]))
6130 }
6131
6132 fn code_actions(
6133 &mut self,
6134 buffer_row: u32,
6135 window: &mut Window,
6136 cx: &mut Context<Self>,
6137 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6138 let mut task = self.code_actions_task.take();
6139 cx.spawn_in(window, async move |editor, cx| {
6140 while let Some(prev_task) = task {
6141 prev_task.await.log_err();
6142 task = editor
6143 .update(cx, |this, _| this.code_actions_task.take())
6144 .ok()?;
6145 }
6146
6147 editor
6148 .update(cx, |editor, cx| {
6149 editor
6150 .available_code_actions
6151 .clone()
6152 .and_then(|(location, code_actions)| {
6153 let snapshot = location.buffer.read(cx).snapshot();
6154 let point_range = location.range.to_point(&snapshot);
6155 let point_range = point_range.start.row..=point_range.end.row;
6156 if point_range.contains(&buffer_row) {
6157 Some(code_actions)
6158 } else {
6159 None
6160 }
6161 })
6162 })
6163 .ok()
6164 .flatten()
6165 })
6166 }
6167
6168 pub fn confirm_code_action(
6169 &mut self,
6170 action: &ConfirmCodeAction,
6171 window: &mut Window,
6172 cx: &mut Context<Self>,
6173 ) -> Option<Task<Result<()>>> {
6174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6175
6176 let actions_menu =
6177 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6178 menu
6179 } else {
6180 return None;
6181 };
6182
6183 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6184 let action = actions_menu.actions.get(action_ix)?;
6185 let title = action.label();
6186 let buffer = actions_menu.buffer;
6187 let workspace = self.workspace()?;
6188
6189 match action {
6190 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6191 workspace.update(cx, |workspace, cx| {
6192 workspace.schedule_resolved_task(
6193 task_source_kind,
6194 resolved_task,
6195 false,
6196 window,
6197 cx,
6198 );
6199
6200 Some(Task::ready(Ok(())))
6201 })
6202 }
6203 CodeActionsItem::CodeAction {
6204 excerpt_id,
6205 action,
6206 provider,
6207 } => {
6208 let apply_code_action =
6209 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6210 let workspace = workspace.downgrade();
6211 Some(cx.spawn_in(window, async move |editor, cx| {
6212 let project_transaction = apply_code_action.await?;
6213 Self::open_project_transaction(
6214 &editor,
6215 workspace,
6216 project_transaction,
6217 title,
6218 cx,
6219 )
6220 .await
6221 }))
6222 }
6223 CodeActionsItem::DebugScenario(scenario) => {
6224 let context = actions_menu.actions.context;
6225
6226 workspace.update(cx, |workspace, cx| {
6227 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6228 workspace.start_debug_session(
6229 scenario,
6230 context,
6231 Some(buffer),
6232 None,
6233 window,
6234 cx,
6235 );
6236 });
6237 Some(Task::ready(Ok(())))
6238 }
6239 }
6240 }
6241
6242 pub async fn open_project_transaction(
6243 editor: &WeakEntity<Editor>,
6244 workspace: WeakEntity<Workspace>,
6245 transaction: ProjectTransaction,
6246 title: String,
6247 cx: &mut AsyncWindowContext,
6248 ) -> Result<()> {
6249 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6250 cx.update(|_, cx| {
6251 entries.sort_unstable_by_key(|(buffer, _)| {
6252 buffer.read(cx).file().map(|f| f.path().clone())
6253 });
6254 })?;
6255 if entries.is_empty() {
6256 return Ok(());
6257 }
6258
6259 // If the project transaction's edits are all contained within this editor, then
6260 // avoid opening a new editor to display them.
6261
6262 if let [(buffer, transaction)] = &*entries {
6263 let excerpt = editor.update(cx, |editor, cx| {
6264 editor
6265 .buffer()
6266 .read(cx)
6267 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6268 })?;
6269 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6270 && excerpted_buffer == *buffer
6271 {
6272 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6273 let excerpt_range = excerpt_range.to_offset(buffer);
6274 buffer
6275 .edited_ranges_for_transaction::<usize>(transaction)
6276 .all(|range| {
6277 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6278 })
6279 })?;
6280
6281 if all_edits_within_excerpt {
6282 return Ok(());
6283 }
6284 }
6285 }
6286
6287 let mut ranges_to_highlight = Vec::new();
6288 let excerpt_buffer = cx.new(|cx| {
6289 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6290 for (buffer_handle, transaction) in &entries {
6291 let edited_ranges = buffer_handle
6292 .read(cx)
6293 .edited_ranges_for_transaction::<Point>(transaction)
6294 .collect::<Vec<_>>();
6295 let (ranges, _) = multibuffer.set_excerpts_for_path(
6296 PathKey::for_buffer(buffer_handle, cx),
6297 buffer_handle.clone(),
6298 edited_ranges,
6299 multibuffer_context_lines(cx),
6300 cx,
6301 );
6302
6303 ranges_to_highlight.extend(ranges);
6304 }
6305 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6306 multibuffer
6307 })?;
6308
6309 workspace.update_in(cx, |workspace, window, cx| {
6310 let project = workspace.project().clone();
6311 let editor =
6312 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6313 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6314 editor.update(cx, |editor, cx| {
6315 editor.highlight_background::<Self>(
6316 &ranges_to_highlight,
6317 |theme| theme.colors().editor_highlighted_line_background,
6318 cx,
6319 );
6320 });
6321 })?;
6322
6323 Ok(())
6324 }
6325
6326 pub fn clear_code_action_providers(&mut self) {
6327 self.code_action_providers.clear();
6328 self.available_code_actions.take();
6329 }
6330
6331 pub fn add_code_action_provider(
6332 &mut self,
6333 provider: Rc<dyn CodeActionProvider>,
6334 window: &mut Window,
6335 cx: &mut Context<Self>,
6336 ) {
6337 if self
6338 .code_action_providers
6339 .iter()
6340 .any(|existing_provider| existing_provider.id() == provider.id())
6341 {
6342 return;
6343 }
6344
6345 self.code_action_providers.push(provider);
6346 self.refresh_code_actions(window, cx);
6347 }
6348
6349 pub fn remove_code_action_provider(
6350 &mut self,
6351 id: Arc<str>,
6352 window: &mut Window,
6353 cx: &mut Context<Self>,
6354 ) {
6355 self.code_action_providers
6356 .retain(|provider| provider.id() != id);
6357 self.refresh_code_actions(window, cx);
6358 }
6359
6360 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6361 !self.code_action_providers.is_empty()
6362 && EditorSettings::get_global(cx).toolbar.code_actions
6363 }
6364
6365 pub fn has_available_code_actions(&self) -> bool {
6366 self.available_code_actions
6367 .as_ref()
6368 .is_some_and(|(_, actions)| !actions.is_empty())
6369 }
6370
6371 fn render_inline_code_actions(
6372 &self,
6373 icon_size: ui::IconSize,
6374 display_row: DisplayRow,
6375 is_active: bool,
6376 cx: &mut Context<Self>,
6377 ) -> AnyElement {
6378 let show_tooltip = !self.context_menu_visible();
6379 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6380 .icon_size(icon_size)
6381 .shape(ui::IconButtonShape::Square)
6382 .icon_color(ui::Color::Hidden)
6383 .toggle_state(is_active)
6384 .when(show_tooltip, |this| {
6385 this.tooltip({
6386 let focus_handle = self.focus_handle.clone();
6387 move |_window, cx| {
6388 Tooltip::for_action_in(
6389 "Toggle Code Actions",
6390 &ToggleCodeActions {
6391 deployed_from: None,
6392 quick_launch: false,
6393 },
6394 &focus_handle,
6395 cx,
6396 )
6397 }
6398 })
6399 })
6400 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6401 window.focus(&editor.focus_handle(cx));
6402 editor.toggle_code_actions(
6403 &crate::actions::ToggleCodeActions {
6404 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6405 display_row,
6406 )),
6407 quick_launch: false,
6408 },
6409 window,
6410 cx,
6411 );
6412 }))
6413 .into_any_element()
6414 }
6415
6416 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6417 &self.context_menu
6418 }
6419
6420 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6421 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6422 cx.background_executor()
6423 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6424 .await;
6425
6426 let (start_buffer, start, _, end, newest_selection) = this
6427 .update(cx, |this, cx| {
6428 let newest_selection = this.selections.newest_anchor().clone();
6429 if newest_selection.head().diff_base_anchor.is_some() {
6430 return None;
6431 }
6432 let display_snapshot = this.display_snapshot(cx);
6433 let newest_selection_adjusted =
6434 this.selections.newest_adjusted(&display_snapshot);
6435 let buffer = this.buffer.read(cx);
6436
6437 let (start_buffer, start) =
6438 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6439 let (end_buffer, end) =
6440 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6441
6442 Some((start_buffer, start, end_buffer, end, newest_selection))
6443 })?
6444 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6445 .context(
6446 "Expected selection to lie in a single buffer when refreshing code actions",
6447 )?;
6448 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6449 let providers = this.code_action_providers.clone();
6450 let tasks = this
6451 .code_action_providers
6452 .iter()
6453 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6454 .collect::<Vec<_>>();
6455 (providers, tasks)
6456 })?;
6457
6458 let mut actions = Vec::new();
6459 for (provider, provider_actions) in
6460 providers.into_iter().zip(future::join_all(tasks).await)
6461 {
6462 if let Some(provider_actions) = provider_actions.log_err() {
6463 actions.extend(provider_actions.into_iter().map(|action| {
6464 AvailableCodeAction {
6465 excerpt_id: newest_selection.start.excerpt_id,
6466 action,
6467 provider: provider.clone(),
6468 }
6469 }));
6470 }
6471 }
6472
6473 this.update(cx, |this, cx| {
6474 this.available_code_actions = if actions.is_empty() {
6475 None
6476 } else {
6477 Some((
6478 Location {
6479 buffer: start_buffer,
6480 range: start..end,
6481 },
6482 actions.into(),
6483 ))
6484 };
6485 cx.notify();
6486 })
6487 }));
6488 }
6489
6490 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6491 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6492 self.show_git_blame_inline = false;
6493
6494 self.show_git_blame_inline_delay_task =
6495 Some(cx.spawn_in(window, async move |this, cx| {
6496 cx.background_executor().timer(delay).await;
6497
6498 this.update(cx, |this, cx| {
6499 this.show_git_blame_inline = true;
6500 cx.notify();
6501 })
6502 .log_err();
6503 }));
6504 }
6505 }
6506
6507 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6508 let snapshot = self.snapshot(window, cx);
6509 let cursor = self
6510 .selections
6511 .newest::<Point>(&snapshot.display_snapshot)
6512 .head();
6513 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6514 else {
6515 return;
6516 };
6517
6518 let Some(blame) = self.blame.as_ref() else {
6519 return;
6520 };
6521
6522 let row_info = RowInfo {
6523 buffer_id: Some(buffer.remote_id()),
6524 buffer_row: Some(point.row),
6525 ..Default::default()
6526 };
6527 let Some((buffer, blame_entry)) = blame
6528 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6529 .flatten()
6530 else {
6531 return;
6532 };
6533
6534 let anchor = self.selections.newest_anchor().head();
6535 let position = self.to_pixel_point(anchor, &snapshot, window);
6536 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6537 self.show_blame_popover(
6538 buffer,
6539 &blame_entry,
6540 position + last_bounds.origin,
6541 true,
6542 cx,
6543 );
6544 };
6545 }
6546
6547 fn show_blame_popover(
6548 &mut self,
6549 buffer: BufferId,
6550 blame_entry: &BlameEntry,
6551 position: gpui::Point<Pixels>,
6552 ignore_timeout: bool,
6553 cx: &mut Context<Self>,
6554 ) {
6555 if let Some(state) = &mut self.inline_blame_popover {
6556 state.hide_task.take();
6557 } else {
6558 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6559 let blame_entry = blame_entry.clone();
6560 let show_task = cx.spawn(async move |editor, cx| {
6561 if !ignore_timeout {
6562 cx.background_executor()
6563 .timer(std::time::Duration::from_millis(blame_popover_delay))
6564 .await;
6565 }
6566 editor
6567 .update(cx, |editor, cx| {
6568 editor.inline_blame_popover_show_task.take();
6569 let Some(blame) = editor.blame.as_ref() else {
6570 return;
6571 };
6572 let blame = blame.read(cx);
6573 let details = blame.details_for_entry(buffer, &blame_entry);
6574 let markdown = cx.new(|cx| {
6575 Markdown::new(
6576 details
6577 .as_ref()
6578 .map(|message| message.message.clone())
6579 .unwrap_or_default(),
6580 None,
6581 None,
6582 cx,
6583 )
6584 });
6585 editor.inline_blame_popover = Some(InlineBlamePopover {
6586 position,
6587 hide_task: None,
6588 popover_bounds: None,
6589 popover_state: InlineBlamePopoverState {
6590 scroll_handle: ScrollHandle::new(),
6591 commit_message: details,
6592 markdown,
6593 },
6594 keyboard_grace: ignore_timeout,
6595 });
6596 cx.notify();
6597 })
6598 .ok();
6599 });
6600 self.inline_blame_popover_show_task = Some(show_task);
6601 }
6602 }
6603
6604 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6605 self.inline_blame_popover_show_task.take();
6606 if let Some(state) = &mut self.inline_blame_popover {
6607 let hide_task = cx.spawn(async move |editor, cx| {
6608 if !ignore_timeout {
6609 cx.background_executor()
6610 .timer(std::time::Duration::from_millis(100))
6611 .await;
6612 }
6613 editor
6614 .update(cx, |editor, cx| {
6615 editor.inline_blame_popover.take();
6616 cx.notify();
6617 })
6618 .ok();
6619 });
6620 state.hide_task = Some(hide_task);
6621 true
6622 } else {
6623 false
6624 }
6625 }
6626
6627 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6628 if self.pending_rename.is_some() {
6629 return None;
6630 }
6631
6632 let provider = self.semantics_provider.clone()?;
6633 let buffer = self.buffer.read(cx);
6634 let newest_selection = self.selections.newest_anchor().clone();
6635 let cursor_position = newest_selection.head();
6636 let (cursor_buffer, cursor_buffer_position) =
6637 buffer.text_anchor_for_position(cursor_position, cx)?;
6638 let (tail_buffer, tail_buffer_position) =
6639 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6640 if cursor_buffer != tail_buffer {
6641 return None;
6642 }
6643
6644 let snapshot = cursor_buffer.read(cx).snapshot();
6645 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6646 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6647 if start_word_range != end_word_range {
6648 self.document_highlights_task.take();
6649 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6650 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6651 return None;
6652 }
6653
6654 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6655 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6656 cx.background_executor()
6657 .timer(Duration::from_millis(debounce))
6658 .await;
6659
6660 let highlights = if let Some(highlights) = cx
6661 .update(|cx| {
6662 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6663 })
6664 .ok()
6665 .flatten()
6666 {
6667 highlights.await.log_err()
6668 } else {
6669 None
6670 };
6671
6672 if let Some(highlights) = highlights {
6673 this.update(cx, |this, cx| {
6674 if this.pending_rename.is_some() {
6675 return;
6676 }
6677
6678 let buffer = this.buffer.read(cx);
6679 if buffer
6680 .text_anchor_for_position(cursor_position, cx)
6681 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6682 {
6683 return;
6684 }
6685
6686 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6687 let mut write_ranges = Vec::new();
6688 let mut read_ranges = Vec::new();
6689 for highlight in highlights {
6690 let buffer_id = cursor_buffer.read(cx).remote_id();
6691 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6692 {
6693 let start = highlight
6694 .range
6695 .start
6696 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6697 let end = highlight
6698 .range
6699 .end
6700 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6701 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6702 continue;
6703 }
6704
6705 let range =
6706 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6707 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6708 write_ranges.push(range);
6709 } else {
6710 read_ranges.push(range);
6711 }
6712 }
6713 }
6714
6715 this.highlight_background::<DocumentHighlightRead>(
6716 &read_ranges,
6717 |theme| theme.colors().editor_document_highlight_read_background,
6718 cx,
6719 );
6720 this.highlight_background::<DocumentHighlightWrite>(
6721 &write_ranges,
6722 |theme| theme.colors().editor_document_highlight_write_background,
6723 cx,
6724 );
6725 cx.notify();
6726 })
6727 .log_err();
6728 }
6729 }));
6730 None
6731 }
6732
6733 fn prepare_highlight_query_from_selection(
6734 &mut self,
6735 window: &Window,
6736 cx: &mut Context<Editor>,
6737 ) -> Option<(String, Range<Anchor>)> {
6738 if matches!(self.mode, EditorMode::SingleLine) {
6739 return None;
6740 }
6741 if !EditorSettings::get_global(cx).selection_highlight {
6742 return None;
6743 }
6744 if self.selections.count() != 1 || self.selections.line_mode() {
6745 return None;
6746 }
6747 let snapshot = self.snapshot(window, cx);
6748 let selection = self.selections.newest::<Point>(&snapshot);
6749 // If the selection spans multiple rows OR it is empty
6750 if selection.start.row != selection.end.row
6751 || selection.start.column == selection.end.column
6752 {
6753 return None;
6754 }
6755 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6756 let query = snapshot
6757 .buffer_snapshot()
6758 .text_for_range(selection_anchor_range.clone())
6759 .collect::<String>();
6760 if query.trim().is_empty() {
6761 return None;
6762 }
6763 Some((query, selection_anchor_range))
6764 }
6765
6766 fn update_selection_occurrence_highlights(
6767 &mut self,
6768 query_text: String,
6769 query_range: Range<Anchor>,
6770 multi_buffer_range_to_query: Range<Point>,
6771 use_debounce: bool,
6772 window: &mut Window,
6773 cx: &mut Context<Editor>,
6774 ) -> Task<()> {
6775 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6776 cx.spawn_in(window, async move |editor, cx| {
6777 if use_debounce {
6778 cx.background_executor()
6779 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6780 .await;
6781 }
6782 let match_task = cx.background_spawn(async move {
6783 let buffer_ranges = multi_buffer_snapshot
6784 .range_to_buffer_ranges(multi_buffer_range_to_query)
6785 .into_iter()
6786 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6787 let mut match_ranges = Vec::new();
6788 let Ok(regex) = project::search::SearchQuery::text(
6789 query_text.clone(),
6790 false,
6791 false,
6792 false,
6793 Default::default(),
6794 Default::default(),
6795 false,
6796 None,
6797 ) else {
6798 return Vec::default();
6799 };
6800 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6801 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6802 match_ranges.extend(
6803 regex
6804 .search(buffer_snapshot, Some(search_range.clone()))
6805 .await
6806 .into_iter()
6807 .filter_map(|match_range| {
6808 let match_start = buffer_snapshot
6809 .anchor_after(search_range.start + match_range.start);
6810 let match_end = buffer_snapshot
6811 .anchor_before(search_range.start + match_range.end);
6812 let match_anchor_range = Anchor::range_in_buffer(
6813 excerpt_id,
6814 buffer_snapshot.remote_id(),
6815 match_start..match_end,
6816 );
6817 (match_anchor_range != query_range).then_some(match_anchor_range)
6818 }),
6819 );
6820 }
6821 match_ranges
6822 });
6823 let match_ranges = match_task.await;
6824 editor
6825 .update_in(cx, |editor, _, cx| {
6826 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6827 if !match_ranges.is_empty() {
6828 editor.highlight_background::<SelectedTextHighlight>(
6829 &match_ranges,
6830 |theme| theme.colors().editor_document_highlight_bracket_background,
6831 cx,
6832 )
6833 }
6834 })
6835 .log_err();
6836 })
6837 }
6838
6839 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6840 struct NewlineFold;
6841 let type_id = std::any::TypeId::of::<NewlineFold>();
6842 if !self.mode.is_single_line() {
6843 return;
6844 }
6845 let snapshot = self.snapshot(window, cx);
6846 if snapshot.buffer_snapshot().max_point().row == 0 {
6847 return;
6848 }
6849 let task = cx.background_spawn(async move {
6850 let new_newlines = snapshot
6851 .buffer_chars_at(0)
6852 .filter_map(|(c, i)| {
6853 if c == '\n' {
6854 Some(
6855 snapshot.buffer_snapshot().anchor_after(i)
6856 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6857 )
6858 } else {
6859 None
6860 }
6861 })
6862 .collect::<Vec<_>>();
6863 let existing_newlines = snapshot
6864 .folds_in_range(0..snapshot.buffer_snapshot().len())
6865 .filter_map(|fold| {
6866 if fold.placeholder.type_tag == Some(type_id) {
6867 Some(fold.range.start..fold.range.end)
6868 } else {
6869 None
6870 }
6871 })
6872 .collect::<Vec<_>>();
6873
6874 (new_newlines, existing_newlines)
6875 });
6876 self.folding_newlines = cx.spawn(async move |this, cx| {
6877 let (new_newlines, existing_newlines) = task.await;
6878 if new_newlines == existing_newlines {
6879 return;
6880 }
6881 let placeholder = FoldPlaceholder {
6882 render: Arc::new(move |_, _, cx| {
6883 div()
6884 .bg(cx.theme().status().hint_background)
6885 .border_b_1()
6886 .size_full()
6887 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6888 .border_color(cx.theme().status().hint)
6889 .child("\\n")
6890 .into_any()
6891 }),
6892 constrain_width: false,
6893 merge_adjacent: false,
6894 type_tag: Some(type_id),
6895 };
6896 let creases = new_newlines
6897 .into_iter()
6898 .map(|range| Crease::simple(range, placeholder.clone()))
6899 .collect();
6900 this.update(cx, |this, cx| {
6901 this.display_map.update(cx, |display_map, cx| {
6902 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6903 display_map.fold(creases, cx);
6904 });
6905 })
6906 .ok();
6907 });
6908 }
6909
6910 fn refresh_selected_text_highlights(
6911 &mut self,
6912 on_buffer_edit: bool,
6913 window: &mut Window,
6914 cx: &mut Context<Editor>,
6915 ) {
6916 let Some((query_text, query_range)) =
6917 self.prepare_highlight_query_from_selection(window, cx)
6918 else {
6919 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6920 self.quick_selection_highlight_task.take();
6921 self.debounced_selection_highlight_task.take();
6922 return;
6923 };
6924 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6925 if on_buffer_edit
6926 || self
6927 .quick_selection_highlight_task
6928 .as_ref()
6929 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6930 {
6931 let multi_buffer_visible_start = self
6932 .scroll_manager
6933 .anchor()
6934 .anchor
6935 .to_point(&multi_buffer_snapshot);
6936 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6937 multi_buffer_visible_start
6938 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6939 Bias::Left,
6940 );
6941 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6942 self.quick_selection_highlight_task = Some((
6943 query_range.clone(),
6944 self.update_selection_occurrence_highlights(
6945 query_text.clone(),
6946 query_range.clone(),
6947 multi_buffer_visible_range,
6948 false,
6949 window,
6950 cx,
6951 ),
6952 ));
6953 }
6954 if on_buffer_edit
6955 || self
6956 .debounced_selection_highlight_task
6957 .as_ref()
6958 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6959 {
6960 let multi_buffer_start = multi_buffer_snapshot
6961 .anchor_before(0)
6962 .to_point(&multi_buffer_snapshot);
6963 let multi_buffer_end = multi_buffer_snapshot
6964 .anchor_after(multi_buffer_snapshot.len())
6965 .to_point(&multi_buffer_snapshot);
6966 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6967 self.debounced_selection_highlight_task = Some((
6968 query_range.clone(),
6969 self.update_selection_occurrence_highlights(
6970 query_text,
6971 query_range,
6972 multi_buffer_full_range,
6973 true,
6974 window,
6975 cx,
6976 ),
6977 ));
6978 }
6979 }
6980
6981 pub fn refresh_edit_prediction(
6982 &mut self,
6983 debounce: bool,
6984 user_requested: bool,
6985 window: &mut Window,
6986 cx: &mut Context<Self>,
6987 ) -> Option<()> {
6988 if DisableAiSettings::get_global(cx).disable_ai {
6989 return None;
6990 }
6991
6992 let provider = self.edit_prediction_provider()?;
6993 let cursor = self.selections.newest_anchor().head();
6994 let (buffer, cursor_buffer_position) =
6995 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6996
6997 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6998 self.discard_edit_prediction(false, cx);
6999 return None;
7000 }
7001
7002 self.update_visible_edit_prediction(window, cx);
7003
7004 if !user_requested
7005 && (!self.should_show_edit_predictions()
7006 || !self.is_focused(window)
7007 || buffer.read(cx).is_empty())
7008 {
7009 self.discard_edit_prediction(false, cx);
7010 return None;
7011 }
7012
7013 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7014 Some(())
7015 }
7016
7017 fn show_edit_predictions_in_menu(&self) -> bool {
7018 match self.edit_prediction_settings {
7019 EditPredictionSettings::Disabled => false,
7020 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7021 }
7022 }
7023
7024 pub fn edit_predictions_enabled(&self) -> bool {
7025 match self.edit_prediction_settings {
7026 EditPredictionSettings::Disabled => false,
7027 EditPredictionSettings::Enabled { .. } => true,
7028 }
7029 }
7030
7031 fn edit_prediction_requires_modifier(&self) -> bool {
7032 match self.edit_prediction_settings {
7033 EditPredictionSettings::Disabled => false,
7034 EditPredictionSettings::Enabled {
7035 preview_requires_modifier,
7036 ..
7037 } => preview_requires_modifier,
7038 }
7039 }
7040
7041 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7042 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7043 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7044 self.discard_edit_prediction(false, cx);
7045 } else {
7046 let selection = self.selections.newest_anchor();
7047 let cursor = selection.head();
7048
7049 if let Some((buffer, cursor_buffer_position)) =
7050 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7051 {
7052 self.edit_prediction_settings =
7053 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7054 }
7055 }
7056 }
7057
7058 fn edit_prediction_settings_at_position(
7059 &self,
7060 buffer: &Entity<Buffer>,
7061 buffer_position: language::Anchor,
7062 cx: &App,
7063 ) -> EditPredictionSettings {
7064 if !self.mode.is_full()
7065 || !self.show_edit_predictions_override.unwrap_or(true)
7066 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7067 {
7068 return EditPredictionSettings::Disabled;
7069 }
7070
7071 let buffer = buffer.read(cx);
7072
7073 let file = buffer.file();
7074
7075 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7076 return EditPredictionSettings::Disabled;
7077 };
7078
7079 let by_provider = matches!(
7080 self.menu_edit_predictions_policy,
7081 MenuEditPredictionsPolicy::ByProvider
7082 );
7083
7084 let show_in_menu = by_provider
7085 && self
7086 .edit_prediction_provider
7087 .as_ref()
7088 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7089
7090 let preview_requires_modifier =
7091 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7092
7093 EditPredictionSettings::Enabled {
7094 show_in_menu,
7095 preview_requires_modifier,
7096 }
7097 }
7098
7099 fn should_show_edit_predictions(&self) -> bool {
7100 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7101 }
7102
7103 pub fn edit_prediction_preview_is_active(&self) -> bool {
7104 matches!(
7105 self.edit_prediction_preview,
7106 EditPredictionPreview::Active { .. }
7107 )
7108 }
7109
7110 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7111 let cursor = self.selections.newest_anchor().head();
7112 if let Some((buffer, cursor_position)) =
7113 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7114 {
7115 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7116 } else {
7117 false
7118 }
7119 }
7120
7121 pub fn supports_minimap(&self, cx: &App) -> bool {
7122 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7123 }
7124
7125 fn edit_predictions_enabled_in_buffer(
7126 &self,
7127 buffer: &Entity<Buffer>,
7128 buffer_position: language::Anchor,
7129 cx: &App,
7130 ) -> bool {
7131 maybe!({
7132 if self.read_only(cx) {
7133 return Some(false);
7134 }
7135 let provider = self.edit_prediction_provider()?;
7136 if !provider.is_enabled(buffer, buffer_position, cx) {
7137 return Some(false);
7138 }
7139 let buffer = buffer.read(cx);
7140 let Some(file) = buffer.file() else {
7141 return Some(true);
7142 };
7143 let settings = all_language_settings(Some(file), cx);
7144 Some(settings.edit_predictions_enabled_for_file(file, cx))
7145 })
7146 .unwrap_or(false)
7147 }
7148
7149 fn cycle_edit_prediction(
7150 &mut self,
7151 direction: Direction,
7152 window: &mut Window,
7153 cx: &mut Context<Self>,
7154 ) -> Option<()> {
7155 let provider = self.edit_prediction_provider()?;
7156 let cursor = self.selections.newest_anchor().head();
7157 let (buffer, cursor_buffer_position) =
7158 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7159 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7160 return None;
7161 }
7162
7163 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7164 self.update_visible_edit_prediction(window, cx);
7165
7166 Some(())
7167 }
7168
7169 pub fn show_edit_prediction(
7170 &mut self,
7171 _: &ShowEditPrediction,
7172 window: &mut Window,
7173 cx: &mut Context<Self>,
7174 ) {
7175 if !self.has_active_edit_prediction() {
7176 self.refresh_edit_prediction(false, true, window, cx);
7177 return;
7178 }
7179
7180 self.update_visible_edit_prediction(window, cx);
7181 }
7182
7183 pub fn display_cursor_names(
7184 &mut self,
7185 _: &DisplayCursorNames,
7186 window: &mut Window,
7187 cx: &mut Context<Self>,
7188 ) {
7189 self.show_cursor_names(window, cx);
7190 }
7191
7192 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7193 self.show_cursor_names = true;
7194 cx.notify();
7195 cx.spawn_in(window, async move |this, cx| {
7196 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7197 this.update(cx, |this, cx| {
7198 this.show_cursor_names = false;
7199 cx.notify()
7200 })
7201 .ok()
7202 })
7203 .detach();
7204 }
7205
7206 pub fn next_edit_prediction(
7207 &mut self,
7208 _: &NextEditPrediction,
7209 window: &mut Window,
7210 cx: &mut Context<Self>,
7211 ) {
7212 if self.has_active_edit_prediction() {
7213 self.cycle_edit_prediction(Direction::Next, window, cx);
7214 } else {
7215 let is_copilot_disabled = self
7216 .refresh_edit_prediction(false, true, window, cx)
7217 .is_none();
7218 if is_copilot_disabled {
7219 cx.propagate();
7220 }
7221 }
7222 }
7223
7224 pub fn previous_edit_prediction(
7225 &mut self,
7226 _: &PreviousEditPrediction,
7227 window: &mut Window,
7228 cx: &mut Context<Self>,
7229 ) {
7230 if self.has_active_edit_prediction() {
7231 self.cycle_edit_prediction(Direction::Prev, window, cx);
7232 } else {
7233 let is_copilot_disabled = self
7234 .refresh_edit_prediction(false, true, window, cx)
7235 .is_none();
7236 if is_copilot_disabled {
7237 cx.propagate();
7238 }
7239 }
7240 }
7241
7242 pub fn accept_edit_prediction(
7243 &mut self,
7244 _: &AcceptEditPrediction,
7245 window: &mut Window,
7246 cx: &mut Context<Self>,
7247 ) {
7248 if self.show_edit_predictions_in_menu() {
7249 self.hide_context_menu(window, cx);
7250 }
7251
7252 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7253 return;
7254 };
7255
7256 match &active_edit_prediction.completion {
7257 EditPrediction::MoveWithin { target, .. } => {
7258 let target = *target;
7259
7260 if let Some(position_map) = &self.last_position_map {
7261 if position_map
7262 .visible_row_range
7263 .contains(&target.to_display_point(&position_map.snapshot).row())
7264 || !self.edit_prediction_requires_modifier()
7265 {
7266 self.unfold_ranges(&[target..target], true, false, cx);
7267 // Note that this is also done in vim's handler of the Tab action.
7268 self.change_selections(
7269 SelectionEffects::scroll(Autoscroll::newest()),
7270 window,
7271 cx,
7272 |selections| {
7273 selections.select_anchor_ranges([target..target]);
7274 },
7275 );
7276 self.clear_row_highlights::<EditPredictionPreview>();
7277
7278 self.edit_prediction_preview
7279 .set_previous_scroll_position(None);
7280 } else {
7281 self.edit_prediction_preview
7282 .set_previous_scroll_position(Some(
7283 position_map.snapshot.scroll_anchor,
7284 ));
7285
7286 self.highlight_rows::<EditPredictionPreview>(
7287 target..target,
7288 cx.theme().colors().editor_highlighted_line_background,
7289 RowHighlightOptions {
7290 autoscroll: true,
7291 ..Default::default()
7292 },
7293 cx,
7294 );
7295 self.request_autoscroll(Autoscroll::fit(), cx);
7296 }
7297 }
7298 }
7299 EditPrediction::MoveOutside { snapshot, target } => {
7300 if let Some(workspace) = self.workspace() {
7301 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7302 .detach_and_log_err(cx);
7303 }
7304 }
7305 EditPrediction::Edit { edits, .. } => {
7306 self.report_edit_prediction_event(
7307 active_edit_prediction.completion_id.clone(),
7308 true,
7309 cx,
7310 );
7311
7312 if let Some(provider) = self.edit_prediction_provider() {
7313 provider.accept(cx);
7314 }
7315
7316 // Store the transaction ID and selections before applying the edit
7317 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7318
7319 let snapshot = self.buffer.read(cx).snapshot(cx);
7320 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7321
7322 self.buffer.update(cx, |buffer, cx| {
7323 buffer.edit(edits.iter().cloned(), None, cx)
7324 });
7325
7326 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7327 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7328 });
7329
7330 let selections = self.selections.disjoint_anchors_arc();
7331 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7332 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7333 if has_new_transaction {
7334 self.selection_history
7335 .insert_transaction(transaction_id_now, selections);
7336 }
7337 }
7338
7339 self.update_visible_edit_prediction(window, cx);
7340 if self.active_edit_prediction.is_none() {
7341 self.refresh_edit_prediction(true, true, window, cx);
7342 }
7343
7344 cx.notify();
7345 }
7346 }
7347
7348 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7349 }
7350
7351 pub fn accept_partial_edit_prediction(
7352 &mut self,
7353 _: &AcceptPartialEditPrediction,
7354 window: &mut Window,
7355 cx: &mut Context<Self>,
7356 ) {
7357 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7358 return;
7359 };
7360 if self.selections.count() != 1 {
7361 return;
7362 }
7363
7364 match &active_edit_prediction.completion {
7365 EditPrediction::MoveWithin { target, .. } => {
7366 let target = *target;
7367 self.change_selections(
7368 SelectionEffects::scroll(Autoscroll::newest()),
7369 window,
7370 cx,
7371 |selections| {
7372 selections.select_anchor_ranges([target..target]);
7373 },
7374 );
7375 }
7376 EditPrediction::MoveOutside { snapshot, target } => {
7377 if let Some(workspace) = self.workspace() {
7378 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7379 .detach_and_log_err(cx);
7380 }
7381 }
7382 EditPrediction::Edit { edits, .. } => {
7383 self.report_edit_prediction_event(
7384 active_edit_prediction.completion_id.clone(),
7385 true,
7386 cx,
7387 );
7388
7389 // Find an insertion that starts at the cursor position.
7390 let snapshot = self.buffer.read(cx).snapshot(cx);
7391 let cursor_offset = self
7392 .selections
7393 .newest::<usize>(&self.display_snapshot(cx))
7394 .head();
7395 let insertion = edits.iter().find_map(|(range, text)| {
7396 let range = range.to_offset(&snapshot);
7397 if range.is_empty() && range.start == cursor_offset {
7398 Some(text)
7399 } else {
7400 None
7401 }
7402 });
7403
7404 if let Some(text) = insertion {
7405 let mut partial_completion = text
7406 .chars()
7407 .by_ref()
7408 .take_while(|c| c.is_alphabetic())
7409 .collect::<String>();
7410 if partial_completion.is_empty() {
7411 partial_completion = text
7412 .chars()
7413 .by_ref()
7414 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7415 .collect::<String>();
7416 }
7417
7418 cx.emit(EditorEvent::InputHandled {
7419 utf16_range_to_replace: None,
7420 text: partial_completion.clone().into(),
7421 });
7422
7423 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7424
7425 self.refresh_edit_prediction(true, true, window, cx);
7426 cx.notify();
7427 } else {
7428 self.accept_edit_prediction(&Default::default(), window, cx);
7429 }
7430 }
7431 }
7432 }
7433
7434 fn discard_edit_prediction(
7435 &mut self,
7436 should_report_edit_prediction_event: bool,
7437 cx: &mut Context<Self>,
7438 ) -> bool {
7439 if should_report_edit_prediction_event {
7440 let completion_id = self
7441 .active_edit_prediction
7442 .as_ref()
7443 .and_then(|active_completion| active_completion.completion_id.clone());
7444
7445 self.report_edit_prediction_event(completion_id, false, cx);
7446 }
7447
7448 if let Some(provider) = self.edit_prediction_provider() {
7449 provider.discard(cx);
7450 }
7451
7452 self.take_active_edit_prediction(cx)
7453 }
7454
7455 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7456 let Some(provider) = self.edit_prediction_provider() else {
7457 return;
7458 };
7459
7460 let Some((_, buffer, _)) = self
7461 .buffer
7462 .read(cx)
7463 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7464 else {
7465 return;
7466 };
7467
7468 let extension = buffer
7469 .read(cx)
7470 .file()
7471 .and_then(|file| Some(file.path().extension()?.to_string()));
7472
7473 let event_type = match accepted {
7474 true => "Edit Prediction Accepted",
7475 false => "Edit Prediction Discarded",
7476 };
7477 telemetry::event!(
7478 event_type,
7479 provider = provider.name(),
7480 prediction_id = id,
7481 suggestion_accepted = accepted,
7482 file_extension = extension,
7483 );
7484 }
7485
7486 fn open_editor_at_anchor(
7487 snapshot: &language::BufferSnapshot,
7488 target: language::Anchor,
7489 workspace: &Entity<Workspace>,
7490 window: &mut Window,
7491 cx: &mut App,
7492 ) -> Task<Result<()>> {
7493 workspace.update(cx, |workspace, cx| {
7494 let path = snapshot.file().map(|file| file.full_path(cx));
7495 let Some(path) =
7496 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7497 else {
7498 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7499 };
7500 let target = text::ToPoint::to_point(&target, snapshot);
7501 let item = workspace.open_path(path, None, true, window, cx);
7502 window.spawn(cx, async move |cx| {
7503 let Some(editor) = item.await?.downcast::<Editor>() else {
7504 return Ok(());
7505 };
7506 editor
7507 .update_in(cx, |editor, window, cx| {
7508 editor.go_to_singleton_buffer_point(target, window, cx);
7509 })
7510 .ok();
7511 anyhow::Ok(())
7512 })
7513 })
7514 }
7515
7516 pub fn has_active_edit_prediction(&self) -> bool {
7517 self.active_edit_prediction.is_some()
7518 }
7519
7520 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7521 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7522 return false;
7523 };
7524
7525 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7526 self.clear_highlights::<EditPredictionHighlight>(cx);
7527 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7528 true
7529 }
7530
7531 /// Returns true when we're displaying the edit prediction popover below the cursor
7532 /// like we are not previewing and the LSP autocomplete menu is visible
7533 /// or we are in `when_holding_modifier` mode.
7534 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7535 if self.edit_prediction_preview_is_active()
7536 || !self.show_edit_predictions_in_menu()
7537 || !self.edit_predictions_enabled()
7538 {
7539 return false;
7540 }
7541
7542 if self.has_visible_completions_menu() {
7543 return true;
7544 }
7545
7546 has_completion && self.edit_prediction_requires_modifier()
7547 }
7548
7549 fn handle_modifiers_changed(
7550 &mut self,
7551 modifiers: Modifiers,
7552 position_map: &PositionMap,
7553 window: &mut Window,
7554 cx: &mut Context<Self>,
7555 ) {
7556 if self.show_edit_predictions_in_menu() {
7557 self.update_edit_prediction_preview(&modifiers, window, cx);
7558 }
7559
7560 self.update_selection_mode(&modifiers, position_map, window, cx);
7561
7562 let mouse_position = window.mouse_position();
7563 if !position_map.text_hitbox.is_hovered(window) {
7564 return;
7565 }
7566
7567 self.update_hovered_link(
7568 position_map.point_for_position(mouse_position),
7569 &position_map.snapshot,
7570 modifiers,
7571 window,
7572 cx,
7573 )
7574 }
7575
7576 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7577 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7578 if invert {
7579 match multi_cursor_setting {
7580 MultiCursorModifier::Alt => modifiers.alt,
7581 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7582 }
7583 } else {
7584 match multi_cursor_setting {
7585 MultiCursorModifier::Alt => modifiers.secondary(),
7586 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7587 }
7588 }
7589 }
7590
7591 fn columnar_selection_mode(
7592 modifiers: &Modifiers,
7593 cx: &mut Context<Self>,
7594 ) -> Option<ColumnarMode> {
7595 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7596 if Self::multi_cursor_modifier(false, modifiers, cx) {
7597 Some(ColumnarMode::FromMouse)
7598 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7599 Some(ColumnarMode::FromSelection)
7600 } else {
7601 None
7602 }
7603 } else {
7604 None
7605 }
7606 }
7607
7608 fn update_selection_mode(
7609 &mut self,
7610 modifiers: &Modifiers,
7611 position_map: &PositionMap,
7612 window: &mut Window,
7613 cx: &mut Context<Self>,
7614 ) {
7615 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7616 return;
7617 };
7618 if self.selections.pending_anchor().is_none() {
7619 return;
7620 }
7621
7622 let mouse_position = window.mouse_position();
7623 let point_for_position = position_map.point_for_position(mouse_position);
7624 let position = point_for_position.previous_valid;
7625
7626 self.select(
7627 SelectPhase::BeginColumnar {
7628 position,
7629 reset: false,
7630 mode,
7631 goal_column: point_for_position.exact_unclipped.column(),
7632 },
7633 window,
7634 cx,
7635 );
7636 }
7637
7638 fn update_edit_prediction_preview(
7639 &mut self,
7640 modifiers: &Modifiers,
7641 window: &mut Window,
7642 cx: &mut Context<Self>,
7643 ) {
7644 let mut modifiers_held = false;
7645 if let Some(accept_keystroke) = self
7646 .accept_edit_prediction_keybind(false, window, cx)
7647 .keystroke()
7648 {
7649 modifiers_held = modifiers_held
7650 || (accept_keystroke.modifiers() == modifiers
7651 && accept_keystroke.modifiers().modified());
7652 };
7653 if let Some(accept_partial_keystroke) = self
7654 .accept_edit_prediction_keybind(true, window, cx)
7655 .keystroke()
7656 {
7657 modifiers_held = modifiers_held
7658 || (accept_partial_keystroke.modifiers() == modifiers
7659 && accept_partial_keystroke.modifiers().modified());
7660 }
7661
7662 if modifiers_held {
7663 if matches!(
7664 self.edit_prediction_preview,
7665 EditPredictionPreview::Inactive { .. }
7666 ) {
7667 self.edit_prediction_preview = EditPredictionPreview::Active {
7668 previous_scroll_position: None,
7669 since: Instant::now(),
7670 };
7671
7672 self.update_visible_edit_prediction(window, cx);
7673 cx.notify();
7674 }
7675 } else if let EditPredictionPreview::Active {
7676 previous_scroll_position,
7677 since,
7678 } = self.edit_prediction_preview
7679 {
7680 if let (Some(previous_scroll_position), Some(position_map)) =
7681 (previous_scroll_position, self.last_position_map.as_ref())
7682 {
7683 self.set_scroll_position(
7684 previous_scroll_position
7685 .scroll_position(&position_map.snapshot.display_snapshot),
7686 window,
7687 cx,
7688 );
7689 }
7690
7691 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7692 released_too_fast: since.elapsed() < Duration::from_millis(200),
7693 };
7694 self.clear_row_highlights::<EditPredictionPreview>();
7695 self.update_visible_edit_prediction(window, cx);
7696 cx.notify();
7697 }
7698 }
7699
7700 fn update_visible_edit_prediction(
7701 &mut self,
7702 _window: &mut Window,
7703 cx: &mut Context<Self>,
7704 ) -> Option<()> {
7705 if DisableAiSettings::get_global(cx).disable_ai {
7706 return None;
7707 }
7708
7709 if self.ime_transaction.is_some() {
7710 self.discard_edit_prediction(false, cx);
7711 return None;
7712 }
7713
7714 let selection = self.selections.newest_anchor();
7715 let cursor = selection.head();
7716 let multibuffer = self.buffer.read(cx).snapshot(cx);
7717 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7718 let excerpt_id = cursor.excerpt_id;
7719
7720 let show_in_menu = self.show_edit_predictions_in_menu();
7721 let completions_menu_has_precedence = !show_in_menu
7722 && (self.context_menu.borrow().is_some()
7723 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7724
7725 if completions_menu_has_precedence
7726 || !offset_selection.is_empty()
7727 || self
7728 .active_edit_prediction
7729 .as_ref()
7730 .is_some_and(|completion| {
7731 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7732 return false;
7733 };
7734 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7735 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7736 !invalidation_range.contains(&offset_selection.head())
7737 })
7738 {
7739 self.discard_edit_prediction(false, cx);
7740 return None;
7741 }
7742
7743 self.take_active_edit_prediction(cx);
7744 let Some(provider) = self.edit_prediction_provider() else {
7745 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7746 return None;
7747 };
7748
7749 let (buffer, cursor_buffer_position) =
7750 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7751
7752 self.edit_prediction_settings =
7753 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7754
7755 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7756
7757 if self.edit_prediction_indent_conflict {
7758 let cursor_point = cursor.to_point(&multibuffer);
7759
7760 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7761
7762 if let Some((_, indent)) = indents.iter().next()
7763 && indent.len == cursor_point.column
7764 {
7765 self.edit_prediction_indent_conflict = false;
7766 }
7767 }
7768
7769 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7770
7771 let (completion_id, edits, edit_preview) = match edit_prediction {
7772 edit_prediction::EditPrediction::Local {
7773 id,
7774 edits,
7775 edit_preview,
7776 } => (id, edits, edit_preview),
7777 edit_prediction::EditPrediction::Jump {
7778 id,
7779 snapshot,
7780 target,
7781 } => {
7782 self.stale_edit_prediction_in_menu = None;
7783 self.active_edit_prediction = Some(EditPredictionState {
7784 inlay_ids: vec![],
7785 completion: EditPrediction::MoveOutside { snapshot, target },
7786 completion_id: id,
7787 invalidation_range: None,
7788 });
7789 cx.notify();
7790 return Some(());
7791 }
7792 };
7793
7794 let edits = edits
7795 .into_iter()
7796 .flat_map(|(range, new_text)| {
7797 Some((
7798 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7799 new_text,
7800 ))
7801 })
7802 .collect::<Vec<_>>();
7803 if edits.is_empty() {
7804 return None;
7805 }
7806
7807 let first_edit_start = edits.first().unwrap().0.start;
7808 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7809 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7810
7811 let last_edit_end = edits.last().unwrap().0.end;
7812 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7813 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7814
7815 let cursor_row = cursor.to_point(&multibuffer).row;
7816
7817 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7818
7819 let mut inlay_ids = Vec::new();
7820 let invalidation_row_range;
7821 let move_invalidation_row_range = if cursor_row < edit_start_row {
7822 Some(cursor_row..edit_end_row)
7823 } else if cursor_row > edit_end_row {
7824 Some(edit_start_row..cursor_row)
7825 } else {
7826 None
7827 };
7828 let supports_jump = self
7829 .edit_prediction_provider
7830 .as_ref()
7831 .map(|provider| provider.provider.supports_jump_to_edit())
7832 .unwrap_or(true);
7833
7834 let is_move = supports_jump
7835 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7836 let completion = if is_move {
7837 invalidation_row_range =
7838 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7839 let target = first_edit_start;
7840 EditPrediction::MoveWithin { target, snapshot }
7841 } else {
7842 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7843 && !self.edit_predictions_hidden_for_vim_mode;
7844
7845 if show_completions_in_buffer {
7846 if edits
7847 .iter()
7848 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7849 {
7850 let mut inlays = Vec::new();
7851 for (range, new_text) in &edits {
7852 let inlay = Inlay::edit_prediction(
7853 post_inc(&mut self.next_inlay_id),
7854 range.start,
7855 new_text.as_str(),
7856 );
7857 inlay_ids.push(inlay.id);
7858 inlays.push(inlay);
7859 }
7860
7861 self.splice_inlays(&[], inlays, cx);
7862 } else {
7863 let background_color = cx.theme().status().deleted_background;
7864 self.highlight_text::<EditPredictionHighlight>(
7865 edits.iter().map(|(range, _)| range.clone()).collect(),
7866 HighlightStyle {
7867 background_color: Some(background_color),
7868 ..Default::default()
7869 },
7870 cx,
7871 );
7872 }
7873 }
7874
7875 invalidation_row_range = edit_start_row..edit_end_row;
7876
7877 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7878 if provider.show_tab_accept_marker() {
7879 EditDisplayMode::TabAccept
7880 } else {
7881 EditDisplayMode::Inline
7882 }
7883 } else {
7884 EditDisplayMode::DiffPopover
7885 };
7886
7887 EditPrediction::Edit {
7888 edits,
7889 edit_preview,
7890 display_mode,
7891 snapshot,
7892 }
7893 };
7894
7895 let invalidation_range = multibuffer
7896 .anchor_before(Point::new(invalidation_row_range.start, 0))
7897 ..multibuffer.anchor_after(Point::new(
7898 invalidation_row_range.end,
7899 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7900 ));
7901
7902 self.stale_edit_prediction_in_menu = None;
7903 self.active_edit_prediction = Some(EditPredictionState {
7904 inlay_ids,
7905 completion,
7906 completion_id,
7907 invalidation_range: Some(invalidation_range),
7908 });
7909
7910 cx.notify();
7911
7912 Some(())
7913 }
7914
7915 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7916 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7917 }
7918
7919 fn clear_tasks(&mut self) {
7920 self.tasks.clear()
7921 }
7922
7923 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7924 if self.tasks.insert(key, value).is_some() {
7925 // This case should hopefully be rare, but just in case...
7926 log::error!(
7927 "multiple different run targets found on a single line, only the last target will be rendered"
7928 )
7929 }
7930 }
7931
7932 /// Get all display points of breakpoints that will be rendered within editor
7933 ///
7934 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7935 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7936 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7937 fn active_breakpoints(
7938 &self,
7939 range: Range<DisplayRow>,
7940 window: &mut Window,
7941 cx: &mut Context<Self>,
7942 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7943 let mut breakpoint_display_points = HashMap::default();
7944
7945 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7946 return breakpoint_display_points;
7947 };
7948
7949 let snapshot = self.snapshot(window, cx);
7950
7951 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7952 let Some(project) = self.project() else {
7953 return breakpoint_display_points;
7954 };
7955
7956 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7957 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7958
7959 for (buffer_snapshot, range, excerpt_id) in
7960 multi_buffer_snapshot.range_to_buffer_ranges(range)
7961 {
7962 let Some(buffer) = project
7963 .read(cx)
7964 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7965 else {
7966 continue;
7967 };
7968 let breakpoints = breakpoint_store.read(cx).breakpoints(
7969 &buffer,
7970 Some(
7971 buffer_snapshot.anchor_before(range.start)
7972 ..buffer_snapshot.anchor_after(range.end),
7973 ),
7974 buffer_snapshot,
7975 cx,
7976 );
7977 for (breakpoint, state) in breakpoints {
7978 let multi_buffer_anchor =
7979 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7980 let position = multi_buffer_anchor
7981 .to_point(&multi_buffer_snapshot)
7982 .to_display_point(&snapshot);
7983
7984 breakpoint_display_points.insert(
7985 position.row(),
7986 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7987 );
7988 }
7989 }
7990
7991 breakpoint_display_points
7992 }
7993
7994 fn breakpoint_context_menu(
7995 &self,
7996 anchor: Anchor,
7997 window: &mut Window,
7998 cx: &mut Context<Self>,
7999 ) -> Entity<ui::ContextMenu> {
8000 let weak_editor = cx.weak_entity();
8001 let focus_handle = self.focus_handle(cx);
8002
8003 let row = self
8004 .buffer
8005 .read(cx)
8006 .snapshot(cx)
8007 .summary_for_anchor::<Point>(&anchor)
8008 .row;
8009
8010 let breakpoint = self
8011 .breakpoint_at_row(row, window, cx)
8012 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8013
8014 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8015 "Edit Log Breakpoint"
8016 } else {
8017 "Set Log Breakpoint"
8018 };
8019
8020 let condition_breakpoint_msg = if breakpoint
8021 .as_ref()
8022 .is_some_and(|bp| bp.1.condition.is_some())
8023 {
8024 "Edit Condition Breakpoint"
8025 } else {
8026 "Set Condition Breakpoint"
8027 };
8028
8029 let hit_condition_breakpoint_msg = if breakpoint
8030 .as_ref()
8031 .is_some_and(|bp| bp.1.hit_condition.is_some())
8032 {
8033 "Edit Hit Condition Breakpoint"
8034 } else {
8035 "Set Hit Condition Breakpoint"
8036 };
8037
8038 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8039 "Unset Breakpoint"
8040 } else {
8041 "Set Breakpoint"
8042 };
8043
8044 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8045
8046 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8047 BreakpointState::Enabled => Some("Disable"),
8048 BreakpointState::Disabled => Some("Enable"),
8049 });
8050
8051 let (anchor, breakpoint) =
8052 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8053
8054 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8055 menu.on_blur_subscription(Subscription::new(|| {}))
8056 .context(focus_handle)
8057 .when(run_to_cursor, |this| {
8058 let weak_editor = weak_editor.clone();
8059 this.entry("Run to cursor", None, move |window, cx| {
8060 weak_editor
8061 .update(cx, |editor, cx| {
8062 editor.change_selections(
8063 SelectionEffects::no_scroll(),
8064 window,
8065 cx,
8066 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8067 );
8068 })
8069 .ok();
8070
8071 window.dispatch_action(Box::new(RunToCursor), cx);
8072 })
8073 .separator()
8074 })
8075 .when_some(toggle_state_msg, |this, msg| {
8076 this.entry(msg, None, {
8077 let weak_editor = weak_editor.clone();
8078 let breakpoint = breakpoint.clone();
8079 move |_window, cx| {
8080 weak_editor
8081 .update(cx, |this, cx| {
8082 this.edit_breakpoint_at_anchor(
8083 anchor,
8084 breakpoint.as_ref().clone(),
8085 BreakpointEditAction::InvertState,
8086 cx,
8087 );
8088 })
8089 .log_err();
8090 }
8091 })
8092 })
8093 .entry(set_breakpoint_msg, None, {
8094 let weak_editor = weak_editor.clone();
8095 let breakpoint = breakpoint.clone();
8096 move |_window, cx| {
8097 weak_editor
8098 .update(cx, |this, cx| {
8099 this.edit_breakpoint_at_anchor(
8100 anchor,
8101 breakpoint.as_ref().clone(),
8102 BreakpointEditAction::Toggle,
8103 cx,
8104 );
8105 })
8106 .log_err();
8107 }
8108 })
8109 .entry(log_breakpoint_msg, None, {
8110 let breakpoint = breakpoint.clone();
8111 let weak_editor = weak_editor.clone();
8112 move |window, cx| {
8113 weak_editor
8114 .update(cx, |this, cx| {
8115 this.add_edit_breakpoint_block(
8116 anchor,
8117 breakpoint.as_ref(),
8118 BreakpointPromptEditAction::Log,
8119 window,
8120 cx,
8121 );
8122 })
8123 .log_err();
8124 }
8125 })
8126 .entry(condition_breakpoint_msg, None, {
8127 let breakpoint = breakpoint.clone();
8128 let weak_editor = weak_editor.clone();
8129 move |window, cx| {
8130 weak_editor
8131 .update(cx, |this, cx| {
8132 this.add_edit_breakpoint_block(
8133 anchor,
8134 breakpoint.as_ref(),
8135 BreakpointPromptEditAction::Condition,
8136 window,
8137 cx,
8138 );
8139 })
8140 .log_err();
8141 }
8142 })
8143 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8144 weak_editor
8145 .update(cx, |this, cx| {
8146 this.add_edit_breakpoint_block(
8147 anchor,
8148 breakpoint.as_ref(),
8149 BreakpointPromptEditAction::HitCondition,
8150 window,
8151 cx,
8152 );
8153 })
8154 .log_err();
8155 })
8156 })
8157 }
8158
8159 fn render_breakpoint(
8160 &self,
8161 position: Anchor,
8162 row: DisplayRow,
8163 breakpoint: &Breakpoint,
8164 state: Option<BreakpointSessionState>,
8165 cx: &mut Context<Self>,
8166 ) -> IconButton {
8167 let is_rejected = state.is_some_and(|s| !s.verified);
8168 // Is it a breakpoint that shows up when hovering over gutter?
8169 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8170 (false, false),
8171 |PhantomBreakpointIndicator {
8172 is_active,
8173 display_row,
8174 collides_with_existing_breakpoint,
8175 }| {
8176 (
8177 is_active && display_row == row,
8178 collides_with_existing_breakpoint,
8179 )
8180 },
8181 );
8182
8183 let (color, icon) = {
8184 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8185 (false, false) => ui::IconName::DebugBreakpoint,
8186 (true, false) => ui::IconName::DebugLogBreakpoint,
8187 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8188 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8189 };
8190
8191 let color = if is_phantom {
8192 Color::Hint
8193 } else if is_rejected {
8194 Color::Disabled
8195 } else {
8196 Color::Debugger
8197 };
8198
8199 (color, icon)
8200 };
8201
8202 let breakpoint = Arc::from(breakpoint.clone());
8203
8204 let alt_as_text = gpui::Keystroke {
8205 modifiers: Modifiers::secondary_key(),
8206 ..Default::default()
8207 };
8208 let primary_action_text = if breakpoint.is_disabled() {
8209 "Enable breakpoint"
8210 } else if is_phantom && !collides_with_existing {
8211 "Set breakpoint"
8212 } else {
8213 "Unset breakpoint"
8214 };
8215 let focus_handle = self.focus_handle.clone();
8216
8217 let meta = if is_rejected {
8218 SharedString::from("No executable code is associated with this line.")
8219 } else if collides_with_existing && !breakpoint.is_disabled() {
8220 SharedString::from(format!(
8221 "{alt_as_text}-click to disable,\nright-click for more options."
8222 ))
8223 } else {
8224 SharedString::from("Right-click for more options.")
8225 };
8226 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8227 .icon_size(IconSize::XSmall)
8228 .size(ui::ButtonSize::None)
8229 .when(is_rejected, |this| {
8230 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8231 })
8232 .icon_color(color)
8233 .style(ButtonStyle::Transparent)
8234 .on_click(cx.listener({
8235 move |editor, event: &ClickEvent, window, cx| {
8236 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8237 BreakpointEditAction::InvertState
8238 } else {
8239 BreakpointEditAction::Toggle
8240 };
8241
8242 window.focus(&editor.focus_handle(cx));
8243 editor.edit_breakpoint_at_anchor(
8244 position,
8245 breakpoint.as_ref().clone(),
8246 edit_action,
8247 cx,
8248 );
8249 }
8250 }))
8251 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8252 editor.set_breakpoint_context_menu(
8253 row,
8254 Some(position),
8255 event.position(),
8256 window,
8257 cx,
8258 );
8259 }))
8260 .tooltip(move |_window, cx| {
8261 Tooltip::with_meta_in(
8262 primary_action_text,
8263 Some(&ToggleBreakpoint),
8264 meta.clone(),
8265 &focus_handle,
8266 cx,
8267 )
8268 })
8269 }
8270
8271 fn build_tasks_context(
8272 project: &Entity<Project>,
8273 buffer: &Entity<Buffer>,
8274 buffer_row: u32,
8275 tasks: &Arc<RunnableTasks>,
8276 cx: &mut Context<Self>,
8277 ) -> Task<Option<task::TaskContext>> {
8278 let position = Point::new(buffer_row, tasks.column);
8279 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8280 let location = Location {
8281 buffer: buffer.clone(),
8282 range: range_start..range_start,
8283 };
8284 // Fill in the environmental variables from the tree-sitter captures
8285 let mut captured_task_variables = TaskVariables::default();
8286 for (capture_name, value) in tasks.extra_variables.clone() {
8287 captured_task_variables.insert(
8288 task::VariableName::Custom(capture_name.into()),
8289 value.clone(),
8290 );
8291 }
8292 project.update(cx, |project, cx| {
8293 project.task_store().update(cx, |task_store, cx| {
8294 task_store.task_context_for_location(captured_task_variables, location, cx)
8295 })
8296 })
8297 }
8298
8299 pub fn spawn_nearest_task(
8300 &mut self,
8301 action: &SpawnNearestTask,
8302 window: &mut Window,
8303 cx: &mut Context<Self>,
8304 ) {
8305 let Some((workspace, _)) = self.workspace.clone() else {
8306 return;
8307 };
8308 let Some(project) = self.project.clone() else {
8309 return;
8310 };
8311
8312 // Try to find a closest, enclosing node using tree-sitter that has a task
8313 let Some((buffer, buffer_row, tasks)) = self
8314 .find_enclosing_node_task(cx)
8315 // Or find the task that's closest in row-distance.
8316 .or_else(|| self.find_closest_task(cx))
8317 else {
8318 return;
8319 };
8320
8321 let reveal_strategy = action.reveal;
8322 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8323 cx.spawn_in(window, async move |_, cx| {
8324 let context = task_context.await?;
8325 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8326
8327 let resolved = &mut resolved_task.resolved;
8328 resolved.reveal = reveal_strategy;
8329
8330 workspace
8331 .update_in(cx, |workspace, window, cx| {
8332 workspace.schedule_resolved_task(
8333 task_source_kind,
8334 resolved_task,
8335 false,
8336 window,
8337 cx,
8338 );
8339 })
8340 .ok()
8341 })
8342 .detach();
8343 }
8344
8345 fn find_closest_task(
8346 &mut self,
8347 cx: &mut Context<Self>,
8348 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8349 let cursor_row = self
8350 .selections
8351 .newest_adjusted(&self.display_snapshot(cx))
8352 .head()
8353 .row;
8354
8355 let ((buffer_id, row), tasks) = self
8356 .tasks
8357 .iter()
8358 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8359
8360 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8361 let tasks = Arc::new(tasks.to_owned());
8362 Some((buffer, *row, tasks))
8363 }
8364
8365 fn find_enclosing_node_task(
8366 &mut self,
8367 cx: &mut Context<Self>,
8368 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8369 let snapshot = self.buffer.read(cx).snapshot(cx);
8370 let offset = self
8371 .selections
8372 .newest::<usize>(&self.display_snapshot(cx))
8373 .head();
8374 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8375 let buffer_id = excerpt.buffer().remote_id();
8376
8377 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8378 let mut cursor = layer.node().walk();
8379
8380 while cursor.goto_first_child_for_byte(offset).is_some() {
8381 if cursor.node().end_byte() == offset {
8382 cursor.goto_next_sibling();
8383 }
8384 }
8385
8386 // Ascend to the smallest ancestor that contains the range and has a task.
8387 loop {
8388 let node = cursor.node();
8389 let node_range = node.byte_range();
8390 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8391
8392 // Check if this node contains our offset
8393 if node_range.start <= offset && node_range.end >= offset {
8394 // If it contains offset, check for task
8395 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8396 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8397 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8398 }
8399 }
8400
8401 if !cursor.goto_parent() {
8402 break;
8403 }
8404 }
8405 None
8406 }
8407
8408 fn render_run_indicator(
8409 &self,
8410 _style: &EditorStyle,
8411 is_active: bool,
8412 row: DisplayRow,
8413 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8414 cx: &mut Context<Self>,
8415 ) -> IconButton {
8416 let color = Color::Muted;
8417 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8418
8419 IconButton::new(
8420 ("run_indicator", row.0 as usize),
8421 ui::IconName::PlayOutlined,
8422 )
8423 .shape(ui::IconButtonShape::Square)
8424 .icon_size(IconSize::XSmall)
8425 .icon_color(color)
8426 .toggle_state(is_active)
8427 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8428 let quick_launch = match e {
8429 ClickEvent::Keyboard(_) => true,
8430 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8431 };
8432
8433 window.focus(&editor.focus_handle(cx));
8434 editor.toggle_code_actions(
8435 &ToggleCodeActions {
8436 deployed_from: Some(CodeActionSource::RunMenu(row)),
8437 quick_launch,
8438 },
8439 window,
8440 cx,
8441 );
8442 }))
8443 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8444 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8445 }))
8446 }
8447
8448 pub fn context_menu_visible(&self) -> bool {
8449 !self.edit_prediction_preview_is_active()
8450 && self
8451 .context_menu
8452 .borrow()
8453 .as_ref()
8454 .is_some_and(|menu| menu.visible())
8455 }
8456
8457 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8458 self.context_menu
8459 .borrow()
8460 .as_ref()
8461 .map(|menu| menu.origin())
8462 }
8463
8464 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8465 self.context_menu_options = Some(options);
8466 }
8467
8468 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8469 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8470
8471 fn render_edit_prediction_popover(
8472 &mut self,
8473 text_bounds: &Bounds<Pixels>,
8474 content_origin: gpui::Point<Pixels>,
8475 right_margin: Pixels,
8476 editor_snapshot: &EditorSnapshot,
8477 visible_row_range: Range<DisplayRow>,
8478 scroll_top: ScrollOffset,
8479 scroll_bottom: ScrollOffset,
8480 line_layouts: &[LineWithInvisibles],
8481 line_height: Pixels,
8482 scroll_position: gpui::Point<ScrollOffset>,
8483 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8484 newest_selection_head: Option<DisplayPoint>,
8485 editor_width: Pixels,
8486 style: &EditorStyle,
8487 window: &mut Window,
8488 cx: &mut App,
8489 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8490 if self.mode().is_minimap() {
8491 return None;
8492 }
8493 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8494
8495 if self.edit_prediction_visible_in_cursor_popover(true) {
8496 return None;
8497 }
8498
8499 match &active_edit_prediction.completion {
8500 EditPrediction::MoveWithin { target, .. } => {
8501 let target_display_point = target.to_display_point(editor_snapshot);
8502
8503 if self.edit_prediction_requires_modifier() {
8504 if !self.edit_prediction_preview_is_active() {
8505 return None;
8506 }
8507
8508 self.render_edit_prediction_modifier_jump_popover(
8509 text_bounds,
8510 content_origin,
8511 visible_row_range,
8512 line_layouts,
8513 line_height,
8514 scroll_pixel_position,
8515 newest_selection_head,
8516 target_display_point,
8517 window,
8518 cx,
8519 )
8520 } else {
8521 self.render_edit_prediction_eager_jump_popover(
8522 text_bounds,
8523 content_origin,
8524 editor_snapshot,
8525 visible_row_range,
8526 scroll_top,
8527 scroll_bottom,
8528 line_height,
8529 scroll_pixel_position,
8530 target_display_point,
8531 editor_width,
8532 window,
8533 cx,
8534 )
8535 }
8536 }
8537 EditPrediction::Edit {
8538 display_mode: EditDisplayMode::Inline,
8539 ..
8540 } => None,
8541 EditPrediction::Edit {
8542 display_mode: EditDisplayMode::TabAccept,
8543 edits,
8544 ..
8545 } => {
8546 let range = &edits.first()?.0;
8547 let target_display_point = range.end.to_display_point(editor_snapshot);
8548
8549 self.render_edit_prediction_end_of_line_popover(
8550 "Accept",
8551 editor_snapshot,
8552 visible_row_range,
8553 target_display_point,
8554 line_height,
8555 scroll_pixel_position,
8556 content_origin,
8557 editor_width,
8558 window,
8559 cx,
8560 )
8561 }
8562 EditPrediction::Edit {
8563 edits,
8564 edit_preview,
8565 display_mode: EditDisplayMode::DiffPopover,
8566 snapshot,
8567 } => self.render_edit_prediction_diff_popover(
8568 text_bounds,
8569 content_origin,
8570 right_margin,
8571 editor_snapshot,
8572 visible_row_range,
8573 line_layouts,
8574 line_height,
8575 scroll_position,
8576 scroll_pixel_position,
8577 newest_selection_head,
8578 editor_width,
8579 style,
8580 edits,
8581 edit_preview,
8582 snapshot,
8583 window,
8584 cx,
8585 ),
8586 EditPrediction::MoveOutside { snapshot, .. } => {
8587 let file_name = snapshot
8588 .file()
8589 .map(|file| file.file_name(cx))
8590 .unwrap_or("untitled");
8591 let mut element = self
8592 .render_edit_prediction_line_popover(
8593 format!("Jump to {file_name}"),
8594 Some(IconName::ZedPredict),
8595 window,
8596 cx,
8597 )
8598 .into_any();
8599
8600 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8601 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8602 let origin_y = text_bounds.size.height - size.height - px(30.);
8603 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8604 element.prepaint_at(origin, window, cx);
8605
8606 Some((element, origin))
8607 }
8608 }
8609 }
8610
8611 fn render_edit_prediction_modifier_jump_popover(
8612 &mut self,
8613 text_bounds: &Bounds<Pixels>,
8614 content_origin: gpui::Point<Pixels>,
8615 visible_row_range: Range<DisplayRow>,
8616 line_layouts: &[LineWithInvisibles],
8617 line_height: Pixels,
8618 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8619 newest_selection_head: Option<DisplayPoint>,
8620 target_display_point: DisplayPoint,
8621 window: &mut Window,
8622 cx: &mut App,
8623 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8624 let scrolled_content_origin =
8625 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8626
8627 const SCROLL_PADDING_Y: Pixels = px(12.);
8628
8629 if target_display_point.row() < visible_row_range.start {
8630 return self.render_edit_prediction_scroll_popover(
8631 |_| SCROLL_PADDING_Y,
8632 IconName::ArrowUp,
8633 visible_row_range,
8634 line_layouts,
8635 newest_selection_head,
8636 scrolled_content_origin,
8637 window,
8638 cx,
8639 );
8640 } else if target_display_point.row() >= visible_row_range.end {
8641 return self.render_edit_prediction_scroll_popover(
8642 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8643 IconName::ArrowDown,
8644 visible_row_range,
8645 line_layouts,
8646 newest_selection_head,
8647 scrolled_content_origin,
8648 window,
8649 cx,
8650 );
8651 }
8652
8653 const POLE_WIDTH: Pixels = px(2.);
8654
8655 let line_layout =
8656 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8657 let target_column = target_display_point.column() as usize;
8658
8659 let target_x = line_layout.x_for_index(target_column);
8660 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8661 - scroll_pixel_position.y;
8662
8663 let flag_on_right = target_x < text_bounds.size.width / 2.;
8664
8665 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8666 border_color.l += 0.001;
8667
8668 let mut element = v_flex()
8669 .items_end()
8670 .when(flag_on_right, |el| el.items_start())
8671 .child(if flag_on_right {
8672 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8673 .rounded_bl(px(0.))
8674 .rounded_tl(px(0.))
8675 .border_l_2()
8676 .border_color(border_color)
8677 } else {
8678 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8679 .rounded_br(px(0.))
8680 .rounded_tr(px(0.))
8681 .border_r_2()
8682 .border_color(border_color)
8683 })
8684 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8685 .into_any();
8686
8687 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8688
8689 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8690 - point(
8691 if flag_on_right {
8692 POLE_WIDTH
8693 } else {
8694 size.width - POLE_WIDTH
8695 },
8696 size.height - line_height,
8697 );
8698
8699 origin.x = origin.x.max(content_origin.x);
8700
8701 element.prepaint_at(origin, window, cx);
8702
8703 Some((element, origin))
8704 }
8705
8706 fn render_edit_prediction_scroll_popover(
8707 &mut self,
8708 to_y: impl Fn(Size<Pixels>) -> Pixels,
8709 scroll_icon: IconName,
8710 visible_row_range: Range<DisplayRow>,
8711 line_layouts: &[LineWithInvisibles],
8712 newest_selection_head: Option<DisplayPoint>,
8713 scrolled_content_origin: gpui::Point<Pixels>,
8714 window: &mut Window,
8715 cx: &mut App,
8716 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8717 let mut element = self
8718 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8719 .into_any();
8720
8721 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8722
8723 let cursor = newest_selection_head?;
8724 let cursor_row_layout =
8725 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8726 let cursor_column = cursor.column() as usize;
8727
8728 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8729
8730 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8731
8732 element.prepaint_at(origin, window, cx);
8733 Some((element, origin))
8734 }
8735
8736 fn render_edit_prediction_eager_jump_popover(
8737 &mut self,
8738 text_bounds: &Bounds<Pixels>,
8739 content_origin: gpui::Point<Pixels>,
8740 editor_snapshot: &EditorSnapshot,
8741 visible_row_range: Range<DisplayRow>,
8742 scroll_top: ScrollOffset,
8743 scroll_bottom: ScrollOffset,
8744 line_height: Pixels,
8745 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8746 target_display_point: DisplayPoint,
8747 editor_width: Pixels,
8748 window: &mut Window,
8749 cx: &mut App,
8750 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8751 if target_display_point.row().as_f64() < scroll_top {
8752 let mut element = self
8753 .render_edit_prediction_line_popover(
8754 "Jump to Edit",
8755 Some(IconName::ArrowUp),
8756 window,
8757 cx,
8758 )
8759 .into_any();
8760
8761 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8762 let offset = point(
8763 (text_bounds.size.width - size.width) / 2.,
8764 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8765 );
8766
8767 let origin = text_bounds.origin + offset;
8768 element.prepaint_at(origin, window, cx);
8769 Some((element, origin))
8770 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8771 let mut element = self
8772 .render_edit_prediction_line_popover(
8773 "Jump to Edit",
8774 Some(IconName::ArrowDown),
8775 window,
8776 cx,
8777 )
8778 .into_any();
8779
8780 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8781 let offset = point(
8782 (text_bounds.size.width - size.width) / 2.,
8783 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8784 );
8785
8786 let origin = text_bounds.origin + offset;
8787 element.prepaint_at(origin, window, cx);
8788 Some((element, origin))
8789 } else {
8790 self.render_edit_prediction_end_of_line_popover(
8791 "Jump to Edit",
8792 editor_snapshot,
8793 visible_row_range,
8794 target_display_point,
8795 line_height,
8796 scroll_pixel_position,
8797 content_origin,
8798 editor_width,
8799 window,
8800 cx,
8801 )
8802 }
8803 }
8804
8805 fn render_edit_prediction_end_of_line_popover(
8806 self: &mut Editor,
8807 label: &'static str,
8808 editor_snapshot: &EditorSnapshot,
8809 visible_row_range: Range<DisplayRow>,
8810 target_display_point: DisplayPoint,
8811 line_height: Pixels,
8812 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8813 content_origin: gpui::Point<Pixels>,
8814 editor_width: Pixels,
8815 window: &mut Window,
8816 cx: &mut App,
8817 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8818 let target_line_end = DisplayPoint::new(
8819 target_display_point.row(),
8820 editor_snapshot.line_len(target_display_point.row()),
8821 );
8822
8823 let mut element = self
8824 .render_edit_prediction_line_popover(label, None, window, cx)
8825 .into_any();
8826
8827 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8828
8829 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8830
8831 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8832 let mut origin = start_point
8833 + line_origin
8834 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8835 origin.x = origin.x.max(content_origin.x);
8836
8837 let max_x = content_origin.x + editor_width - size.width;
8838
8839 if origin.x > max_x {
8840 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8841
8842 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8843 origin.y += offset;
8844 IconName::ArrowUp
8845 } else {
8846 origin.y -= offset;
8847 IconName::ArrowDown
8848 };
8849
8850 element = self
8851 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8852 .into_any();
8853
8854 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8855
8856 origin.x = content_origin.x + editor_width - size.width - px(2.);
8857 }
8858
8859 element.prepaint_at(origin, window, cx);
8860 Some((element, origin))
8861 }
8862
8863 fn render_edit_prediction_diff_popover(
8864 self: &Editor,
8865 text_bounds: &Bounds<Pixels>,
8866 content_origin: gpui::Point<Pixels>,
8867 right_margin: Pixels,
8868 editor_snapshot: &EditorSnapshot,
8869 visible_row_range: Range<DisplayRow>,
8870 line_layouts: &[LineWithInvisibles],
8871 line_height: Pixels,
8872 scroll_position: gpui::Point<ScrollOffset>,
8873 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8874 newest_selection_head: Option<DisplayPoint>,
8875 editor_width: Pixels,
8876 style: &EditorStyle,
8877 edits: &Vec<(Range<Anchor>, String)>,
8878 edit_preview: &Option<language::EditPreview>,
8879 snapshot: &language::BufferSnapshot,
8880 window: &mut Window,
8881 cx: &mut App,
8882 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8883 let edit_start = edits
8884 .first()
8885 .unwrap()
8886 .0
8887 .start
8888 .to_display_point(editor_snapshot);
8889 let edit_end = edits
8890 .last()
8891 .unwrap()
8892 .0
8893 .end
8894 .to_display_point(editor_snapshot);
8895
8896 let is_visible = visible_row_range.contains(&edit_start.row())
8897 || visible_row_range.contains(&edit_end.row());
8898 if !is_visible {
8899 return None;
8900 }
8901
8902 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8903 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8904 } else {
8905 // Fallback for providers without edit_preview
8906 crate::edit_prediction_fallback_text(edits, cx)
8907 };
8908
8909 let styled_text = highlighted_edits.to_styled_text(&style.text);
8910 let line_count = highlighted_edits.text.lines().count();
8911
8912 const BORDER_WIDTH: Pixels = px(1.);
8913
8914 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8915 let has_keybind = keybind.is_some();
8916
8917 let mut element = h_flex()
8918 .items_start()
8919 .child(
8920 h_flex()
8921 .bg(cx.theme().colors().editor_background)
8922 .border(BORDER_WIDTH)
8923 .shadow_xs()
8924 .border_color(cx.theme().colors().border)
8925 .rounded_l_lg()
8926 .when(line_count > 1, |el| el.rounded_br_lg())
8927 .pr_1()
8928 .child(styled_text),
8929 )
8930 .child(
8931 h_flex()
8932 .h(line_height + BORDER_WIDTH * 2.)
8933 .px_1p5()
8934 .gap_1()
8935 // Workaround: For some reason, there's a gap if we don't do this
8936 .ml(-BORDER_WIDTH)
8937 .shadow(vec![gpui::BoxShadow {
8938 color: gpui::black().opacity(0.05),
8939 offset: point(px(1.), px(1.)),
8940 blur_radius: px(2.),
8941 spread_radius: px(0.),
8942 }])
8943 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8944 .border(BORDER_WIDTH)
8945 .border_color(cx.theme().colors().border)
8946 .rounded_r_lg()
8947 .id("edit_prediction_diff_popover_keybind")
8948 .when(!has_keybind, |el| {
8949 let status_colors = cx.theme().status();
8950
8951 el.bg(status_colors.error_background)
8952 .border_color(status_colors.error.opacity(0.6))
8953 .child(Icon::new(IconName::Info).color(Color::Error))
8954 .cursor_default()
8955 .hoverable_tooltip(move |_window, cx| {
8956 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8957 })
8958 })
8959 .children(keybind),
8960 )
8961 .into_any();
8962
8963 let longest_row =
8964 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8965 let longest_line_width = if visible_row_range.contains(&longest_row) {
8966 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8967 } else {
8968 layout_line(
8969 longest_row,
8970 editor_snapshot,
8971 style,
8972 editor_width,
8973 |_| false,
8974 window,
8975 cx,
8976 )
8977 .width
8978 };
8979
8980 let viewport_bounds =
8981 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8982 right: -right_margin,
8983 ..Default::default()
8984 });
8985
8986 let x_after_longest = Pixels::from(
8987 ScrollPixelOffset::from(
8988 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
8989 ) - scroll_pixel_position.x,
8990 );
8991
8992 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8993
8994 // Fully visible if it can be displayed within the window (allow overlapping other
8995 // panes). However, this is only allowed if the popover starts within text_bounds.
8996 let can_position_to_the_right = x_after_longest < text_bounds.right()
8997 && x_after_longest + element_bounds.width < viewport_bounds.right();
8998
8999 let mut origin = if can_position_to_the_right {
9000 point(
9001 x_after_longest,
9002 text_bounds.origin.y
9003 + Pixels::from(
9004 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9005 - scroll_pixel_position.y,
9006 ),
9007 )
9008 } else {
9009 let cursor_row = newest_selection_head.map(|head| head.row());
9010 let above_edit = edit_start
9011 .row()
9012 .0
9013 .checked_sub(line_count as u32)
9014 .map(DisplayRow);
9015 let below_edit = Some(edit_end.row() + 1);
9016 let above_cursor =
9017 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9018 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9019
9020 // Place the edit popover adjacent to the edit if there is a location
9021 // available that is onscreen and does not obscure the cursor. Otherwise,
9022 // place it adjacent to the cursor.
9023 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9024 .into_iter()
9025 .flatten()
9026 .find(|&start_row| {
9027 let end_row = start_row + line_count as u32;
9028 visible_row_range.contains(&start_row)
9029 && visible_row_range.contains(&end_row)
9030 && cursor_row
9031 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9032 })?;
9033
9034 content_origin
9035 + point(
9036 Pixels::from(-scroll_pixel_position.x),
9037 Pixels::from(
9038 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9039 ),
9040 )
9041 };
9042
9043 origin.x -= BORDER_WIDTH;
9044
9045 window.defer_draw(element, origin, 1);
9046
9047 // Do not return an element, since it will already be drawn due to defer_draw.
9048 None
9049 }
9050
9051 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9052 px(30.)
9053 }
9054
9055 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9056 if self.read_only(cx) {
9057 cx.theme().players().read_only()
9058 } else {
9059 self.style.as_ref().unwrap().local_player
9060 }
9061 }
9062
9063 fn render_edit_prediction_accept_keybind(
9064 &self,
9065 window: &mut Window,
9066 cx: &mut App,
9067 ) -> Option<AnyElement> {
9068 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9069 let accept_keystroke = accept_binding.keystroke()?;
9070
9071 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9072
9073 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9074 Color::Accent
9075 } else {
9076 Color::Muted
9077 };
9078
9079 h_flex()
9080 .px_0p5()
9081 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9082 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9083 .text_size(TextSize::XSmall.rems(cx))
9084 .child(h_flex().children(ui::render_modifiers(
9085 accept_keystroke.modifiers(),
9086 PlatformStyle::platform(),
9087 Some(modifiers_color),
9088 Some(IconSize::XSmall.rems().into()),
9089 true,
9090 )))
9091 .when(is_platform_style_mac, |parent| {
9092 parent.child(accept_keystroke.key().to_string())
9093 })
9094 .when(!is_platform_style_mac, |parent| {
9095 parent.child(
9096 Key::new(
9097 util::capitalize(accept_keystroke.key()),
9098 Some(Color::Default),
9099 )
9100 .size(Some(IconSize::XSmall.rems().into())),
9101 )
9102 })
9103 .into_any()
9104 .into()
9105 }
9106
9107 fn render_edit_prediction_line_popover(
9108 &self,
9109 label: impl Into<SharedString>,
9110 icon: Option<IconName>,
9111 window: &mut Window,
9112 cx: &mut App,
9113 ) -> Stateful<Div> {
9114 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9115
9116 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9117 let has_keybind = keybind.is_some();
9118
9119 h_flex()
9120 .id("ep-line-popover")
9121 .py_0p5()
9122 .pl_1()
9123 .pr(padding_right)
9124 .gap_1()
9125 .rounded_md()
9126 .border_1()
9127 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9128 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9129 .shadow_xs()
9130 .when(!has_keybind, |el| {
9131 let status_colors = cx.theme().status();
9132
9133 el.bg(status_colors.error_background)
9134 .border_color(status_colors.error.opacity(0.6))
9135 .pl_2()
9136 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9137 .cursor_default()
9138 .hoverable_tooltip(move |_window, cx| {
9139 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9140 })
9141 })
9142 .children(keybind)
9143 .child(
9144 Label::new(label)
9145 .size(LabelSize::Small)
9146 .when(!has_keybind, |el| {
9147 el.color(cx.theme().status().error.into()).strikethrough()
9148 }),
9149 )
9150 .when(!has_keybind, |el| {
9151 el.child(
9152 h_flex().ml_1().child(
9153 Icon::new(IconName::Info)
9154 .size(IconSize::Small)
9155 .color(cx.theme().status().error.into()),
9156 ),
9157 )
9158 })
9159 .when_some(icon, |element, icon| {
9160 element.child(
9161 div()
9162 .mt(px(1.5))
9163 .child(Icon::new(icon).size(IconSize::Small)),
9164 )
9165 })
9166 }
9167
9168 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9169 let accent_color = cx.theme().colors().text_accent;
9170 let editor_bg_color = cx.theme().colors().editor_background;
9171 editor_bg_color.blend(accent_color.opacity(0.1))
9172 }
9173
9174 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9175 let accent_color = cx.theme().colors().text_accent;
9176 let editor_bg_color = cx.theme().colors().editor_background;
9177 editor_bg_color.blend(accent_color.opacity(0.6))
9178 }
9179 fn get_prediction_provider_icon_name(
9180 provider: &Option<RegisteredEditPredictionProvider>,
9181 ) -> IconName {
9182 match provider {
9183 Some(provider) => match provider.provider.name() {
9184 "copilot" => IconName::Copilot,
9185 "supermaven" => IconName::Supermaven,
9186 _ => IconName::ZedPredict,
9187 },
9188 None => IconName::ZedPredict,
9189 }
9190 }
9191
9192 fn render_edit_prediction_cursor_popover(
9193 &self,
9194 min_width: Pixels,
9195 max_width: Pixels,
9196 cursor_point: Point,
9197 style: &EditorStyle,
9198 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9199 _window: &Window,
9200 cx: &mut Context<Editor>,
9201 ) -> Option<AnyElement> {
9202 let provider = self.edit_prediction_provider.as_ref()?;
9203 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9204
9205 let is_refreshing = provider.provider.is_refreshing(cx);
9206
9207 fn pending_completion_container(icon: IconName) -> Div {
9208 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9209 }
9210
9211 let completion = match &self.active_edit_prediction {
9212 Some(prediction) => {
9213 if !self.has_visible_completions_menu() {
9214 const RADIUS: Pixels = px(6.);
9215 const BORDER_WIDTH: Pixels = px(1.);
9216
9217 return Some(
9218 h_flex()
9219 .elevation_2(cx)
9220 .border(BORDER_WIDTH)
9221 .border_color(cx.theme().colors().border)
9222 .when(accept_keystroke.is_none(), |el| {
9223 el.border_color(cx.theme().status().error)
9224 })
9225 .rounded(RADIUS)
9226 .rounded_tl(px(0.))
9227 .overflow_hidden()
9228 .child(div().px_1p5().child(match &prediction.completion {
9229 EditPrediction::MoveWithin { target, snapshot } => {
9230 use text::ToPoint as _;
9231 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9232 {
9233 Icon::new(IconName::ZedPredictDown)
9234 } else {
9235 Icon::new(IconName::ZedPredictUp)
9236 }
9237 }
9238 EditPrediction::MoveOutside { .. } => {
9239 // TODO [zeta2] custom icon for external jump?
9240 Icon::new(provider_icon)
9241 }
9242 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9243 }))
9244 .child(
9245 h_flex()
9246 .gap_1()
9247 .py_1()
9248 .px_2()
9249 .rounded_r(RADIUS - BORDER_WIDTH)
9250 .border_l_1()
9251 .border_color(cx.theme().colors().border)
9252 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9253 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9254 el.child(
9255 Label::new("Hold")
9256 .size(LabelSize::Small)
9257 .when(accept_keystroke.is_none(), |el| {
9258 el.strikethrough()
9259 })
9260 .line_height_style(LineHeightStyle::UiLabel),
9261 )
9262 })
9263 .id("edit_prediction_cursor_popover_keybind")
9264 .when(accept_keystroke.is_none(), |el| {
9265 let status_colors = cx.theme().status();
9266
9267 el.bg(status_colors.error_background)
9268 .border_color(status_colors.error.opacity(0.6))
9269 .child(Icon::new(IconName::Info).color(Color::Error))
9270 .cursor_default()
9271 .hoverable_tooltip(move |_window, cx| {
9272 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9273 .into()
9274 })
9275 })
9276 .when_some(
9277 accept_keystroke.as_ref(),
9278 |el, accept_keystroke| {
9279 el.child(h_flex().children(ui::render_modifiers(
9280 accept_keystroke.modifiers(),
9281 PlatformStyle::platform(),
9282 Some(Color::Default),
9283 Some(IconSize::XSmall.rems().into()),
9284 false,
9285 )))
9286 },
9287 ),
9288 )
9289 .into_any(),
9290 );
9291 }
9292
9293 self.render_edit_prediction_cursor_popover_preview(
9294 prediction,
9295 cursor_point,
9296 style,
9297 cx,
9298 )?
9299 }
9300
9301 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9302 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9303 stale_completion,
9304 cursor_point,
9305 style,
9306 cx,
9307 )?,
9308
9309 None => pending_completion_container(provider_icon)
9310 .child(Label::new("...").size(LabelSize::Small)),
9311 },
9312
9313 None => pending_completion_container(provider_icon)
9314 .child(Label::new("...").size(LabelSize::Small)),
9315 };
9316
9317 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9318 completion
9319 .with_animation(
9320 "loading-completion",
9321 Animation::new(Duration::from_secs(2))
9322 .repeat()
9323 .with_easing(pulsating_between(0.4, 0.8)),
9324 |label, delta| label.opacity(delta),
9325 )
9326 .into_any_element()
9327 } else {
9328 completion.into_any_element()
9329 };
9330
9331 let has_completion = self.active_edit_prediction.is_some();
9332
9333 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9334 Some(
9335 h_flex()
9336 .min_w(min_width)
9337 .max_w(max_width)
9338 .flex_1()
9339 .elevation_2(cx)
9340 .border_color(cx.theme().colors().border)
9341 .child(
9342 div()
9343 .flex_1()
9344 .py_1()
9345 .px_2()
9346 .overflow_hidden()
9347 .child(completion),
9348 )
9349 .when_some(accept_keystroke, |el, accept_keystroke| {
9350 if !accept_keystroke.modifiers().modified() {
9351 return el;
9352 }
9353
9354 el.child(
9355 h_flex()
9356 .h_full()
9357 .border_l_1()
9358 .rounded_r_lg()
9359 .border_color(cx.theme().colors().border)
9360 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9361 .gap_1()
9362 .py_1()
9363 .px_2()
9364 .child(
9365 h_flex()
9366 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9367 .when(is_platform_style_mac, |parent| parent.gap_1())
9368 .child(h_flex().children(ui::render_modifiers(
9369 accept_keystroke.modifiers(),
9370 PlatformStyle::platform(),
9371 Some(if !has_completion {
9372 Color::Muted
9373 } else {
9374 Color::Default
9375 }),
9376 None,
9377 false,
9378 ))),
9379 )
9380 .child(Label::new("Preview").into_any_element())
9381 .opacity(if has_completion { 1.0 } else { 0.4 }),
9382 )
9383 })
9384 .into_any(),
9385 )
9386 }
9387
9388 fn render_edit_prediction_cursor_popover_preview(
9389 &self,
9390 completion: &EditPredictionState,
9391 cursor_point: Point,
9392 style: &EditorStyle,
9393 cx: &mut Context<Editor>,
9394 ) -> Option<Div> {
9395 use text::ToPoint as _;
9396
9397 fn render_relative_row_jump(
9398 prefix: impl Into<String>,
9399 current_row: u32,
9400 target_row: u32,
9401 ) -> Div {
9402 let (row_diff, arrow) = if target_row < current_row {
9403 (current_row - target_row, IconName::ArrowUp)
9404 } else {
9405 (target_row - current_row, IconName::ArrowDown)
9406 };
9407
9408 h_flex()
9409 .child(
9410 Label::new(format!("{}{}", prefix.into(), row_diff))
9411 .color(Color::Muted)
9412 .size(LabelSize::Small),
9413 )
9414 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9415 }
9416
9417 let supports_jump = self
9418 .edit_prediction_provider
9419 .as_ref()
9420 .map(|provider| provider.provider.supports_jump_to_edit())
9421 .unwrap_or(true);
9422
9423 match &completion.completion {
9424 EditPrediction::MoveWithin {
9425 target, snapshot, ..
9426 } => {
9427 if !supports_jump {
9428 return None;
9429 }
9430
9431 Some(
9432 h_flex()
9433 .px_2()
9434 .gap_2()
9435 .flex_1()
9436 .child(
9437 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9438 Icon::new(IconName::ZedPredictDown)
9439 } else {
9440 Icon::new(IconName::ZedPredictUp)
9441 },
9442 )
9443 .child(Label::new("Jump to Edit")),
9444 )
9445 }
9446 EditPrediction::MoveOutside { snapshot, .. } => {
9447 let file_name = snapshot
9448 .file()
9449 .map(|file| file.file_name(cx))
9450 .unwrap_or("untitled");
9451 Some(
9452 h_flex()
9453 .px_2()
9454 .gap_2()
9455 .flex_1()
9456 .child(Icon::new(IconName::ZedPredict))
9457 .child(Label::new(format!("Jump to {file_name}"))),
9458 )
9459 }
9460 EditPrediction::Edit {
9461 edits,
9462 edit_preview,
9463 snapshot,
9464 display_mode: _,
9465 } => {
9466 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9467
9468 let (highlighted_edits, has_more_lines) =
9469 if let Some(edit_preview) = edit_preview.as_ref() {
9470 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9471 .first_line_preview()
9472 } else {
9473 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9474 };
9475
9476 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9477 .with_default_highlights(&style.text, highlighted_edits.highlights);
9478
9479 let preview = h_flex()
9480 .gap_1()
9481 .min_w_16()
9482 .child(styled_text)
9483 .when(has_more_lines, |parent| parent.child("…"));
9484
9485 let left = if supports_jump && first_edit_row != cursor_point.row {
9486 render_relative_row_jump("", cursor_point.row, first_edit_row)
9487 .into_any_element()
9488 } else {
9489 let icon_name =
9490 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9491 Icon::new(icon_name).into_any_element()
9492 };
9493
9494 Some(
9495 h_flex()
9496 .h_full()
9497 .flex_1()
9498 .gap_2()
9499 .pr_1()
9500 .overflow_x_hidden()
9501 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9502 .child(left)
9503 .child(preview),
9504 )
9505 }
9506 }
9507 }
9508
9509 pub fn render_context_menu(
9510 &self,
9511 style: &EditorStyle,
9512 max_height_in_lines: u32,
9513 window: &mut Window,
9514 cx: &mut Context<Editor>,
9515 ) -> Option<AnyElement> {
9516 let menu = self.context_menu.borrow();
9517 let menu = menu.as_ref()?;
9518 if !menu.visible() {
9519 return None;
9520 };
9521 Some(menu.render(style, max_height_in_lines, window, cx))
9522 }
9523
9524 fn render_context_menu_aside(
9525 &mut self,
9526 max_size: Size<Pixels>,
9527 window: &mut Window,
9528 cx: &mut Context<Editor>,
9529 ) -> Option<AnyElement> {
9530 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9531 if menu.visible() {
9532 menu.render_aside(max_size, window, cx)
9533 } else {
9534 None
9535 }
9536 })
9537 }
9538
9539 fn hide_context_menu(
9540 &mut self,
9541 window: &mut Window,
9542 cx: &mut Context<Self>,
9543 ) -> Option<CodeContextMenu> {
9544 cx.notify();
9545 self.completion_tasks.clear();
9546 let context_menu = self.context_menu.borrow_mut().take();
9547 self.stale_edit_prediction_in_menu.take();
9548 self.update_visible_edit_prediction(window, cx);
9549 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9550 && let Some(completion_provider) = &self.completion_provider
9551 {
9552 completion_provider.selection_changed(None, window, cx);
9553 }
9554 context_menu
9555 }
9556
9557 fn show_snippet_choices(
9558 &mut self,
9559 choices: &Vec<String>,
9560 selection: Range<Anchor>,
9561 cx: &mut Context<Self>,
9562 ) {
9563 let Some((_, buffer, _)) = self
9564 .buffer()
9565 .read(cx)
9566 .excerpt_containing(selection.start, cx)
9567 else {
9568 return;
9569 };
9570 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9571 else {
9572 return;
9573 };
9574 if buffer != end_buffer {
9575 log::error!("expected anchor range to have matching buffer IDs");
9576 return;
9577 }
9578
9579 let id = post_inc(&mut self.next_completion_id);
9580 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9581 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9582 CompletionsMenu::new_snippet_choices(
9583 id,
9584 true,
9585 choices,
9586 selection,
9587 buffer,
9588 snippet_sort_order,
9589 ),
9590 ));
9591 }
9592
9593 pub fn insert_snippet(
9594 &mut self,
9595 insertion_ranges: &[Range<usize>],
9596 snippet: Snippet,
9597 window: &mut Window,
9598 cx: &mut Context<Self>,
9599 ) -> Result<()> {
9600 struct Tabstop<T> {
9601 is_end_tabstop: bool,
9602 ranges: Vec<Range<T>>,
9603 choices: Option<Vec<String>>,
9604 }
9605
9606 let tabstops = self.buffer.update(cx, |buffer, cx| {
9607 let snippet_text: Arc<str> = snippet.text.clone().into();
9608 let edits = insertion_ranges
9609 .iter()
9610 .cloned()
9611 .map(|range| (range, snippet_text.clone()));
9612 let autoindent_mode = AutoindentMode::Block {
9613 original_indent_columns: Vec::new(),
9614 };
9615 buffer.edit(edits, Some(autoindent_mode), cx);
9616
9617 let snapshot = &*buffer.read(cx);
9618 let snippet = &snippet;
9619 snippet
9620 .tabstops
9621 .iter()
9622 .map(|tabstop| {
9623 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9624 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9625 });
9626 let mut tabstop_ranges = tabstop
9627 .ranges
9628 .iter()
9629 .flat_map(|tabstop_range| {
9630 let mut delta = 0_isize;
9631 insertion_ranges.iter().map(move |insertion_range| {
9632 let insertion_start = insertion_range.start as isize + delta;
9633 delta +=
9634 snippet.text.len() as isize - insertion_range.len() as isize;
9635
9636 let start = ((insertion_start + tabstop_range.start) as usize)
9637 .min(snapshot.len());
9638 let end = ((insertion_start + tabstop_range.end) as usize)
9639 .min(snapshot.len());
9640 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9641 })
9642 })
9643 .collect::<Vec<_>>();
9644 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9645
9646 Tabstop {
9647 is_end_tabstop,
9648 ranges: tabstop_ranges,
9649 choices: tabstop.choices.clone(),
9650 }
9651 })
9652 .collect::<Vec<_>>()
9653 });
9654 if let Some(tabstop) = tabstops.first() {
9655 self.change_selections(Default::default(), window, cx, |s| {
9656 // Reverse order so that the first range is the newest created selection.
9657 // Completions will use it and autoscroll will prioritize it.
9658 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9659 });
9660
9661 if let Some(choices) = &tabstop.choices
9662 && let Some(selection) = tabstop.ranges.first()
9663 {
9664 self.show_snippet_choices(choices, selection.clone(), cx)
9665 }
9666
9667 // If we're already at the last tabstop and it's at the end of the snippet,
9668 // we're done, we don't need to keep the state around.
9669 if !tabstop.is_end_tabstop {
9670 let choices = tabstops
9671 .iter()
9672 .map(|tabstop| tabstop.choices.clone())
9673 .collect();
9674
9675 let ranges = tabstops
9676 .into_iter()
9677 .map(|tabstop| tabstop.ranges)
9678 .collect::<Vec<_>>();
9679
9680 self.snippet_stack.push(SnippetState {
9681 active_index: 0,
9682 ranges,
9683 choices,
9684 });
9685 }
9686
9687 // Check whether the just-entered snippet ends with an auto-closable bracket.
9688 if self.autoclose_regions.is_empty() {
9689 let snapshot = self.buffer.read(cx).snapshot(cx);
9690 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9691 let selection_head = selection.head();
9692 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9693 continue;
9694 };
9695
9696 let mut bracket_pair = None;
9697 let max_lookup_length = scope
9698 .brackets()
9699 .map(|(pair, _)| {
9700 pair.start
9701 .as_str()
9702 .chars()
9703 .count()
9704 .max(pair.end.as_str().chars().count())
9705 })
9706 .max();
9707 if let Some(max_lookup_length) = max_lookup_length {
9708 let next_text = snapshot
9709 .chars_at(selection_head)
9710 .take(max_lookup_length)
9711 .collect::<String>();
9712 let prev_text = snapshot
9713 .reversed_chars_at(selection_head)
9714 .take(max_lookup_length)
9715 .collect::<String>();
9716
9717 for (pair, enabled) in scope.brackets() {
9718 if enabled
9719 && pair.close
9720 && prev_text.starts_with(pair.start.as_str())
9721 && next_text.starts_with(pair.end.as_str())
9722 {
9723 bracket_pair = Some(pair.clone());
9724 break;
9725 }
9726 }
9727 }
9728
9729 if let Some(pair) = bracket_pair {
9730 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9731 let autoclose_enabled =
9732 self.use_autoclose && snapshot_settings.use_autoclose;
9733 if autoclose_enabled {
9734 let start = snapshot.anchor_after(selection_head);
9735 let end = snapshot.anchor_after(selection_head);
9736 self.autoclose_regions.push(AutocloseRegion {
9737 selection_id: selection.id,
9738 range: start..end,
9739 pair,
9740 });
9741 }
9742 }
9743 }
9744 }
9745 }
9746 Ok(())
9747 }
9748
9749 pub fn move_to_next_snippet_tabstop(
9750 &mut self,
9751 window: &mut Window,
9752 cx: &mut Context<Self>,
9753 ) -> bool {
9754 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9755 }
9756
9757 pub fn move_to_prev_snippet_tabstop(
9758 &mut self,
9759 window: &mut Window,
9760 cx: &mut Context<Self>,
9761 ) -> bool {
9762 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9763 }
9764
9765 pub fn move_to_snippet_tabstop(
9766 &mut self,
9767 bias: Bias,
9768 window: &mut Window,
9769 cx: &mut Context<Self>,
9770 ) -> bool {
9771 if let Some(mut snippet) = self.snippet_stack.pop() {
9772 match bias {
9773 Bias::Left => {
9774 if snippet.active_index > 0 {
9775 snippet.active_index -= 1;
9776 } else {
9777 self.snippet_stack.push(snippet);
9778 return false;
9779 }
9780 }
9781 Bias::Right => {
9782 if snippet.active_index + 1 < snippet.ranges.len() {
9783 snippet.active_index += 1;
9784 } else {
9785 self.snippet_stack.push(snippet);
9786 return false;
9787 }
9788 }
9789 }
9790 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9791 self.change_selections(Default::default(), window, cx, |s| {
9792 // Reverse order so that the first range is the newest created selection.
9793 // Completions will use it and autoscroll will prioritize it.
9794 s.select_ranges(current_ranges.iter().rev().cloned())
9795 });
9796
9797 if let Some(choices) = &snippet.choices[snippet.active_index]
9798 && let Some(selection) = current_ranges.first()
9799 {
9800 self.show_snippet_choices(choices, selection.clone(), cx);
9801 }
9802
9803 // If snippet state is not at the last tabstop, push it back on the stack
9804 if snippet.active_index + 1 < snippet.ranges.len() {
9805 self.snippet_stack.push(snippet);
9806 }
9807 return true;
9808 }
9809 }
9810
9811 false
9812 }
9813
9814 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9815 self.transact(window, cx, |this, window, cx| {
9816 this.select_all(&SelectAll, window, cx);
9817 this.insert("", window, cx);
9818 });
9819 }
9820
9821 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9822 if self.read_only(cx) {
9823 return;
9824 }
9825 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9826 self.transact(window, cx, |this, window, cx| {
9827 this.select_autoclose_pair(window, cx);
9828
9829 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9830
9831 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9832 if !this.linked_edit_ranges.is_empty() {
9833 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9834 let snapshot = this.buffer.read(cx).snapshot(cx);
9835
9836 for selection in selections.iter() {
9837 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9838 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9839 if selection_start.buffer_id != selection_end.buffer_id {
9840 continue;
9841 }
9842 if let Some(ranges) =
9843 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9844 {
9845 for (buffer, entries) in ranges {
9846 linked_ranges.entry(buffer).or_default().extend(entries);
9847 }
9848 }
9849 }
9850 }
9851
9852 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9853 for selection in &mut selections {
9854 if selection.is_empty() {
9855 let old_head = selection.head();
9856 let mut new_head =
9857 movement::left(&display_map, old_head.to_display_point(&display_map))
9858 .to_point(&display_map);
9859 if let Some((buffer, line_buffer_range)) = display_map
9860 .buffer_snapshot()
9861 .buffer_line_for_row(MultiBufferRow(old_head.row))
9862 {
9863 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9864 let indent_len = match indent_size.kind {
9865 IndentKind::Space => {
9866 buffer.settings_at(line_buffer_range.start, cx).tab_size
9867 }
9868 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9869 };
9870 if old_head.column <= indent_size.len && old_head.column > 0 {
9871 let indent_len = indent_len.get();
9872 new_head = cmp::min(
9873 new_head,
9874 MultiBufferPoint::new(
9875 old_head.row,
9876 ((old_head.column - 1) / indent_len) * indent_len,
9877 ),
9878 );
9879 }
9880 }
9881
9882 selection.set_head(new_head, SelectionGoal::None);
9883 }
9884 }
9885
9886 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9887 this.insert("", window, cx);
9888 let empty_str: Arc<str> = Arc::from("");
9889 for (buffer, edits) in linked_ranges {
9890 let snapshot = buffer.read(cx).snapshot();
9891 use text::ToPoint as TP;
9892
9893 let edits = edits
9894 .into_iter()
9895 .map(|range| {
9896 let end_point = TP::to_point(&range.end, &snapshot);
9897 let mut start_point = TP::to_point(&range.start, &snapshot);
9898
9899 if end_point == start_point {
9900 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9901 .saturating_sub(1);
9902 start_point =
9903 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9904 };
9905
9906 (start_point..end_point, empty_str.clone())
9907 })
9908 .sorted_by_key(|(range, _)| range.start)
9909 .collect::<Vec<_>>();
9910 buffer.update(cx, |this, cx| {
9911 this.edit(edits, None, cx);
9912 })
9913 }
9914 this.refresh_edit_prediction(true, false, window, cx);
9915 refresh_linked_ranges(this, window, cx);
9916 });
9917 }
9918
9919 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9920 if self.read_only(cx) {
9921 return;
9922 }
9923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9924 self.transact(window, cx, |this, window, cx| {
9925 this.change_selections(Default::default(), window, cx, |s| {
9926 s.move_with(|map, selection| {
9927 if selection.is_empty() {
9928 let cursor = movement::right(map, selection.head());
9929 selection.end = cursor;
9930 selection.reversed = true;
9931 selection.goal = SelectionGoal::None;
9932 }
9933 })
9934 });
9935 this.insert("", window, cx);
9936 this.refresh_edit_prediction(true, false, window, cx);
9937 });
9938 }
9939
9940 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9941 if self.mode.is_single_line() {
9942 cx.propagate();
9943 return;
9944 }
9945
9946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9947 if self.move_to_prev_snippet_tabstop(window, cx) {
9948 return;
9949 }
9950 self.outdent(&Outdent, window, cx);
9951 }
9952
9953 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9954 if self.mode.is_single_line() {
9955 cx.propagate();
9956 return;
9957 }
9958
9959 if self.move_to_next_snippet_tabstop(window, cx) {
9960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9961 return;
9962 }
9963 if self.read_only(cx) {
9964 return;
9965 }
9966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9967 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
9968 let buffer = self.buffer.read(cx);
9969 let snapshot = buffer.snapshot(cx);
9970 let rows_iter = selections.iter().map(|s| s.head().row);
9971 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9972
9973 let has_some_cursor_in_whitespace = selections
9974 .iter()
9975 .filter(|selection| selection.is_empty())
9976 .any(|selection| {
9977 let cursor = selection.head();
9978 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9979 cursor.column < current_indent.len
9980 });
9981
9982 let mut edits = Vec::new();
9983 let mut prev_edited_row = 0;
9984 let mut row_delta = 0;
9985 for selection in &mut selections {
9986 if selection.start.row != prev_edited_row {
9987 row_delta = 0;
9988 }
9989 prev_edited_row = selection.end.row;
9990
9991 // If the selection is non-empty, then increase the indentation of the selected lines.
9992 if !selection.is_empty() {
9993 row_delta =
9994 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9995 continue;
9996 }
9997
9998 let cursor = selection.head();
9999 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10000 if let Some(suggested_indent) =
10001 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10002 {
10003 // Don't do anything if already at suggested indent
10004 // and there is any other cursor which is not
10005 if has_some_cursor_in_whitespace
10006 && cursor.column == current_indent.len
10007 && current_indent.len == suggested_indent.len
10008 {
10009 continue;
10010 }
10011
10012 // Adjust line and move cursor to suggested indent
10013 // if cursor is not at suggested indent
10014 if cursor.column < suggested_indent.len
10015 && cursor.column <= current_indent.len
10016 && current_indent.len <= suggested_indent.len
10017 {
10018 selection.start = Point::new(cursor.row, suggested_indent.len);
10019 selection.end = selection.start;
10020 if row_delta == 0 {
10021 edits.extend(Buffer::edit_for_indent_size_adjustment(
10022 cursor.row,
10023 current_indent,
10024 suggested_indent,
10025 ));
10026 row_delta = suggested_indent.len - current_indent.len;
10027 }
10028 continue;
10029 }
10030
10031 // If current indent is more than suggested indent
10032 // only move cursor to current indent and skip indent
10033 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10034 selection.start = Point::new(cursor.row, current_indent.len);
10035 selection.end = selection.start;
10036 continue;
10037 }
10038 }
10039
10040 // Otherwise, insert a hard or soft tab.
10041 let settings = buffer.language_settings_at(cursor, cx);
10042 let tab_size = if settings.hard_tabs {
10043 IndentSize::tab()
10044 } else {
10045 let tab_size = settings.tab_size.get();
10046 let indent_remainder = snapshot
10047 .text_for_range(Point::new(cursor.row, 0)..cursor)
10048 .flat_map(str::chars)
10049 .fold(row_delta % tab_size, |counter: u32, c| {
10050 if c == '\t' {
10051 0
10052 } else {
10053 (counter + 1) % tab_size
10054 }
10055 });
10056
10057 let chars_to_next_tab_stop = tab_size - indent_remainder;
10058 IndentSize::spaces(chars_to_next_tab_stop)
10059 };
10060 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10061 selection.end = selection.start;
10062 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10063 row_delta += tab_size.len;
10064 }
10065
10066 self.transact(window, cx, |this, window, cx| {
10067 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10068 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10069 this.refresh_edit_prediction(true, false, window, cx);
10070 });
10071 }
10072
10073 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10074 if self.read_only(cx) {
10075 return;
10076 }
10077 if self.mode.is_single_line() {
10078 cx.propagate();
10079 return;
10080 }
10081
10082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10083 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10084 let mut prev_edited_row = 0;
10085 let mut row_delta = 0;
10086 let mut edits = Vec::new();
10087 let buffer = self.buffer.read(cx);
10088 let snapshot = buffer.snapshot(cx);
10089 for selection in &mut selections {
10090 if selection.start.row != prev_edited_row {
10091 row_delta = 0;
10092 }
10093 prev_edited_row = selection.end.row;
10094
10095 row_delta =
10096 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10097 }
10098
10099 self.transact(window, cx, |this, window, cx| {
10100 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10101 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10102 });
10103 }
10104
10105 fn indent_selection(
10106 buffer: &MultiBuffer,
10107 snapshot: &MultiBufferSnapshot,
10108 selection: &mut Selection<Point>,
10109 edits: &mut Vec<(Range<Point>, String)>,
10110 delta_for_start_row: u32,
10111 cx: &App,
10112 ) -> u32 {
10113 let settings = buffer.language_settings_at(selection.start, cx);
10114 let tab_size = settings.tab_size.get();
10115 let indent_kind = if settings.hard_tabs {
10116 IndentKind::Tab
10117 } else {
10118 IndentKind::Space
10119 };
10120 let mut start_row = selection.start.row;
10121 let mut end_row = selection.end.row + 1;
10122
10123 // If a selection ends at the beginning of a line, don't indent
10124 // that last line.
10125 if selection.end.column == 0 && selection.end.row > selection.start.row {
10126 end_row -= 1;
10127 }
10128
10129 // Avoid re-indenting a row that has already been indented by a
10130 // previous selection, but still update this selection's column
10131 // to reflect that indentation.
10132 if delta_for_start_row > 0 {
10133 start_row += 1;
10134 selection.start.column += delta_for_start_row;
10135 if selection.end.row == selection.start.row {
10136 selection.end.column += delta_for_start_row;
10137 }
10138 }
10139
10140 let mut delta_for_end_row = 0;
10141 let has_multiple_rows = start_row + 1 != end_row;
10142 for row in start_row..end_row {
10143 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10144 let indent_delta = match (current_indent.kind, indent_kind) {
10145 (IndentKind::Space, IndentKind::Space) => {
10146 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10147 IndentSize::spaces(columns_to_next_tab_stop)
10148 }
10149 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10150 (_, IndentKind::Tab) => IndentSize::tab(),
10151 };
10152
10153 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10154 0
10155 } else {
10156 selection.start.column
10157 };
10158 let row_start = Point::new(row, start);
10159 edits.push((
10160 row_start..row_start,
10161 indent_delta.chars().collect::<String>(),
10162 ));
10163
10164 // Update this selection's endpoints to reflect the indentation.
10165 if row == selection.start.row {
10166 selection.start.column += indent_delta.len;
10167 }
10168 if row == selection.end.row {
10169 selection.end.column += indent_delta.len;
10170 delta_for_end_row = indent_delta.len;
10171 }
10172 }
10173
10174 if selection.start.row == selection.end.row {
10175 delta_for_start_row + delta_for_end_row
10176 } else {
10177 delta_for_end_row
10178 }
10179 }
10180
10181 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10182 if self.read_only(cx) {
10183 return;
10184 }
10185 if self.mode.is_single_line() {
10186 cx.propagate();
10187 return;
10188 }
10189
10190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10192 let selections = self.selections.all::<Point>(&display_map);
10193 let mut deletion_ranges = Vec::new();
10194 let mut last_outdent = None;
10195 {
10196 let buffer = self.buffer.read(cx);
10197 let snapshot = buffer.snapshot(cx);
10198 for selection in &selections {
10199 let settings = buffer.language_settings_at(selection.start, cx);
10200 let tab_size = settings.tab_size.get();
10201 let mut rows = selection.spanned_rows(false, &display_map);
10202
10203 // Avoid re-outdenting a row that has already been outdented by a
10204 // previous selection.
10205 if let Some(last_row) = last_outdent
10206 && last_row == rows.start
10207 {
10208 rows.start = rows.start.next_row();
10209 }
10210 let has_multiple_rows = rows.len() > 1;
10211 for row in rows.iter_rows() {
10212 let indent_size = snapshot.indent_size_for_line(row);
10213 if indent_size.len > 0 {
10214 let deletion_len = match indent_size.kind {
10215 IndentKind::Space => {
10216 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10217 if columns_to_prev_tab_stop == 0 {
10218 tab_size
10219 } else {
10220 columns_to_prev_tab_stop
10221 }
10222 }
10223 IndentKind::Tab => 1,
10224 };
10225 let start = if has_multiple_rows
10226 || deletion_len > selection.start.column
10227 || indent_size.len < selection.start.column
10228 {
10229 0
10230 } else {
10231 selection.start.column - deletion_len
10232 };
10233 deletion_ranges.push(
10234 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10235 );
10236 last_outdent = Some(row);
10237 }
10238 }
10239 }
10240 }
10241
10242 self.transact(window, cx, |this, window, cx| {
10243 this.buffer.update(cx, |buffer, cx| {
10244 let empty_str: Arc<str> = Arc::default();
10245 buffer.edit(
10246 deletion_ranges
10247 .into_iter()
10248 .map(|range| (range, empty_str.clone())),
10249 None,
10250 cx,
10251 );
10252 });
10253 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10254 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10255 });
10256 }
10257
10258 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10259 if self.read_only(cx) {
10260 return;
10261 }
10262 if self.mode.is_single_line() {
10263 cx.propagate();
10264 return;
10265 }
10266
10267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10268 let selections = self
10269 .selections
10270 .all::<usize>(&self.display_snapshot(cx))
10271 .into_iter()
10272 .map(|s| s.range());
10273
10274 self.transact(window, cx, |this, window, cx| {
10275 this.buffer.update(cx, |buffer, cx| {
10276 buffer.autoindent_ranges(selections, cx);
10277 });
10278 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10279 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10280 });
10281 }
10282
10283 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10284 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10286 let selections = self.selections.all::<Point>(&display_map);
10287
10288 let mut new_cursors = Vec::new();
10289 let mut edit_ranges = Vec::new();
10290 let mut selections = selections.iter().peekable();
10291 while let Some(selection) = selections.next() {
10292 let mut rows = selection.spanned_rows(false, &display_map);
10293
10294 // Accumulate contiguous regions of rows that we want to delete.
10295 while let Some(next_selection) = selections.peek() {
10296 let next_rows = next_selection.spanned_rows(false, &display_map);
10297 if next_rows.start <= rows.end {
10298 rows.end = next_rows.end;
10299 selections.next().unwrap();
10300 } else {
10301 break;
10302 }
10303 }
10304
10305 let buffer = display_map.buffer_snapshot();
10306 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10307 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10308 // If there's a line after the range, delete the \n from the end of the row range
10309 (
10310 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10311 rows.end,
10312 )
10313 } else {
10314 // If there isn't a line after the range, delete the \n from the line before the
10315 // start of the row range
10316 edit_start = edit_start.saturating_sub(1);
10317 (buffer.len(), rows.start.previous_row())
10318 };
10319
10320 let text_layout_details = self.text_layout_details(window);
10321 let x = display_map.x_for_display_point(
10322 selection.head().to_display_point(&display_map),
10323 &text_layout_details,
10324 );
10325 let row = Point::new(target_row.0, 0)
10326 .to_display_point(&display_map)
10327 .row();
10328 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10329
10330 new_cursors.push((
10331 selection.id,
10332 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10333 SelectionGoal::None,
10334 ));
10335 edit_ranges.push(edit_start..edit_end);
10336 }
10337
10338 self.transact(window, cx, |this, window, cx| {
10339 let buffer = this.buffer.update(cx, |buffer, cx| {
10340 let empty_str: Arc<str> = Arc::default();
10341 buffer.edit(
10342 edit_ranges
10343 .into_iter()
10344 .map(|range| (range, empty_str.clone())),
10345 None,
10346 cx,
10347 );
10348 buffer.snapshot(cx)
10349 });
10350 let new_selections = new_cursors
10351 .into_iter()
10352 .map(|(id, cursor, goal)| {
10353 let cursor = cursor.to_point(&buffer);
10354 Selection {
10355 id,
10356 start: cursor,
10357 end: cursor,
10358 reversed: false,
10359 goal,
10360 }
10361 })
10362 .collect();
10363
10364 this.change_selections(Default::default(), window, cx, |s| {
10365 s.select(new_selections);
10366 });
10367 });
10368 }
10369
10370 pub fn join_lines_impl(
10371 &mut self,
10372 insert_whitespace: bool,
10373 window: &mut Window,
10374 cx: &mut Context<Self>,
10375 ) {
10376 if self.read_only(cx) {
10377 return;
10378 }
10379 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10380 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10381 let start = MultiBufferRow(selection.start.row);
10382 // Treat single line selections as if they include the next line. Otherwise this action
10383 // would do nothing for single line selections individual cursors.
10384 let end = if selection.start.row == selection.end.row {
10385 MultiBufferRow(selection.start.row + 1)
10386 } else {
10387 MultiBufferRow(selection.end.row)
10388 };
10389
10390 if let Some(last_row_range) = row_ranges.last_mut()
10391 && start <= last_row_range.end
10392 {
10393 last_row_range.end = end;
10394 continue;
10395 }
10396 row_ranges.push(start..end);
10397 }
10398
10399 let snapshot = self.buffer.read(cx).snapshot(cx);
10400 let mut cursor_positions = Vec::new();
10401 for row_range in &row_ranges {
10402 let anchor = snapshot.anchor_before(Point::new(
10403 row_range.end.previous_row().0,
10404 snapshot.line_len(row_range.end.previous_row()),
10405 ));
10406 cursor_positions.push(anchor..anchor);
10407 }
10408
10409 self.transact(window, cx, |this, window, cx| {
10410 for row_range in row_ranges.into_iter().rev() {
10411 for row in row_range.iter_rows().rev() {
10412 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10413 let next_line_row = row.next_row();
10414 let indent = snapshot.indent_size_for_line(next_line_row);
10415 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10416
10417 let replace =
10418 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10419 " "
10420 } else {
10421 ""
10422 };
10423
10424 this.buffer.update(cx, |buffer, cx| {
10425 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10426 });
10427 }
10428 }
10429
10430 this.change_selections(Default::default(), window, cx, |s| {
10431 s.select_anchor_ranges(cursor_positions)
10432 });
10433 });
10434 }
10435
10436 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10437 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10438 self.join_lines_impl(true, window, cx);
10439 }
10440
10441 pub fn sort_lines_case_sensitive(
10442 &mut self,
10443 _: &SortLinesCaseSensitive,
10444 window: &mut Window,
10445 cx: &mut Context<Self>,
10446 ) {
10447 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10448 }
10449
10450 pub fn sort_lines_by_length(
10451 &mut self,
10452 _: &SortLinesByLength,
10453 window: &mut Window,
10454 cx: &mut Context<Self>,
10455 ) {
10456 self.manipulate_immutable_lines(window, cx, |lines| {
10457 lines.sort_by_key(|&line| line.chars().count())
10458 })
10459 }
10460
10461 pub fn sort_lines_case_insensitive(
10462 &mut self,
10463 _: &SortLinesCaseInsensitive,
10464 window: &mut Window,
10465 cx: &mut Context<Self>,
10466 ) {
10467 self.manipulate_immutable_lines(window, cx, |lines| {
10468 lines.sort_by_key(|line| line.to_lowercase())
10469 })
10470 }
10471
10472 pub fn unique_lines_case_insensitive(
10473 &mut self,
10474 _: &UniqueLinesCaseInsensitive,
10475 window: &mut Window,
10476 cx: &mut Context<Self>,
10477 ) {
10478 self.manipulate_immutable_lines(window, cx, |lines| {
10479 let mut seen = HashSet::default();
10480 lines.retain(|line| seen.insert(line.to_lowercase()));
10481 })
10482 }
10483
10484 pub fn unique_lines_case_sensitive(
10485 &mut self,
10486 _: &UniqueLinesCaseSensitive,
10487 window: &mut Window,
10488 cx: &mut Context<Self>,
10489 ) {
10490 self.manipulate_immutable_lines(window, cx, |lines| {
10491 let mut seen = HashSet::default();
10492 lines.retain(|line| seen.insert(*line));
10493 })
10494 }
10495
10496 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10497 let snapshot = self.buffer.read(cx).snapshot(cx);
10498 for selection in self.selections.disjoint_anchors_arc().iter() {
10499 if snapshot
10500 .language_at(selection.start)
10501 .and_then(|lang| lang.config().wrap_characters.as_ref())
10502 .is_some()
10503 {
10504 return true;
10505 }
10506 }
10507 false
10508 }
10509
10510 fn wrap_selections_in_tag(
10511 &mut self,
10512 _: &WrapSelectionsInTag,
10513 window: &mut Window,
10514 cx: &mut Context<Self>,
10515 ) {
10516 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10517
10518 let snapshot = self.buffer.read(cx).snapshot(cx);
10519
10520 let mut edits = Vec::new();
10521 let mut boundaries = Vec::new();
10522
10523 for selection in self
10524 .selections
10525 .all_adjusted(&self.display_snapshot(cx))
10526 .iter()
10527 {
10528 let Some(wrap_config) = snapshot
10529 .language_at(selection.start)
10530 .and_then(|lang| lang.config().wrap_characters.clone())
10531 else {
10532 continue;
10533 };
10534
10535 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10536 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10537
10538 let start_before = snapshot.anchor_before(selection.start);
10539 let end_after = snapshot.anchor_after(selection.end);
10540
10541 edits.push((start_before..start_before, open_tag));
10542 edits.push((end_after..end_after, close_tag));
10543
10544 boundaries.push((
10545 start_before,
10546 end_after,
10547 wrap_config.start_prefix.len(),
10548 wrap_config.end_suffix.len(),
10549 ));
10550 }
10551
10552 if edits.is_empty() {
10553 return;
10554 }
10555
10556 self.transact(window, cx, |this, window, cx| {
10557 let buffer = this.buffer.update(cx, |buffer, cx| {
10558 buffer.edit(edits, None, cx);
10559 buffer.snapshot(cx)
10560 });
10561
10562 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10563 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10564 boundaries.into_iter()
10565 {
10566 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10567 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10568 new_selections.push(open_offset..open_offset);
10569 new_selections.push(close_offset..close_offset);
10570 }
10571
10572 this.change_selections(Default::default(), window, cx, |s| {
10573 s.select_ranges(new_selections);
10574 });
10575
10576 this.request_autoscroll(Autoscroll::fit(), cx);
10577 });
10578 }
10579
10580 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10581 let Some(project) = self.project.clone() else {
10582 return;
10583 };
10584 self.reload(project, window, cx)
10585 .detach_and_notify_err(window, cx);
10586 }
10587
10588 pub fn restore_file(
10589 &mut self,
10590 _: &::git::RestoreFile,
10591 window: &mut Window,
10592 cx: &mut Context<Self>,
10593 ) {
10594 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10595 let mut buffer_ids = HashSet::default();
10596 let snapshot = self.buffer().read(cx).snapshot(cx);
10597 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10598 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10599 }
10600
10601 let buffer = self.buffer().read(cx);
10602 let ranges = buffer_ids
10603 .into_iter()
10604 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10605 .collect::<Vec<_>>();
10606
10607 self.restore_hunks_in_ranges(ranges, window, cx);
10608 }
10609
10610 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10611 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10612 let selections = self
10613 .selections
10614 .all(&self.display_snapshot(cx))
10615 .into_iter()
10616 .map(|s| s.range())
10617 .collect();
10618 self.restore_hunks_in_ranges(selections, window, cx);
10619 }
10620
10621 pub fn restore_hunks_in_ranges(
10622 &mut self,
10623 ranges: Vec<Range<Point>>,
10624 window: &mut Window,
10625 cx: &mut Context<Editor>,
10626 ) {
10627 let mut revert_changes = HashMap::default();
10628 let chunk_by = self
10629 .snapshot(window, cx)
10630 .hunks_for_ranges(ranges)
10631 .into_iter()
10632 .chunk_by(|hunk| hunk.buffer_id);
10633 for (buffer_id, hunks) in &chunk_by {
10634 let hunks = hunks.collect::<Vec<_>>();
10635 for hunk in &hunks {
10636 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10637 }
10638 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10639 }
10640 drop(chunk_by);
10641 if !revert_changes.is_empty() {
10642 self.transact(window, cx, |editor, window, cx| {
10643 editor.restore(revert_changes, window, cx);
10644 });
10645 }
10646 }
10647
10648 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10649 if let Some(status) = self
10650 .addons
10651 .iter()
10652 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10653 {
10654 return Some(status);
10655 }
10656 self.project
10657 .as_ref()?
10658 .read(cx)
10659 .status_for_buffer_id(buffer_id, cx)
10660 }
10661
10662 pub fn open_active_item_in_terminal(
10663 &mut self,
10664 _: &OpenInTerminal,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10669 let project_path = buffer.read(cx).project_path(cx)?;
10670 let project = self.project()?.read(cx);
10671 let entry = project.entry_for_path(&project_path, cx)?;
10672 let parent = match &entry.canonical_path {
10673 Some(canonical_path) => canonical_path.to_path_buf(),
10674 None => project.absolute_path(&project_path, cx)?,
10675 }
10676 .parent()?
10677 .to_path_buf();
10678 Some(parent)
10679 }) {
10680 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10681 }
10682 }
10683
10684 fn set_breakpoint_context_menu(
10685 &mut self,
10686 display_row: DisplayRow,
10687 position: Option<Anchor>,
10688 clicked_point: gpui::Point<Pixels>,
10689 window: &mut Window,
10690 cx: &mut Context<Self>,
10691 ) {
10692 let source = self
10693 .buffer
10694 .read(cx)
10695 .snapshot(cx)
10696 .anchor_before(Point::new(display_row.0, 0u32));
10697
10698 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10699
10700 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10701 self,
10702 source,
10703 clicked_point,
10704 context_menu,
10705 window,
10706 cx,
10707 );
10708 }
10709
10710 fn add_edit_breakpoint_block(
10711 &mut self,
10712 anchor: Anchor,
10713 breakpoint: &Breakpoint,
10714 edit_action: BreakpointPromptEditAction,
10715 window: &mut Window,
10716 cx: &mut Context<Self>,
10717 ) {
10718 let weak_editor = cx.weak_entity();
10719 let bp_prompt = cx.new(|cx| {
10720 BreakpointPromptEditor::new(
10721 weak_editor,
10722 anchor,
10723 breakpoint.clone(),
10724 edit_action,
10725 window,
10726 cx,
10727 )
10728 });
10729
10730 let height = bp_prompt.update(cx, |this, cx| {
10731 this.prompt
10732 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10733 });
10734 let cloned_prompt = bp_prompt.clone();
10735 let blocks = vec![BlockProperties {
10736 style: BlockStyle::Sticky,
10737 placement: BlockPlacement::Above(anchor),
10738 height: Some(height),
10739 render: Arc::new(move |cx| {
10740 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10741 cloned_prompt.clone().into_any_element()
10742 }),
10743 priority: 0,
10744 }];
10745
10746 let focus_handle = bp_prompt.focus_handle(cx);
10747 window.focus(&focus_handle);
10748
10749 let block_ids = self.insert_blocks(blocks, None, cx);
10750 bp_prompt.update(cx, |prompt, _| {
10751 prompt.add_block_ids(block_ids);
10752 });
10753 }
10754
10755 pub(crate) fn breakpoint_at_row(
10756 &self,
10757 row: u32,
10758 window: &mut Window,
10759 cx: &mut Context<Self>,
10760 ) -> Option<(Anchor, Breakpoint)> {
10761 let snapshot = self.snapshot(window, cx);
10762 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10763
10764 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10765 }
10766
10767 pub(crate) fn breakpoint_at_anchor(
10768 &self,
10769 breakpoint_position: Anchor,
10770 snapshot: &EditorSnapshot,
10771 cx: &mut Context<Self>,
10772 ) -> Option<(Anchor, Breakpoint)> {
10773 let buffer = self
10774 .buffer
10775 .read(cx)
10776 .buffer_for_anchor(breakpoint_position, cx)?;
10777
10778 let enclosing_excerpt = breakpoint_position.excerpt_id;
10779 let buffer_snapshot = buffer.read(cx).snapshot();
10780
10781 let row = buffer_snapshot
10782 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10783 .row;
10784
10785 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10786 let anchor_end = snapshot
10787 .buffer_snapshot()
10788 .anchor_after(Point::new(row, line_len));
10789
10790 self.breakpoint_store
10791 .as_ref()?
10792 .read_with(cx, |breakpoint_store, cx| {
10793 breakpoint_store
10794 .breakpoints(
10795 &buffer,
10796 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10797 &buffer_snapshot,
10798 cx,
10799 )
10800 .next()
10801 .and_then(|(bp, _)| {
10802 let breakpoint_row = buffer_snapshot
10803 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10804 .row;
10805
10806 if breakpoint_row == row {
10807 snapshot
10808 .buffer_snapshot()
10809 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10810 .map(|position| (position, bp.bp.clone()))
10811 } else {
10812 None
10813 }
10814 })
10815 })
10816 }
10817
10818 pub fn edit_log_breakpoint(
10819 &mut self,
10820 _: &EditLogBreakpoint,
10821 window: &mut Window,
10822 cx: &mut Context<Self>,
10823 ) {
10824 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10825 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10826 message: None,
10827 state: BreakpointState::Enabled,
10828 condition: None,
10829 hit_condition: None,
10830 });
10831
10832 self.add_edit_breakpoint_block(
10833 anchor,
10834 &breakpoint,
10835 BreakpointPromptEditAction::Log,
10836 window,
10837 cx,
10838 );
10839 }
10840 }
10841
10842 fn breakpoints_at_cursors(
10843 &self,
10844 window: &mut Window,
10845 cx: &mut Context<Self>,
10846 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10847 let snapshot = self.snapshot(window, cx);
10848 let cursors = self
10849 .selections
10850 .disjoint_anchors_arc()
10851 .iter()
10852 .map(|selection| {
10853 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10854
10855 let breakpoint_position = self
10856 .breakpoint_at_row(cursor_position.row, window, cx)
10857 .map(|bp| bp.0)
10858 .unwrap_or_else(|| {
10859 snapshot
10860 .display_snapshot
10861 .buffer_snapshot()
10862 .anchor_after(Point::new(cursor_position.row, 0))
10863 });
10864
10865 let breakpoint = self
10866 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10867 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10868
10869 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10870 })
10871 // 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.
10872 .collect::<HashMap<Anchor, _>>();
10873
10874 cursors.into_iter().collect()
10875 }
10876
10877 pub fn enable_breakpoint(
10878 &mut self,
10879 _: &crate::actions::EnableBreakpoint,
10880 window: &mut Window,
10881 cx: &mut Context<Self>,
10882 ) {
10883 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10884 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10885 continue;
10886 };
10887 self.edit_breakpoint_at_anchor(
10888 anchor,
10889 breakpoint,
10890 BreakpointEditAction::InvertState,
10891 cx,
10892 );
10893 }
10894 }
10895
10896 pub fn disable_breakpoint(
10897 &mut self,
10898 _: &crate::actions::DisableBreakpoint,
10899 window: &mut Window,
10900 cx: &mut Context<Self>,
10901 ) {
10902 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10903 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10904 continue;
10905 };
10906 self.edit_breakpoint_at_anchor(
10907 anchor,
10908 breakpoint,
10909 BreakpointEditAction::InvertState,
10910 cx,
10911 );
10912 }
10913 }
10914
10915 pub fn toggle_breakpoint(
10916 &mut self,
10917 _: &crate::actions::ToggleBreakpoint,
10918 window: &mut Window,
10919 cx: &mut Context<Self>,
10920 ) {
10921 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10922 if let Some(breakpoint) = breakpoint {
10923 self.edit_breakpoint_at_anchor(
10924 anchor,
10925 breakpoint,
10926 BreakpointEditAction::Toggle,
10927 cx,
10928 );
10929 } else {
10930 self.edit_breakpoint_at_anchor(
10931 anchor,
10932 Breakpoint::new_standard(),
10933 BreakpointEditAction::Toggle,
10934 cx,
10935 );
10936 }
10937 }
10938 }
10939
10940 pub fn edit_breakpoint_at_anchor(
10941 &mut self,
10942 breakpoint_position: Anchor,
10943 breakpoint: Breakpoint,
10944 edit_action: BreakpointEditAction,
10945 cx: &mut Context<Self>,
10946 ) {
10947 let Some(breakpoint_store) = &self.breakpoint_store else {
10948 return;
10949 };
10950
10951 let Some(buffer) = self
10952 .buffer
10953 .read(cx)
10954 .buffer_for_anchor(breakpoint_position, cx)
10955 else {
10956 return;
10957 };
10958
10959 breakpoint_store.update(cx, |breakpoint_store, cx| {
10960 breakpoint_store.toggle_breakpoint(
10961 buffer,
10962 BreakpointWithPosition {
10963 position: breakpoint_position.text_anchor,
10964 bp: breakpoint,
10965 },
10966 edit_action,
10967 cx,
10968 );
10969 });
10970
10971 cx.notify();
10972 }
10973
10974 #[cfg(any(test, feature = "test-support"))]
10975 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10976 self.breakpoint_store.clone()
10977 }
10978
10979 pub fn prepare_restore_change(
10980 &self,
10981 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10982 hunk: &MultiBufferDiffHunk,
10983 cx: &mut App,
10984 ) -> Option<()> {
10985 if hunk.is_created_file() {
10986 return None;
10987 }
10988 let buffer = self.buffer.read(cx);
10989 let diff = buffer.diff_for(hunk.buffer_id)?;
10990 let buffer = buffer.buffer(hunk.buffer_id)?;
10991 let buffer = buffer.read(cx);
10992 let original_text = diff
10993 .read(cx)
10994 .base_text()
10995 .as_rope()
10996 .slice(hunk.diff_base_byte_range.clone());
10997 let buffer_snapshot = buffer.snapshot();
10998 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10999 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11000 probe
11001 .0
11002 .start
11003 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11004 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11005 }) {
11006 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11007 Some(())
11008 } else {
11009 None
11010 }
11011 }
11012
11013 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11014 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11015 }
11016
11017 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11018 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11019 }
11020
11021 fn manipulate_lines<M>(
11022 &mut self,
11023 window: &mut Window,
11024 cx: &mut Context<Self>,
11025 mut manipulate: M,
11026 ) where
11027 M: FnMut(&str) -> LineManipulationResult,
11028 {
11029 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11030
11031 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11032 let buffer = self.buffer.read(cx).snapshot(cx);
11033
11034 let mut edits = Vec::new();
11035
11036 let selections = self.selections.all::<Point>(&display_map);
11037 let mut selections = selections.iter().peekable();
11038 let mut contiguous_row_selections = Vec::new();
11039 let mut new_selections = Vec::new();
11040 let mut added_lines = 0;
11041 let mut removed_lines = 0;
11042
11043 while let Some(selection) = selections.next() {
11044 let (start_row, end_row) = consume_contiguous_rows(
11045 &mut contiguous_row_selections,
11046 selection,
11047 &display_map,
11048 &mut selections,
11049 );
11050
11051 let start_point = Point::new(start_row.0, 0);
11052 let end_point = Point::new(
11053 end_row.previous_row().0,
11054 buffer.line_len(end_row.previous_row()),
11055 );
11056 let text = buffer
11057 .text_for_range(start_point..end_point)
11058 .collect::<String>();
11059
11060 let LineManipulationResult {
11061 new_text,
11062 line_count_before,
11063 line_count_after,
11064 } = manipulate(&text);
11065
11066 edits.push((start_point..end_point, new_text));
11067
11068 // Selections must change based on added and removed line count
11069 let start_row =
11070 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11071 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11072 new_selections.push(Selection {
11073 id: selection.id,
11074 start: start_row,
11075 end: end_row,
11076 goal: SelectionGoal::None,
11077 reversed: selection.reversed,
11078 });
11079
11080 if line_count_after > line_count_before {
11081 added_lines += line_count_after - line_count_before;
11082 } else if line_count_before > line_count_after {
11083 removed_lines += line_count_before - line_count_after;
11084 }
11085 }
11086
11087 self.transact(window, cx, |this, window, cx| {
11088 let buffer = this.buffer.update(cx, |buffer, cx| {
11089 buffer.edit(edits, None, cx);
11090 buffer.snapshot(cx)
11091 });
11092
11093 // Recalculate offsets on newly edited buffer
11094 let new_selections = new_selections
11095 .iter()
11096 .map(|s| {
11097 let start_point = Point::new(s.start.0, 0);
11098 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11099 Selection {
11100 id: s.id,
11101 start: buffer.point_to_offset(start_point),
11102 end: buffer.point_to_offset(end_point),
11103 goal: s.goal,
11104 reversed: s.reversed,
11105 }
11106 })
11107 .collect();
11108
11109 this.change_selections(Default::default(), window, cx, |s| {
11110 s.select(new_selections);
11111 });
11112
11113 this.request_autoscroll(Autoscroll::fit(), cx);
11114 });
11115 }
11116
11117 fn manipulate_immutable_lines<Fn>(
11118 &mut self,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 mut callback: Fn,
11122 ) where
11123 Fn: FnMut(&mut Vec<&str>),
11124 {
11125 self.manipulate_lines(window, cx, |text| {
11126 let mut lines: Vec<&str> = text.split('\n').collect();
11127 let line_count_before = lines.len();
11128
11129 callback(&mut lines);
11130
11131 LineManipulationResult {
11132 new_text: lines.join("\n"),
11133 line_count_before,
11134 line_count_after: lines.len(),
11135 }
11136 });
11137 }
11138
11139 fn manipulate_mutable_lines<Fn>(
11140 &mut self,
11141 window: &mut Window,
11142 cx: &mut Context<Self>,
11143 mut callback: Fn,
11144 ) where
11145 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11146 {
11147 self.manipulate_lines(window, cx, |text| {
11148 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11149 let line_count_before = lines.len();
11150
11151 callback(&mut lines);
11152
11153 LineManipulationResult {
11154 new_text: lines.join("\n"),
11155 line_count_before,
11156 line_count_after: lines.len(),
11157 }
11158 });
11159 }
11160
11161 pub fn convert_indentation_to_spaces(
11162 &mut self,
11163 _: &ConvertIndentationToSpaces,
11164 window: &mut Window,
11165 cx: &mut Context<Self>,
11166 ) {
11167 let settings = self.buffer.read(cx).language_settings(cx);
11168 let tab_size = settings.tab_size.get() as usize;
11169
11170 self.manipulate_mutable_lines(window, cx, |lines| {
11171 // Allocates a reasonably sized scratch buffer once for the whole loop
11172 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11173 // Avoids recomputing spaces that could be inserted many times
11174 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11175 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11176 .collect();
11177
11178 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11179 let mut chars = line.as_ref().chars();
11180 let mut col = 0;
11181 let mut changed = false;
11182
11183 for ch in chars.by_ref() {
11184 match ch {
11185 ' ' => {
11186 reindented_line.push(' ');
11187 col += 1;
11188 }
11189 '\t' => {
11190 // \t are converted to spaces depending on the current column
11191 let spaces_len = tab_size - (col % tab_size);
11192 reindented_line.extend(&space_cache[spaces_len - 1]);
11193 col += spaces_len;
11194 changed = true;
11195 }
11196 _ => {
11197 // If we dont append before break, the character is consumed
11198 reindented_line.push(ch);
11199 break;
11200 }
11201 }
11202 }
11203
11204 if !changed {
11205 reindented_line.clear();
11206 continue;
11207 }
11208 // Append the rest of the line and replace old reference with new one
11209 reindented_line.extend(chars);
11210 *line = Cow::Owned(reindented_line.clone());
11211 reindented_line.clear();
11212 }
11213 });
11214 }
11215
11216 pub fn convert_indentation_to_tabs(
11217 &mut self,
11218 _: &ConvertIndentationToTabs,
11219 window: &mut Window,
11220 cx: &mut Context<Self>,
11221 ) {
11222 let settings = self.buffer.read(cx).language_settings(cx);
11223 let tab_size = settings.tab_size.get() as usize;
11224
11225 self.manipulate_mutable_lines(window, cx, |lines| {
11226 // Allocates a reasonably sized buffer once for the whole loop
11227 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11228 // Avoids recomputing spaces that could be inserted many times
11229 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11230 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11231 .collect();
11232
11233 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11234 let mut chars = line.chars();
11235 let mut spaces_count = 0;
11236 let mut first_non_indent_char = None;
11237 let mut changed = false;
11238
11239 for ch in chars.by_ref() {
11240 match ch {
11241 ' ' => {
11242 // Keep track of spaces. Append \t when we reach tab_size
11243 spaces_count += 1;
11244 changed = true;
11245 if spaces_count == tab_size {
11246 reindented_line.push('\t');
11247 spaces_count = 0;
11248 }
11249 }
11250 '\t' => {
11251 reindented_line.push('\t');
11252 spaces_count = 0;
11253 }
11254 _ => {
11255 // Dont append it yet, we might have remaining spaces
11256 first_non_indent_char = Some(ch);
11257 break;
11258 }
11259 }
11260 }
11261
11262 if !changed {
11263 reindented_line.clear();
11264 continue;
11265 }
11266 // Remaining spaces that didn't make a full tab stop
11267 if spaces_count > 0 {
11268 reindented_line.extend(&space_cache[spaces_count - 1]);
11269 }
11270 // If we consume an extra character that was not indentation, add it back
11271 if let Some(extra_char) = first_non_indent_char {
11272 reindented_line.push(extra_char);
11273 }
11274 // Append the rest of the line and replace old reference with new one
11275 reindented_line.extend(chars);
11276 *line = Cow::Owned(reindented_line.clone());
11277 reindented_line.clear();
11278 }
11279 });
11280 }
11281
11282 pub fn convert_to_upper_case(
11283 &mut self,
11284 _: &ConvertToUpperCase,
11285 window: &mut Window,
11286 cx: &mut Context<Self>,
11287 ) {
11288 self.manipulate_text(window, cx, |text| text.to_uppercase())
11289 }
11290
11291 pub fn convert_to_lower_case(
11292 &mut self,
11293 _: &ConvertToLowerCase,
11294 window: &mut Window,
11295 cx: &mut Context<Self>,
11296 ) {
11297 self.manipulate_text(window, cx, |text| text.to_lowercase())
11298 }
11299
11300 pub fn convert_to_title_case(
11301 &mut self,
11302 _: &ConvertToTitleCase,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.manipulate_text(window, cx, |text| {
11307 text.split('\n')
11308 .map(|line| line.to_case(Case::Title))
11309 .join("\n")
11310 })
11311 }
11312
11313 pub fn convert_to_snake_case(
11314 &mut self,
11315 _: &ConvertToSnakeCase,
11316 window: &mut Window,
11317 cx: &mut Context<Self>,
11318 ) {
11319 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11320 }
11321
11322 pub fn convert_to_kebab_case(
11323 &mut self,
11324 _: &ConvertToKebabCase,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11329 }
11330
11331 pub fn convert_to_upper_camel_case(
11332 &mut self,
11333 _: &ConvertToUpperCamelCase,
11334 window: &mut Window,
11335 cx: &mut Context<Self>,
11336 ) {
11337 self.manipulate_text(window, cx, |text| {
11338 text.split('\n')
11339 .map(|line| line.to_case(Case::UpperCamel))
11340 .join("\n")
11341 })
11342 }
11343
11344 pub fn convert_to_lower_camel_case(
11345 &mut self,
11346 _: &ConvertToLowerCamelCase,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11351 }
11352
11353 pub fn convert_to_opposite_case(
11354 &mut self,
11355 _: &ConvertToOppositeCase,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 self.manipulate_text(window, cx, |text| {
11360 text.chars()
11361 .fold(String::with_capacity(text.len()), |mut t, c| {
11362 if c.is_uppercase() {
11363 t.extend(c.to_lowercase());
11364 } else {
11365 t.extend(c.to_uppercase());
11366 }
11367 t
11368 })
11369 })
11370 }
11371
11372 pub fn convert_to_sentence_case(
11373 &mut self,
11374 _: &ConvertToSentenceCase,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11379 }
11380
11381 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11382 self.manipulate_text(window, cx, |text| {
11383 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11384 if has_upper_case_characters {
11385 text.to_lowercase()
11386 } else {
11387 text.to_uppercase()
11388 }
11389 })
11390 }
11391
11392 pub fn convert_to_rot13(
11393 &mut self,
11394 _: &ConvertToRot13,
11395 window: &mut Window,
11396 cx: &mut Context<Self>,
11397 ) {
11398 self.manipulate_text(window, cx, |text| {
11399 text.chars()
11400 .map(|c| match c {
11401 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11402 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11403 _ => c,
11404 })
11405 .collect()
11406 })
11407 }
11408
11409 pub fn convert_to_rot47(
11410 &mut self,
11411 _: &ConvertToRot47,
11412 window: &mut Window,
11413 cx: &mut Context<Self>,
11414 ) {
11415 self.manipulate_text(window, cx, |text| {
11416 text.chars()
11417 .map(|c| {
11418 let code_point = c as u32;
11419 if code_point >= 33 && code_point <= 126 {
11420 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11421 }
11422 c
11423 })
11424 .collect()
11425 })
11426 }
11427
11428 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11429 where
11430 Fn: FnMut(&str) -> String,
11431 {
11432 let buffer = self.buffer.read(cx).snapshot(cx);
11433
11434 let mut new_selections = Vec::new();
11435 let mut edits = Vec::new();
11436 let mut selection_adjustment = 0i32;
11437
11438 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11439 let selection_is_empty = selection.is_empty();
11440
11441 let (start, end) = if selection_is_empty {
11442 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11443 (word_range.start, word_range.end)
11444 } else {
11445 (
11446 buffer.point_to_offset(selection.start),
11447 buffer.point_to_offset(selection.end),
11448 )
11449 };
11450
11451 let text = buffer.text_for_range(start..end).collect::<String>();
11452 let old_length = text.len() as i32;
11453 let text = callback(&text);
11454
11455 new_selections.push(Selection {
11456 start: (start as i32 - selection_adjustment) as usize,
11457 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11458 goal: SelectionGoal::None,
11459 id: selection.id,
11460 reversed: selection.reversed,
11461 });
11462
11463 selection_adjustment += old_length - text.len() as i32;
11464
11465 edits.push((start..end, text));
11466 }
11467
11468 self.transact(window, cx, |this, window, cx| {
11469 this.buffer.update(cx, |buffer, cx| {
11470 buffer.edit(edits, None, cx);
11471 });
11472
11473 this.change_selections(Default::default(), window, cx, |s| {
11474 s.select(new_selections);
11475 });
11476
11477 this.request_autoscroll(Autoscroll::fit(), cx);
11478 });
11479 }
11480
11481 pub fn move_selection_on_drop(
11482 &mut self,
11483 selection: &Selection<Anchor>,
11484 target: DisplayPoint,
11485 is_cut: bool,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11490 let buffer = display_map.buffer_snapshot();
11491 let mut edits = Vec::new();
11492 let insert_point = display_map
11493 .clip_point(target, Bias::Left)
11494 .to_point(&display_map);
11495 let text = buffer
11496 .text_for_range(selection.start..selection.end)
11497 .collect::<String>();
11498 if is_cut {
11499 edits.push(((selection.start..selection.end), String::new()));
11500 }
11501 let insert_anchor = buffer.anchor_before(insert_point);
11502 edits.push(((insert_anchor..insert_anchor), text));
11503 let last_edit_start = insert_anchor.bias_left(buffer);
11504 let last_edit_end = insert_anchor.bias_right(buffer);
11505 self.transact(window, cx, |this, window, cx| {
11506 this.buffer.update(cx, |buffer, cx| {
11507 buffer.edit(edits, None, cx);
11508 });
11509 this.change_selections(Default::default(), window, cx, |s| {
11510 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11511 });
11512 });
11513 }
11514
11515 pub fn clear_selection_drag_state(&mut self) {
11516 self.selection_drag_state = SelectionDragState::None;
11517 }
11518
11519 pub fn duplicate(
11520 &mut self,
11521 upwards: bool,
11522 whole_lines: bool,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11527
11528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11529 let buffer = display_map.buffer_snapshot();
11530 let selections = self.selections.all::<Point>(&display_map);
11531
11532 let mut edits = Vec::new();
11533 let mut selections_iter = selections.iter().peekable();
11534 while let Some(selection) = selections_iter.next() {
11535 let mut rows = selection.spanned_rows(false, &display_map);
11536 // duplicate line-wise
11537 if whole_lines || selection.start == selection.end {
11538 // Avoid duplicating the same lines twice.
11539 while let Some(next_selection) = selections_iter.peek() {
11540 let next_rows = next_selection.spanned_rows(false, &display_map);
11541 if next_rows.start < rows.end {
11542 rows.end = next_rows.end;
11543 selections_iter.next().unwrap();
11544 } else {
11545 break;
11546 }
11547 }
11548
11549 // Copy the text from the selected row region and splice it either at the start
11550 // or end of the region.
11551 let start = Point::new(rows.start.0, 0);
11552 let end = Point::new(
11553 rows.end.previous_row().0,
11554 buffer.line_len(rows.end.previous_row()),
11555 );
11556
11557 let mut text = buffer.text_for_range(start..end).collect::<String>();
11558
11559 let insert_location = if upwards {
11560 // When duplicating upward, we need to insert before the current line.
11561 // If we're on the last line and it doesn't end with a newline,
11562 // we need to add a newline before the duplicated content.
11563 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11564 && buffer.max_point().column > 0
11565 && !text.ends_with('\n');
11566
11567 if needs_leading_newline {
11568 text.insert(0, '\n');
11569 end
11570 } else {
11571 text.push('\n');
11572 Point::new(rows.start.0, 0)
11573 }
11574 } else {
11575 text.push('\n');
11576 start
11577 };
11578 edits.push((insert_location..insert_location, text));
11579 } else {
11580 // duplicate character-wise
11581 let start = selection.start;
11582 let end = selection.end;
11583 let text = buffer.text_for_range(start..end).collect::<String>();
11584 edits.push((selection.end..selection.end, text));
11585 }
11586 }
11587
11588 self.transact(window, cx, |this, window, cx| {
11589 this.buffer.update(cx, |buffer, cx| {
11590 buffer.edit(edits, None, cx);
11591 });
11592
11593 // When duplicating upward with whole lines, move the cursor to the duplicated line
11594 if upwards && whole_lines {
11595 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11596
11597 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11598 let mut new_ranges = Vec::new();
11599 let selections = s.all::<Point>(&display_map);
11600 let mut selections_iter = selections.iter().peekable();
11601
11602 while let Some(first_selection) = selections_iter.next() {
11603 // Group contiguous selections together to find the total row span
11604 let mut group_selections = vec![first_selection];
11605 let mut rows = first_selection.spanned_rows(false, &display_map);
11606
11607 while let Some(next_selection) = selections_iter.peek() {
11608 let next_rows = next_selection.spanned_rows(false, &display_map);
11609 if next_rows.start < rows.end {
11610 rows.end = next_rows.end;
11611 group_selections.push(selections_iter.next().unwrap());
11612 } else {
11613 break;
11614 }
11615 }
11616
11617 let row_count = rows.end.0 - rows.start.0;
11618
11619 // Move all selections in this group up by the total number of duplicated rows
11620 for selection in group_selections {
11621 let new_start = Point::new(
11622 selection.start.row.saturating_sub(row_count),
11623 selection.start.column,
11624 );
11625
11626 let new_end = Point::new(
11627 selection.end.row.saturating_sub(row_count),
11628 selection.end.column,
11629 );
11630
11631 new_ranges.push(new_start..new_end);
11632 }
11633 }
11634
11635 s.select_ranges(new_ranges);
11636 });
11637 }
11638
11639 this.request_autoscroll(Autoscroll::fit(), cx);
11640 });
11641 }
11642
11643 pub fn duplicate_line_up(
11644 &mut self,
11645 _: &DuplicateLineUp,
11646 window: &mut Window,
11647 cx: &mut Context<Self>,
11648 ) {
11649 self.duplicate(true, true, window, cx);
11650 }
11651
11652 pub fn duplicate_line_down(
11653 &mut self,
11654 _: &DuplicateLineDown,
11655 window: &mut Window,
11656 cx: &mut Context<Self>,
11657 ) {
11658 self.duplicate(false, true, window, cx);
11659 }
11660
11661 pub fn duplicate_selection(
11662 &mut self,
11663 _: &DuplicateSelection,
11664 window: &mut Window,
11665 cx: &mut Context<Self>,
11666 ) {
11667 self.duplicate(false, false, window, cx);
11668 }
11669
11670 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11671 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11672 if self.mode.is_single_line() {
11673 cx.propagate();
11674 return;
11675 }
11676
11677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11678 let buffer = self.buffer.read(cx).snapshot(cx);
11679
11680 let mut edits = Vec::new();
11681 let mut unfold_ranges = Vec::new();
11682 let mut refold_creases = Vec::new();
11683
11684 let selections = self.selections.all::<Point>(&display_map);
11685 let mut selections = selections.iter().peekable();
11686 let mut contiguous_row_selections = Vec::new();
11687 let mut new_selections = Vec::new();
11688
11689 while let Some(selection) = selections.next() {
11690 // Find all the selections that span a contiguous row range
11691 let (start_row, end_row) = consume_contiguous_rows(
11692 &mut contiguous_row_selections,
11693 selection,
11694 &display_map,
11695 &mut selections,
11696 );
11697
11698 // Move the text spanned by the row range to be before the line preceding the row range
11699 if start_row.0 > 0 {
11700 let range_to_move = Point::new(
11701 start_row.previous_row().0,
11702 buffer.line_len(start_row.previous_row()),
11703 )
11704 ..Point::new(
11705 end_row.previous_row().0,
11706 buffer.line_len(end_row.previous_row()),
11707 );
11708 let insertion_point = display_map
11709 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11710 .0;
11711
11712 // Don't move lines across excerpts
11713 if buffer
11714 .excerpt_containing(insertion_point..range_to_move.end)
11715 .is_some()
11716 {
11717 let text = buffer
11718 .text_for_range(range_to_move.clone())
11719 .flat_map(|s| s.chars())
11720 .skip(1)
11721 .chain(['\n'])
11722 .collect::<String>();
11723
11724 edits.push((
11725 buffer.anchor_after(range_to_move.start)
11726 ..buffer.anchor_before(range_to_move.end),
11727 String::new(),
11728 ));
11729 let insertion_anchor = buffer.anchor_after(insertion_point);
11730 edits.push((insertion_anchor..insertion_anchor, text));
11731
11732 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11733
11734 // Move selections up
11735 new_selections.extend(contiguous_row_selections.drain(..).map(
11736 |mut selection| {
11737 selection.start.row -= row_delta;
11738 selection.end.row -= row_delta;
11739 selection
11740 },
11741 ));
11742
11743 // Move folds up
11744 unfold_ranges.push(range_to_move.clone());
11745 for fold in display_map.folds_in_range(
11746 buffer.anchor_before(range_to_move.start)
11747 ..buffer.anchor_after(range_to_move.end),
11748 ) {
11749 let mut start = fold.range.start.to_point(&buffer);
11750 let mut end = fold.range.end.to_point(&buffer);
11751 start.row -= row_delta;
11752 end.row -= row_delta;
11753 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11754 }
11755 }
11756 }
11757
11758 // If we didn't move line(s), preserve the existing selections
11759 new_selections.append(&mut contiguous_row_selections);
11760 }
11761
11762 self.transact(window, cx, |this, window, cx| {
11763 this.unfold_ranges(&unfold_ranges, true, true, cx);
11764 this.buffer.update(cx, |buffer, cx| {
11765 for (range, text) in edits {
11766 buffer.edit([(range, text)], None, cx);
11767 }
11768 });
11769 this.fold_creases(refold_creases, true, window, cx);
11770 this.change_selections(Default::default(), window, cx, |s| {
11771 s.select(new_selections);
11772 })
11773 });
11774 }
11775
11776 pub fn move_line_down(
11777 &mut self,
11778 _: &MoveLineDown,
11779 window: &mut Window,
11780 cx: &mut Context<Self>,
11781 ) {
11782 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11783 if self.mode.is_single_line() {
11784 cx.propagate();
11785 return;
11786 }
11787
11788 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11789 let buffer = self.buffer.read(cx).snapshot(cx);
11790
11791 let mut edits = Vec::new();
11792 let mut unfold_ranges = Vec::new();
11793 let mut refold_creases = Vec::new();
11794
11795 let selections = self.selections.all::<Point>(&display_map);
11796 let mut selections = selections.iter().peekable();
11797 let mut contiguous_row_selections = Vec::new();
11798 let mut new_selections = Vec::new();
11799
11800 while let Some(selection) = selections.next() {
11801 // Find all the selections that span a contiguous row range
11802 let (start_row, end_row) = consume_contiguous_rows(
11803 &mut contiguous_row_selections,
11804 selection,
11805 &display_map,
11806 &mut selections,
11807 );
11808
11809 // Move the text spanned by the row range to be after the last line of the row range
11810 if end_row.0 <= buffer.max_point().row {
11811 let range_to_move =
11812 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11813 let insertion_point = display_map
11814 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11815 .0;
11816
11817 // Don't move lines across excerpt boundaries
11818 if buffer
11819 .excerpt_containing(range_to_move.start..insertion_point)
11820 .is_some()
11821 {
11822 let mut text = String::from("\n");
11823 text.extend(buffer.text_for_range(range_to_move.clone()));
11824 text.pop(); // Drop trailing newline
11825 edits.push((
11826 buffer.anchor_after(range_to_move.start)
11827 ..buffer.anchor_before(range_to_move.end),
11828 String::new(),
11829 ));
11830 let insertion_anchor = buffer.anchor_after(insertion_point);
11831 edits.push((insertion_anchor..insertion_anchor, text));
11832
11833 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11834
11835 // Move selections down
11836 new_selections.extend(contiguous_row_selections.drain(..).map(
11837 |mut selection| {
11838 selection.start.row += row_delta;
11839 selection.end.row += row_delta;
11840 selection
11841 },
11842 ));
11843
11844 // Move folds down
11845 unfold_ranges.push(range_to_move.clone());
11846 for fold in display_map.folds_in_range(
11847 buffer.anchor_before(range_to_move.start)
11848 ..buffer.anchor_after(range_to_move.end),
11849 ) {
11850 let mut start = fold.range.start.to_point(&buffer);
11851 let mut end = fold.range.end.to_point(&buffer);
11852 start.row += row_delta;
11853 end.row += row_delta;
11854 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11855 }
11856 }
11857 }
11858
11859 // If we didn't move line(s), preserve the existing selections
11860 new_selections.append(&mut contiguous_row_selections);
11861 }
11862
11863 self.transact(window, cx, |this, window, cx| {
11864 this.unfold_ranges(&unfold_ranges, true, true, cx);
11865 this.buffer.update(cx, |buffer, cx| {
11866 for (range, text) in edits {
11867 buffer.edit([(range, text)], None, cx);
11868 }
11869 });
11870 this.fold_creases(refold_creases, true, window, cx);
11871 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11872 });
11873 }
11874
11875 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11876 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11877 let text_layout_details = &self.text_layout_details(window);
11878 self.transact(window, cx, |this, window, cx| {
11879 let edits = this.change_selections(Default::default(), window, cx, |s| {
11880 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11881 s.move_with(|display_map, selection| {
11882 if !selection.is_empty() {
11883 return;
11884 }
11885
11886 let mut head = selection.head();
11887 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11888 if head.column() == display_map.line_len(head.row()) {
11889 transpose_offset = display_map
11890 .buffer_snapshot()
11891 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11892 }
11893
11894 if transpose_offset == 0 {
11895 return;
11896 }
11897
11898 *head.column_mut() += 1;
11899 head = display_map.clip_point(head, Bias::Right);
11900 let goal = SelectionGoal::HorizontalPosition(
11901 display_map
11902 .x_for_display_point(head, text_layout_details)
11903 .into(),
11904 );
11905 selection.collapse_to(head, goal);
11906
11907 let transpose_start = display_map
11908 .buffer_snapshot()
11909 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11910 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11911 let transpose_end = display_map
11912 .buffer_snapshot()
11913 .clip_offset(transpose_offset + 1, Bias::Right);
11914 if let Some(ch) = display_map
11915 .buffer_snapshot()
11916 .chars_at(transpose_start)
11917 .next()
11918 {
11919 edits.push((transpose_start..transpose_offset, String::new()));
11920 edits.push((transpose_end..transpose_end, ch.to_string()));
11921 }
11922 }
11923 });
11924 edits
11925 });
11926 this.buffer
11927 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11928 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11929 this.change_selections(Default::default(), window, cx, |s| {
11930 s.select(selections);
11931 });
11932 });
11933 }
11934
11935 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11936 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11937 if self.mode.is_single_line() {
11938 cx.propagate();
11939 return;
11940 }
11941
11942 self.rewrap_impl(RewrapOptions::default(), cx)
11943 }
11944
11945 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11946 let buffer = self.buffer.read(cx).snapshot(cx);
11947 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11948
11949 #[derive(Clone, Debug, PartialEq)]
11950 enum CommentFormat {
11951 /// single line comment, with prefix for line
11952 Line(String),
11953 /// single line within a block comment, with prefix for line
11954 BlockLine(String),
11955 /// a single line of a block comment that includes the initial delimiter
11956 BlockCommentWithStart(BlockCommentConfig),
11957 /// a single line of a block comment that includes the ending delimiter
11958 BlockCommentWithEnd(BlockCommentConfig),
11959 }
11960
11961 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11962 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11963 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11964 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11965 .peekable();
11966
11967 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11968 row
11969 } else {
11970 return Vec::new();
11971 };
11972
11973 let language_settings = buffer.language_settings_at(selection.head(), cx);
11974 let language_scope = buffer.language_scope_at(selection.head());
11975
11976 let indent_and_prefix_for_row =
11977 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11978 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11979 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11980 &language_scope
11981 {
11982 let indent_end = Point::new(row, indent.len);
11983 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11984 let line_text_after_indent = buffer
11985 .text_for_range(indent_end..line_end)
11986 .collect::<String>();
11987
11988 let is_within_comment_override = buffer
11989 .language_scope_at(indent_end)
11990 .is_some_and(|scope| scope.override_name() == Some("comment"));
11991 let comment_delimiters = if is_within_comment_override {
11992 // we are within a comment syntax node, but we don't
11993 // yet know what kind of comment: block, doc or line
11994 match (
11995 language_scope.documentation_comment(),
11996 language_scope.block_comment(),
11997 ) {
11998 (Some(config), _) | (_, Some(config))
11999 if buffer.contains_str_at(indent_end, &config.start) =>
12000 {
12001 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12002 }
12003 (Some(config), _) | (_, Some(config))
12004 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12005 {
12006 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12007 }
12008 (Some(config), _) | (_, Some(config))
12009 if buffer.contains_str_at(indent_end, &config.prefix) =>
12010 {
12011 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12012 }
12013 (_, _) => language_scope
12014 .line_comment_prefixes()
12015 .iter()
12016 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12017 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12018 }
12019 } else {
12020 // we not in an overridden comment node, but we may
12021 // be within a non-overridden line comment node
12022 language_scope
12023 .line_comment_prefixes()
12024 .iter()
12025 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12026 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12027 };
12028
12029 let rewrap_prefix = language_scope
12030 .rewrap_prefixes()
12031 .iter()
12032 .find_map(|prefix_regex| {
12033 prefix_regex.find(&line_text_after_indent).map(|mat| {
12034 if mat.start() == 0 {
12035 Some(mat.as_str().to_string())
12036 } else {
12037 None
12038 }
12039 })
12040 })
12041 .flatten();
12042 (comment_delimiters, rewrap_prefix)
12043 } else {
12044 (None, None)
12045 };
12046 (indent, comment_prefix, rewrap_prefix)
12047 };
12048
12049 let mut ranges = Vec::new();
12050 let from_empty_selection = selection.is_empty();
12051
12052 let mut current_range_start = first_row;
12053 let mut prev_row = first_row;
12054 let (
12055 mut current_range_indent,
12056 mut current_range_comment_delimiters,
12057 mut current_range_rewrap_prefix,
12058 ) = indent_and_prefix_for_row(first_row);
12059
12060 for row in non_blank_rows_iter.skip(1) {
12061 let has_paragraph_break = row > prev_row + 1;
12062
12063 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12064 indent_and_prefix_for_row(row);
12065
12066 let has_indent_change = row_indent != current_range_indent;
12067 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12068
12069 let has_boundary_change = has_comment_change
12070 || row_rewrap_prefix.is_some()
12071 || (has_indent_change && current_range_comment_delimiters.is_some());
12072
12073 if has_paragraph_break || has_boundary_change {
12074 ranges.push((
12075 language_settings.clone(),
12076 Point::new(current_range_start, 0)
12077 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12078 current_range_indent,
12079 current_range_comment_delimiters.clone(),
12080 current_range_rewrap_prefix.clone(),
12081 from_empty_selection,
12082 ));
12083 current_range_start = row;
12084 current_range_indent = row_indent;
12085 current_range_comment_delimiters = row_comment_delimiters;
12086 current_range_rewrap_prefix = row_rewrap_prefix;
12087 }
12088 prev_row = row;
12089 }
12090
12091 ranges.push((
12092 language_settings.clone(),
12093 Point::new(current_range_start, 0)
12094 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12095 current_range_indent,
12096 current_range_comment_delimiters,
12097 current_range_rewrap_prefix,
12098 from_empty_selection,
12099 ));
12100
12101 ranges
12102 });
12103
12104 let mut edits = Vec::new();
12105 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12106
12107 for (
12108 language_settings,
12109 wrap_range,
12110 mut indent_size,
12111 comment_prefix,
12112 rewrap_prefix,
12113 from_empty_selection,
12114 ) in wrap_ranges
12115 {
12116 let mut start_row = wrap_range.start.row;
12117 let mut end_row = wrap_range.end.row;
12118
12119 // Skip selections that overlap with a range that has already been rewrapped.
12120 let selection_range = start_row..end_row;
12121 if rewrapped_row_ranges
12122 .iter()
12123 .any(|range| range.overlaps(&selection_range))
12124 {
12125 continue;
12126 }
12127
12128 let tab_size = language_settings.tab_size;
12129
12130 let (line_prefix, inside_comment) = match &comment_prefix {
12131 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12132 (Some(prefix.as_str()), true)
12133 }
12134 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12135 (Some(prefix.as_ref()), true)
12136 }
12137 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12138 start: _,
12139 end: _,
12140 prefix,
12141 tab_size,
12142 })) => {
12143 indent_size.len += tab_size;
12144 (Some(prefix.as_ref()), true)
12145 }
12146 None => (None, false),
12147 };
12148 let indent_prefix = indent_size.chars().collect::<String>();
12149 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12150
12151 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12152 RewrapBehavior::InComments => inside_comment,
12153 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12154 RewrapBehavior::Anywhere => true,
12155 };
12156
12157 let should_rewrap = options.override_language_settings
12158 || allow_rewrap_based_on_language
12159 || self.hard_wrap.is_some();
12160 if !should_rewrap {
12161 continue;
12162 }
12163
12164 if from_empty_selection {
12165 'expand_upwards: while start_row > 0 {
12166 let prev_row = start_row - 1;
12167 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12168 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12169 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12170 {
12171 start_row = prev_row;
12172 } else {
12173 break 'expand_upwards;
12174 }
12175 }
12176
12177 'expand_downwards: while end_row < buffer.max_point().row {
12178 let next_row = end_row + 1;
12179 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12180 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12181 && !buffer.is_line_blank(MultiBufferRow(next_row))
12182 {
12183 end_row = next_row;
12184 } else {
12185 break 'expand_downwards;
12186 }
12187 }
12188 }
12189
12190 let start = Point::new(start_row, 0);
12191 let start_offset = ToOffset::to_offset(&start, &buffer);
12192 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12193 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12194 let mut first_line_delimiter = None;
12195 let mut last_line_delimiter = None;
12196 let Some(lines_without_prefixes) = selection_text
12197 .lines()
12198 .enumerate()
12199 .map(|(ix, line)| {
12200 let line_trimmed = line.trim_start();
12201 if rewrap_prefix.is_some() && ix > 0 {
12202 Ok(line_trimmed)
12203 } else if let Some(
12204 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12205 start,
12206 prefix,
12207 end,
12208 tab_size,
12209 })
12210 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12211 start,
12212 prefix,
12213 end,
12214 tab_size,
12215 }),
12216 ) = &comment_prefix
12217 {
12218 let line_trimmed = line_trimmed
12219 .strip_prefix(start.as_ref())
12220 .map(|s| {
12221 let mut indent_size = indent_size;
12222 indent_size.len -= tab_size;
12223 let indent_prefix: String = indent_size.chars().collect();
12224 first_line_delimiter = Some((indent_prefix, start));
12225 s.trim_start()
12226 })
12227 .unwrap_or(line_trimmed);
12228 let line_trimmed = line_trimmed
12229 .strip_suffix(end.as_ref())
12230 .map(|s| {
12231 last_line_delimiter = Some(end);
12232 s.trim_end()
12233 })
12234 .unwrap_or(line_trimmed);
12235 let line_trimmed = line_trimmed
12236 .strip_prefix(prefix.as_ref())
12237 .unwrap_or(line_trimmed);
12238 Ok(line_trimmed)
12239 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12240 line_trimmed.strip_prefix(prefix).with_context(|| {
12241 format!("line did not start with prefix {prefix:?}: {line:?}")
12242 })
12243 } else {
12244 line_trimmed
12245 .strip_prefix(&line_prefix.trim_start())
12246 .with_context(|| {
12247 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12248 })
12249 }
12250 })
12251 .collect::<Result<Vec<_>, _>>()
12252 .log_err()
12253 else {
12254 continue;
12255 };
12256
12257 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12258 buffer
12259 .language_settings_at(Point::new(start_row, 0), cx)
12260 .preferred_line_length as usize
12261 });
12262
12263 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12264 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12265 } else {
12266 line_prefix.clone()
12267 };
12268
12269 let wrapped_text = {
12270 let mut wrapped_text = wrap_with_prefix(
12271 line_prefix,
12272 subsequent_lines_prefix,
12273 lines_without_prefixes.join("\n"),
12274 wrap_column,
12275 tab_size,
12276 options.preserve_existing_whitespace,
12277 );
12278
12279 if let Some((indent, delimiter)) = first_line_delimiter {
12280 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12281 }
12282 if let Some(last_line) = last_line_delimiter {
12283 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12284 }
12285
12286 wrapped_text
12287 };
12288
12289 // TODO: should always use char-based diff while still supporting cursor behavior that
12290 // matches vim.
12291 let mut diff_options = DiffOptions::default();
12292 if options.override_language_settings {
12293 diff_options.max_word_diff_len = 0;
12294 diff_options.max_word_diff_line_count = 0;
12295 } else {
12296 diff_options.max_word_diff_len = usize::MAX;
12297 diff_options.max_word_diff_line_count = usize::MAX;
12298 }
12299
12300 for (old_range, new_text) in
12301 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12302 {
12303 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12304 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12305 edits.push((edit_start..edit_end, new_text));
12306 }
12307
12308 rewrapped_row_ranges.push(start_row..=end_row);
12309 }
12310
12311 self.buffer
12312 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12313 }
12314
12315 pub fn cut_common(
12316 &mut self,
12317 cut_no_selection_line: bool,
12318 window: &mut Window,
12319 cx: &mut Context<Self>,
12320 ) -> ClipboardItem {
12321 let mut text = String::new();
12322 let buffer = self.buffer.read(cx).snapshot(cx);
12323 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12324 let mut clipboard_selections = Vec::with_capacity(selections.len());
12325 {
12326 let max_point = buffer.max_point();
12327 let mut is_first = true;
12328 for selection in &mut selections {
12329 let is_entire_line =
12330 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12331 if is_entire_line {
12332 selection.start = Point::new(selection.start.row, 0);
12333 if !selection.is_empty() && selection.end.column == 0 {
12334 selection.end = cmp::min(max_point, selection.end);
12335 } else {
12336 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12337 }
12338 selection.goal = SelectionGoal::None;
12339 }
12340 if is_first {
12341 is_first = false;
12342 } else {
12343 text += "\n";
12344 }
12345 let mut len = 0;
12346 for chunk in buffer.text_for_range(selection.start..selection.end) {
12347 text.push_str(chunk);
12348 len += chunk.len();
12349 }
12350 clipboard_selections.push(ClipboardSelection {
12351 len,
12352 is_entire_line,
12353 first_line_indent: buffer
12354 .indent_size_for_line(MultiBufferRow(selection.start.row))
12355 .len,
12356 });
12357 }
12358 }
12359
12360 self.transact(window, cx, |this, window, cx| {
12361 this.change_selections(Default::default(), window, cx, |s| {
12362 s.select(selections);
12363 });
12364 this.insert("", window, cx);
12365 });
12366 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12367 }
12368
12369 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12370 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12371 let item = self.cut_common(true, window, cx);
12372 cx.write_to_clipboard(item);
12373 }
12374
12375 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12376 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12377 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12378 s.move_with(|snapshot, sel| {
12379 if sel.is_empty() {
12380 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12381 }
12382 if sel.is_empty() {
12383 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12384 }
12385 });
12386 });
12387 let item = self.cut_common(false, window, cx);
12388 cx.set_global(KillRing(item))
12389 }
12390
12391 pub fn kill_ring_yank(
12392 &mut self,
12393 _: &KillRingYank,
12394 window: &mut Window,
12395 cx: &mut Context<Self>,
12396 ) {
12397 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12398 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12399 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12400 (kill_ring.text().to_string(), kill_ring.metadata_json())
12401 } else {
12402 return;
12403 }
12404 } else {
12405 return;
12406 };
12407 self.do_paste(&text, metadata, false, window, cx);
12408 }
12409
12410 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12411 self.do_copy(true, cx);
12412 }
12413
12414 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12415 self.do_copy(false, cx);
12416 }
12417
12418 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12419 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12420 let buffer = self.buffer.read(cx).read(cx);
12421 let mut text = String::new();
12422
12423 let mut clipboard_selections = Vec::with_capacity(selections.len());
12424 {
12425 let max_point = buffer.max_point();
12426 let mut is_first = true;
12427 for selection in &selections {
12428 let mut start = selection.start;
12429 let mut end = selection.end;
12430 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12431 let mut add_trailing_newline = false;
12432 if is_entire_line {
12433 start = Point::new(start.row, 0);
12434 let next_line_start = Point::new(end.row + 1, 0);
12435 if next_line_start <= max_point {
12436 end = next_line_start;
12437 } else {
12438 // We're on the last line without a trailing newline.
12439 // Copy to the end of the line and add a newline afterwards.
12440 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12441 add_trailing_newline = true;
12442 }
12443 }
12444
12445 let mut trimmed_selections = Vec::new();
12446 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12447 let row = MultiBufferRow(start.row);
12448 let first_indent = buffer.indent_size_for_line(row);
12449 if first_indent.len == 0 || start.column > first_indent.len {
12450 trimmed_selections.push(start..end);
12451 } else {
12452 trimmed_selections.push(
12453 Point::new(row.0, first_indent.len)
12454 ..Point::new(row.0, buffer.line_len(row)),
12455 );
12456 for row in start.row + 1..=end.row {
12457 let mut line_len = buffer.line_len(MultiBufferRow(row));
12458 if row == end.row {
12459 line_len = end.column;
12460 }
12461 if line_len == 0 {
12462 trimmed_selections
12463 .push(Point::new(row, 0)..Point::new(row, line_len));
12464 continue;
12465 }
12466 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12467 if row_indent_size.len >= first_indent.len {
12468 trimmed_selections.push(
12469 Point::new(row, first_indent.len)..Point::new(row, line_len),
12470 );
12471 } else {
12472 trimmed_selections.clear();
12473 trimmed_selections.push(start..end);
12474 break;
12475 }
12476 }
12477 }
12478 } else {
12479 trimmed_selections.push(start..end);
12480 }
12481
12482 for trimmed_range in trimmed_selections {
12483 if is_first {
12484 is_first = false;
12485 } else {
12486 text += "\n";
12487 }
12488 let mut len = 0;
12489 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12490 text.push_str(chunk);
12491 len += chunk.len();
12492 }
12493 if add_trailing_newline {
12494 text.push('\n');
12495 len += 1;
12496 }
12497 clipboard_selections.push(ClipboardSelection {
12498 len,
12499 is_entire_line,
12500 first_line_indent: buffer
12501 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12502 .len,
12503 });
12504 }
12505 }
12506 }
12507
12508 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12509 text,
12510 clipboard_selections,
12511 ));
12512 }
12513
12514 pub fn do_paste(
12515 &mut self,
12516 text: &String,
12517 clipboard_selections: Option<Vec<ClipboardSelection>>,
12518 handle_entire_lines: bool,
12519 window: &mut Window,
12520 cx: &mut Context<Self>,
12521 ) {
12522 if self.read_only(cx) {
12523 return;
12524 }
12525
12526 let clipboard_text = Cow::Borrowed(text.as_str());
12527
12528 self.transact(window, cx, |this, window, cx| {
12529 let had_active_edit_prediction = this.has_active_edit_prediction();
12530 let display_map = this.display_snapshot(cx);
12531 let old_selections = this.selections.all::<usize>(&display_map);
12532 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12533
12534 if let Some(mut clipboard_selections) = clipboard_selections {
12535 let all_selections_were_entire_line =
12536 clipboard_selections.iter().all(|s| s.is_entire_line);
12537 let first_selection_indent_column =
12538 clipboard_selections.first().map(|s| s.first_line_indent);
12539 if clipboard_selections.len() != old_selections.len() {
12540 clipboard_selections.drain(..);
12541 }
12542 let mut auto_indent_on_paste = true;
12543
12544 this.buffer.update(cx, |buffer, cx| {
12545 let snapshot = buffer.read(cx);
12546 auto_indent_on_paste = snapshot
12547 .language_settings_at(cursor_offset, cx)
12548 .auto_indent_on_paste;
12549
12550 let mut start_offset = 0;
12551 let mut edits = Vec::new();
12552 let mut original_indent_columns = Vec::new();
12553 for (ix, selection) in old_selections.iter().enumerate() {
12554 let to_insert;
12555 let entire_line;
12556 let original_indent_column;
12557 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12558 let end_offset = start_offset + clipboard_selection.len;
12559 to_insert = &clipboard_text[start_offset..end_offset];
12560 entire_line = clipboard_selection.is_entire_line;
12561 start_offset = end_offset + 1;
12562 original_indent_column = Some(clipboard_selection.first_line_indent);
12563 } else {
12564 to_insert = &*clipboard_text;
12565 entire_line = all_selections_were_entire_line;
12566 original_indent_column = first_selection_indent_column
12567 }
12568
12569 let (range, to_insert) =
12570 if selection.is_empty() && handle_entire_lines && entire_line {
12571 // If the corresponding selection was empty when this slice of the
12572 // clipboard text was written, then the entire line containing the
12573 // selection was copied. If this selection is also currently empty,
12574 // then paste the line before the current line of the buffer.
12575 let column = selection.start.to_point(&snapshot).column as usize;
12576 let line_start = selection.start - column;
12577 (line_start..line_start, Cow::Borrowed(to_insert))
12578 } else {
12579 let language = snapshot.language_at(selection.head());
12580 let range = selection.range();
12581 if let Some(language) = language
12582 && language.name() == "Markdown".into()
12583 {
12584 edit_for_markdown_paste(
12585 &snapshot,
12586 range,
12587 to_insert,
12588 url::Url::parse(to_insert).ok(),
12589 )
12590 } else {
12591 (range, Cow::Borrowed(to_insert))
12592 }
12593 };
12594
12595 edits.push((range, to_insert));
12596 original_indent_columns.push(original_indent_column);
12597 }
12598 drop(snapshot);
12599
12600 buffer.edit(
12601 edits,
12602 if auto_indent_on_paste {
12603 Some(AutoindentMode::Block {
12604 original_indent_columns,
12605 })
12606 } else {
12607 None
12608 },
12609 cx,
12610 );
12611 });
12612
12613 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12614 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12615 } else {
12616 let url = url::Url::parse(&clipboard_text).ok();
12617
12618 let auto_indent_mode = if !clipboard_text.is_empty() {
12619 Some(AutoindentMode::Block {
12620 original_indent_columns: Vec::new(),
12621 })
12622 } else {
12623 None
12624 };
12625
12626 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12627 let snapshot = buffer.snapshot(cx);
12628
12629 let anchors = old_selections
12630 .iter()
12631 .map(|s| {
12632 let anchor = snapshot.anchor_after(s.head());
12633 s.map(|_| anchor)
12634 })
12635 .collect::<Vec<_>>();
12636
12637 let mut edits = Vec::new();
12638
12639 for selection in old_selections.iter() {
12640 let language = snapshot.language_at(selection.head());
12641 let range = selection.range();
12642
12643 let (edit_range, edit_text) = if let Some(language) = language
12644 && language.name() == "Markdown".into()
12645 {
12646 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12647 } else {
12648 (range, clipboard_text.clone())
12649 };
12650
12651 edits.push((edit_range, edit_text));
12652 }
12653
12654 drop(snapshot);
12655 buffer.edit(edits, auto_indent_mode, cx);
12656
12657 anchors
12658 });
12659
12660 this.change_selections(Default::default(), window, cx, |s| {
12661 s.select_anchors(selection_anchors);
12662 });
12663 }
12664
12665 let trigger_in_words =
12666 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12667
12668 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12669 });
12670 }
12671
12672 pub fn diff_clipboard_with_selection(
12673 &mut self,
12674 _: &DiffClipboardWithSelection,
12675 window: &mut Window,
12676 cx: &mut Context<Self>,
12677 ) {
12678 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12679
12680 if selections.is_empty() {
12681 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12682 return;
12683 };
12684
12685 let clipboard_text = match cx.read_from_clipboard() {
12686 Some(item) => match item.entries().first() {
12687 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12688 _ => None,
12689 },
12690 None => None,
12691 };
12692
12693 let Some(clipboard_text) = clipboard_text else {
12694 log::warn!("Clipboard doesn't contain text.");
12695 return;
12696 };
12697
12698 window.dispatch_action(
12699 Box::new(DiffClipboardWithSelectionData {
12700 clipboard_text,
12701 editor: cx.entity(),
12702 }),
12703 cx,
12704 );
12705 }
12706
12707 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12709 if let Some(item) = cx.read_from_clipboard() {
12710 let entries = item.entries();
12711
12712 match entries.first() {
12713 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12714 // of all the pasted entries.
12715 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12716 .do_paste(
12717 clipboard_string.text(),
12718 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12719 true,
12720 window,
12721 cx,
12722 ),
12723 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12724 }
12725 }
12726 }
12727
12728 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12729 if self.read_only(cx) {
12730 return;
12731 }
12732
12733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12734
12735 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12736 if let Some((selections, _)) =
12737 self.selection_history.transaction(transaction_id).cloned()
12738 {
12739 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12740 s.select_anchors(selections.to_vec());
12741 });
12742 } else {
12743 log::error!(
12744 "No entry in selection_history found for undo. \
12745 This may correspond to a bug where undo does not update the selection. \
12746 If this is occurring, please add details to \
12747 https://github.com/zed-industries/zed/issues/22692"
12748 );
12749 }
12750 self.request_autoscroll(Autoscroll::fit(), cx);
12751 self.unmark_text(window, cx);
12752 self.refresh_edit_prediction(true, false, window, cx);
12753 cx.emit(EditorEvent::Edited { transaction_id });
12754 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12755 }
12756 }
12757
12758 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12759 if self.read_only(cx) {
12760 return;
12761 }
12762
12763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12764
12765 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12766 if let Some((_, Some(selections))) =
12767 self.selection_history.transaction(transaction_id).cloned()
12768 {
12769 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12770 s.select_anchors(selections.to_vec());
12771 });
12772 } else {
12773 log::error!(
12774 "No entry in selection_history found for redo. \
12775 This may correspond to a bug where undo does not update the selection. \
12776 If this is occurring, please add details to \
12777 https://github.com/zed-industries/zed/issues/22692"
12778 );
12779 }
12780 self.request_autoscroll(Autoscroll::fit(), cx);
12781 self.unmark_text(window, cx);
12782 self.refresh_edit_prediction(true, false, window, cx);
12783 cx.emit(EditorEvent::Edited { transaction_id });
12784 }
12785 }
12786
12787 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12788 self.buffer
12789 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12790 }
12791
12792 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12793 self.buffer
12794 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12795 }
12796
12797 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12799 self.change_selections(Default::default(), window, cx, |s| {
12800 s.move_with(|map, selection| {
12801 let cursor = if selection.is_empty() {
12802 movement::left(map, selection.start)
12803 } else {
12804 selection.start
12805 };
12806 selection.collapse_to(cursor, SelectionGoal::None);
12807 });
12808 })
12809 }
12810
12811 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12812 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12813 self.change_selections(Default::default(), window, cx, |s| {
12814 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12815 })
12816 }
12817
12818 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12820 self.change_selections(Default::default(), window, cx, |s| {
12821 s.move_with(|map, selection| {
12822 let cursor = if selection.is_empty() {
12823 movement::right(map, selection.end)
12824 } else {
12825 selection.end
12826 };
12827 selection.collapse_to(cursor, SelectionGoal::None)
12828 });
12829 })
12830 }
12831
12832 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12834 self.change_selections(Default::default(), window, cx, |s| {
12835 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12836 });
12837 }
12838
12839 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12840 if self.take_rename(true, window, cx).is_some() {
12841 return;
12842 }
12843
12844 if self.mode.is_single_line() {
12845 cx.propagate();
12846 return;
12847 }
12848
12849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12850
12851 let text_layout_details = &self.text_layout_details(window);
12852 let selection_count = self.selections.count();
12853 let first_selection = self.selections.first_anchor();
12854
12855 self.change_selections(Default::default(), window, cx, |s| {
12856 s.move_with(|map, selection| {
12857 if !selection.is_empty() {
12858 selection.goal = SelectionGoal::None;
12859 }
12860 let (cursor, goal) = movement::up(
12861 map,
12862 selection.start,
12863 selection.goal,
12864 false,
12865 text_layout_details,
12866 );
12867 selection.collapse_to(cursor, goal);
12868 });
12869 });
12870
12871 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12872 {
12873 cx.propagate();
12874 }
12875 }
12876
12877 pub fn move_up_by_lines(
12878 &mut self,
12879 action: &MoveUpByLines,
12880 window: &mut Window,
12881 cx: &mut Context<Self>,
12882 ) {
12883 if self.take_rename(true, window, cx).is_some() {
12884 return;
12885 }
12886
12887 if self.mode.is_single_line() {
12888 cx.propagate();
12889 return;
12890 }
12891
12892 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12893
12894 let text_layout_details = &self.text_layout_details(window);
12895
12896 self.change_selections(Default::default(), window, cx, |s| {
12897 s.move_with(|map, selection| {
12898 if !selection.is_empty() {
12899 selection.goal = SelectionGoal::None;
12900 }
12901 let (cursor, goal) = movement::up_by_rows(
12902 map,
12903 selection.start,
12904 action.lines,
12905 selection.goal,
12906 false,
12907 text_layout_details,
12908 );
12909 selection.collapse_to(cursor, goal);
12910 });
12911 })
12912 }
12913
12914 pub fn move_down_by_lines(
12915 &mut self,
12916 action: &MoveDownByLines,
12917 window: &mut Window,
12918 cx: &mut Context<Self>,
12919 ) {
12920 if self.take_rename(true, window, cx).is_some() {
12921 return;
12922 }
12923
12924 if self.mode.is_single_line() {
12925 cx.propagate();
12926 return;
12927 }
12928
12929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12930
12931 let text_layout_details = &self.text_layout_details(window);
12932
12933 self.change_selections(Default::default(), window, cx, |s| {
12934 s.move_with(|map, selection| {
12935 if !selection.is_empty() {
12936 selection.goal = SelectionGoal::None;
12937 }
12938 let (cursor, goal) = movement::down_by_rows(
12939 map,
12940 selection.start,
12941 action.lines,
12942 selection.goal,
12943 false,
12944 text_layout_details,
12945 );
12946 selection.collapse_to(cursor, goal);
12947 });
12948 })
12949 }
12950
12951 pub fn select_down_by_lines(
12952 &mut self,
12953 action: &SelectDownByLines,
12954 window: &mut Window,
12955 cx: &mut Context<Self>,
12956 ) {
12957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12958 let text_layout_details = &self.text_layout_details(window);
12959 self.change_selections(Default::default(), window, cx, |s| {
12960 s.move_heads_with(|map, head, goal| {
12961 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12962 })
12963 })
12964 }
12965
12966 pub fn select_up_by_lines(
12967 &mut self,
12968 action: &SelectUpByLines,
12969 window: &mut Window,
12970 cx: &mut Context<Self>,
12971 ) {
12972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12973 let text_layout_details = &self.text_layout_details(window);
12974 self.change_selections(Default::default(), window, cx, |s| {
12975 s.move_heads_with(|map, head, goal| {
12976 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12977 })
12978 })
12979 }
12980
12981 pub fn select_page_up(
12982 &mut self,
12983 _: &SelectPageUp,
12984 window: &mut Window,
12985 cx: &mut Context<Self>,
12986 ) {
12987 let Some(row_count) = self.visible_row_count() else {
12988 return;
12989 };
12990
12991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12992
12993 let text_layout_details = &self.text_layout_details(window);
12994
12995 self.change_selections(Default::default(), window, cx, |s| {
12996 s.move_heads_with(|map, head, goal| {
12997 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12998 })
12999 })
13000 }
13001
13002 pub fn move_page_up(
13003 &mut self,
13004 action: &MovePageUp,
13005 window: &mut Window,
13006 cx: &mut Context<Self>,
13007 ) {
13008 if self.take_rename(true, window, cx).is_some() {
13009 return;
13010 }
13011
13012 if self
13013 .context_menu
13014 .borrow_mut()
13015 .as_mut()
13016 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13017 .unwrap_or(false)
13018 {
13019 return;
13020 }
13021
13022 if matches!(self.mode, EditorMode::SingleLine) {
13023 cx.propagate();
13024 return;
13025 }
13026
13027 let Some(row_count) = self.visible_row_count() else {
13028 return;
13029 };
13030
13031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13032
13033 let effects = if action.center_cursor {
13034 SelectionEffects::scroll(Autoscroll::center())
13035 } else {
13036 SelectionEffects::default()
13037 };
13038
13039 let text_layout_details = &self.text_layout_details(window);
13040
13041 self.change_selections(effects, window, cx, |s| {
13042 s.move_with(|map, selection| {
13043 if !selection.is_empty() {
13044 selection.goal = SelectionGoal::None;
13045 }
13046 let (cursor, goal) = movement::up_by_rows(
13047 map,
13048 selection.end,
13049 row_count,
13050 selection.goal,
13051 false,
13052 text_layout_details,
13053 );
13054 selection.collapse_to(cursor, goal);
13055 });
13056 });
13057 }
13058
13059 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13061 let text_layout_details = &self.text_layout_details(window);
13062 self.change_selections(Default::default(), window, cx, |s| {
13063 s.move_heads_with(|map, head, goal| {
13064 movement::up(map, head, goal, false, text_layout_details)
13065 })
13066 })
13067 }
13068
13069 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13070 self.take_rename(true, window, cx);
13071
13072 if self.mode.is_single_line() {
13073 cx.propagate();
13074 return;
13075 }
13076
13077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13078
13079 let text_layout_details = &self.text_layout_details(window);
13080 let selection_count = self.selections.count();
13081 let first_selection = self.selections.first_anchor();
13082
13083 self.change_selections(Default::default(), window, cx, |s| {
13084 s.move_with(|map, selection| {
13085 if !selection.is_empty() {
13086 selection.goal = SelectionGoal::None;
13087 }
13088 let (cursor, goal) = movement::down(
13089 map,
13090 selection.end,
13091 selection.goal,
13092 false,
13093 text_layout_details,
13094 );
13095 selection.collapse_to(cursor, goal);
13096 });
13097 });
13098
13099 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13100 {
13101 cx.propagate();
13102 }
13103 }
13104
13105 pub fn select_page_down(
13106 &mut self,
13107 _: &SelectPageDown,
13108 window: &mut Window,
13109 cx: &mut Context<Self>,
13110 ) {
13111 let Some(row_count) = self.visible_row_count() else {
13112 return;
13113 };
13114
13115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13116
13117 let text_layout_details = &self.text_layout_details(window);
13118
13119 self.change_selections(Default::default(), window, cx, |s| {
13120 s.move_heads_with(|map, head, goal| {
13121 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13122 })
13123 })
13124 }
13125
13126 pub fn move_page_down(
13127 &mut self,
13128 action: &MovePageDown,
13129 window: &mut Window,
13130 cx: &mut Context<Self>,
13131 ) {
13132 if self.take_rename(true, window, cx).is_some() {
13133 return;
13134 }
13135
13136 if self
13137 .context_menu
13138 .borrow_mut()
13139 .as_mut()
13140 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13141 .unwrap_or(false)
13142 {
13143 return;
13144 }
13145
13146 if matches!(self.mode, EditorMode::SingleLine) {
13147 cx.propagate();
13148 return;
13149 }
13150
13151 let Some(row_count) = self.visible_row_count() else {
13152 return;
13153 };
13154
13155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13156
13157 let effects = if action.center_cursor {
13158 SelectionEffects::scroll(Autoscroll::center())
13159 } else {
13160 SelectionEffects::default()
13161 };
13162
13163 let text_layout_details = &self.text_layout_details(window);
13164 self.change_selections(effects, window, cx, |s| {
13165 s.move_with(|map, selection| {
13166 if !selection.is_empty() {
13167 selection.goal = SelectionGoal::None;
13168 }
13169 let (cursor, goal) = movement::down_by_rows(
13170 map,
13171 selection.end,
13172 row_count,
13173 selection.goal,
13174 false,
13175 text_layout_details,
13176 );
13177 selection.collapse_to(cursor, goal);
13178 });
13179 });
13180 }
13181
13182 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13184 let text_layout_details = &self.text_layout_details(window);
13185 self.change_selections(Default::default(), window, cx, |s| {
13186 s.move_heads_with(|map, head, goal| {
13187 movement::down(map, head, goal, false, text_layout_details)
13188 })
13189 });
13190 }
13191
13192 pub fn context_menu_first(
13193 &mut self,
13194 _: &ContextMenuFirst,
13195 window: &mut Window,
13196 cx: &mut Context<Self>,
13197 ) {
13198 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13199 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13200 }
13201 }
13202
13203 pub fn context_menu_prev(
13204 &mut self,
13205 _: &ContextMenuPrevious,
13206 window: &mut Window,
13207 cx: &mut Context<Self>,
13208 ) {
13209 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13210 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13211 }
13212 }
13213
13214 pub fn context_menu_next(
13215 &mut self,
13216 _: &ContextMenuNext,
13217 window: &mut Window,
13218 cx: &mut Context<Self>,
13219 ) {
13220 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13221 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13222 }
13223 }
13224
13225 pub fn context_menu_last(
13226 &mut self,
13227 _: &ContextMenuLast,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13232 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13233 }
13234 }
13235
13236 pub fn signature_help_prev(
13237 &mut self,
13238 _: &SignatureHelpPrevious,
13239 _: &mut Window,
13240 cx: &mut Context<Self>,
13241 ) {
13242 if let Some(popover) = self.signature_help_state.popover_mut() {
13243 if popover.current_signature == 0 {
13244 popover.current_signature = popover.signatures.len() - 1;
13245 } else {
13246 popover.current_signature -= 1;
13247 }
13248 cx.notify();
13249 }
13250 }
13251
13252 pub fn signature_help_next(
13253 &mut self,
13254 _: &SignatureHelpNext,
13255 _: &mut Window,
13256 cx: &mut Context<Self>,
13257 ) {
13258 if let Some(popover) = self.signature_help_state.popover_mut() {
13259 if popover.current_signature + 1 == popover.signatures.len() {
13260 popover.current_signature = 0;
13261 } else {
13262 popover.current_signature += 1;
13263 }
13264 cx.notify();
13265 }
13266 }
13267
13268 pub fn move_to_previous_word_start(
13269 &mut self,
13270 _: &MoveToPreviousWordStart,
13271 window: &mut Window,
13272 cx: &mut Context<Self>,
13273 ) {
13274 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13275 self.change_selections(Default::default(), window, cx, |s| {
13276 s.move_cursors_with(|map, head, _| {
13277 (
13278 movement::previous_word_start(map, head),
13279 SelectionGoal::None,
13280 )
13281 });
13282 })
13283 }
13284
13285 pub fn move_to_previous_subword_start(
13286 &mut self,
13287 _: &MoveToPreviousSubwordStart,
13288 window: &mut Window,
13289 cx: &mut Context<Self>,
13290 ) {
13291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13292 self.change_selections(Default::default(), window, cx, |s| {
13293 s.move_cursors_with(|map, head, _| {
13294 (
13295 movement::previous_subword_start(map, head),
13296 SelectionGoal::None,
13297 )
13298 });
13299 })
13300 }
13301
13302 pub fn select_to_previous_word_start(
13303 &mut self,
13304 _: &SelectToPreviousWordStart,
13305 window: &mut Window,
13306 cx: &mut Context<Self>,
13307 ) {
13308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13309 self.change_selections(Default::default(), window, cx, |s| {
13310 s.move_heads_with(|map, head, _| {
13311 (
13312 movement::previous_word_start(map, head),
13313 SelectionGoal::None,
13314 )
13315 });
13316 })
13317 }
13318
13319 pub fn select_to_previous_subword_start(
13320 &mut self,
13321 _: &SelectToPreviousSubwordStart,
13322 window: &mut Window,
13323 cx: &mut Context<Self>,
13324 ) {
13325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13326 self.change_selections(Default::default(), window, cx, |s| {
13327 s.move_heads_with(|map, head, _| {
13328 (
13329 movement::previous_subword_start(map, head),
13330 SelectionGoal::None,
13331 )
13332 });
13333 })
13334 }
13335
13336 pub fn delete_to_previous_word_start(
13337 &mut self,
13338 action: &DeleteToPreviousWordStart,
13339 window: &mut Window,
13340 cx: &mut Context<Self>,
13341 ) {
13342 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13343 self.transact(window, cx, |this, window, cx| {
13344 this.select_autoclose_pair(window, cx);
13345 this.change_selections(Default::default(), window, cx, |s| {
13346 s.move_with(|map, selection| {
13347 if selection.is_empty() {
13348 let mut cursor = if action.ignore_newlines {
13349 movement::previous_word_start(map, selection.head())
13350 } else {
13351 movement::previous_word_start_or_newline(map, selection.head())
13352 };
13353 cursor = movement::adjust_greedy_deletion(
13354 map,
13355 selection.head(),
13356 cursor,
13357 action.ignore_brackets,
13358 );
13359 selection.set_head(cursor, SelectionGoal::None);
13360 }
13361 });
13362 });
13363 this.insert("", window, cx);
13364 });
13365 }
13366
13367 pub fn delete_to_previous_subword_start(
13368 &mut self,
13369 _: &DeleteToPreviousSubwordStart,
13370 window: &mut Window,
13371 cx: &mut Context<Self>,
13372 ) {
13373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13374 self.transact(window, cx, |this, window, cx| {
13375 this.select_autoclose_pair(window, cx);
13376 this.change_selections(Default::default(), window, cx, |s| {
13377 s.move_with(|map, selection| {
13378 if selection.is_empty() {
13379 let mut cursor = movement::previous_subword_start(map, selection.head());
13380 cursor =
13381 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13382 selection.set_head(cursor, SelectionGoal::None);
13383 }
13384 });
13385 });
13386 this.insert("", window, cx);
13387 });
13388 }
13389
13390 pub fn move_to_next_word_end(
13391 &mut self,
13392 _: &MoveToNextWordEnd,
13393 window: &mut Window,
13394 cx: &mut Context<Self>,
13395 ) {
13396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13397 self.change_selections(Default::default(), window, cx, |s| {
13398 s.move_cursors_with(|map, head, _| {
13399 (movement::next_word_end(map, head), SelectionGoal::None)
13400 });
13401 })
13402 }
13403
13404 pub fn move_to_next_subword_end(
13405 &mut self,
13406 _: &MoveToNextSubwordEnd,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) {
13410 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13411 self.change_selections(Default::default(), window, cx, |s| {
13412 s.move_cursors_with(|map, head, _| {
13413 (movement::next_subword_end(map, head), SelectionGoal::None)
13414 });
13415 })
13416 }
13417
13418 pub fn select_to_next_word_end(
13419 &mut self,
13420 _: &SelectToNextWordEnd,
13421 window: &mut Window,
13422 cx: &mut Context<Self>,
13423 ) {
13424 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13425 self.change_selections(Default::default(), window, cx, |s| {
13426 s.move_heads_with(|map, head, _| {
13427 (movement::next_word_end(map, head), SelectionGoal::None)
13428 });
13429 })
13430 }
13431
13432 pub fn select_to_next_subword_end(
13433 &mut self,
13434 _: &SelectToNextSubwordEnd,
13435 window: &mut Window,
13436 cx: &mut Context<Self>,
13437 ) {
13438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13439 self.change_selections(Default::default(), window, cx, |s| {
13440 s.move_heads_with(|map, head, _| {
13441 (movement::next_subword_end(map, head), SelectionGoal::None)
13442 });
13443 })
13444 }
13445
13446 pub fn delete_to_next_word_end(
13447 &mut self,
13448 action: &DeleteToNextWordEnd,
13449 window: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) {
13452 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13453 self.transact(window, cx, |this, window, cx| {
13454 this.change_selections(Default::default(), window, cx, |s| {
13455 s.move_with(|map, selection| {
13456 if selection.is_empty() {
13457 let mut cursor = if action.ignore_newlines {
13458 movement::next_word_end(map, selection.head())
13459 } else {
13460 movement::next_word_end_or_newline(map, selection.head())
13461 };
13462 cursor = movement::adjust_greedy_deletion(
13463 map,
13464 selection.head(),
13465 cursor,
13466 action.ignore_brackets,
13467 );
13468 selection.set_head(cursor, SelectionGoal::None);
13469 }
13470 });
13471 });
13472 this.insert("", window, cx);
13473 });
13474 }
13475
13476 pub fn delete_to_next_subword_end(
13477 &mut self,
13478 _: &DeleteToNextSubwordEnd,
13479 window: &mut Window,
13480 cx: &mut Context<Self>,
13481 ) {
13482 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13483 self.transact(window, cx, |this, window, cx| {
13484 this.change_selections(Default::default(), window, cx, |s| {
13485 s.move_with(|map, selection| {
13486 if selection.is_empty() {
13487 let mut cursor = movement::next_subword_end(map, selection.head());
13488 cursor =
13489 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13490 selection.set_head(cursor, SelectionGoal::None);
13491 }
13492 });
13493 });
13494 this.insert("", window, cx);
13495 });
13496 }
13497
13498 pub fn move_to_beginning_of_line(
13499 &mut self,
13500 action: &MoveToBeginningOfLine,
13501 window: &mut Window,
13502 cx: &mut Context<Self>,
13503 ) {
13504 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13505 self.change_selections(Default::default(), window, cx, |s| {
13506 s.move_cursors_with(|map, head, _| {
13507 (
13508 movement::indented_line_beginning(
13509 map,
13510 head,
13511 action.stop_at_soft_wraps,
13512 action.stop_at_indent,
13513 ),
13514 SelectionGoal::None,
13515 )
13516 });
13517 })
13518 }
13519
13520 pub fn select_to_beginning_of_line(
13521 &mut self,
13522 action: &SelectToBeginningOfLine,
13523 window: &mut Window,
13524 cx: &mut Context<Self>,
13525 ) {
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_heads_with(|map, head, _| {
13529 (
13530 movement::indented_line_beginning(
13531 map,
13532 head,
13533 action.stop_at_soft_wraps,
13534 action.stop_at_indent,
13535 ),
13536 SelectionGoal::None,
13537 )
13538 });
13539 });
13540 }
13541
13542 pub fn delete_to_beginning_of_line(
13543 &mut self,
13544 action: &DeleteToBeginningOfLine,
13545 window: &mut Window,
13546 cx: &mut Context<Self>,
13547 ) {
13548 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13549 self.transact(window, cx, |this, window, cx| {
13550 this.change_selections(Default::default(), window, cx, |s| {
13551 s.move_with(|_, selection| {
13552 selection.reversed = true;
13553 });
13554 });
13555
13556 this.select_to_beginning_of_line(
13557 &SelectToBeginningOfLine {
13558 stop_at_soft_wraps: false,
13559 stop_at_indent: action.stop_at_indent,
13560 },
13561 window,
13562 cx,
13563 );
13564 this.backspace(&Backspace, window, cx);
13565 });
13566 }
13567
13568 pub fn move_to_end_of_line(
13569 &mut self,
13570 action: &MoveToEndOfLine,
13571 window: &mut Window,
13572 cx: &mut Context<Self>,
13573 ) {
13574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13575 self.change_selections(Default::default(), window, cx, |s| {
13576 s.move_cursors_with(|map, head, _| {
13577 (
13578 movement::line_end(map, head, action.stop_at_soft_wraps),
13579 SelectionGoal::None,
13580 )
13581 });
13582 })
13583 }
13584
13585 pub fn select_to_end_of_line(
13586 &mut self,
13587 action: &SelectToEndOfLine,
13588 window: &mut Window,
13589 cx: &mut Context<Self>,
13590 ) {
13591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13592 self.change_selections(Default::default(), window, cx, |s| {
13593 s.move_heads_with(|map, head, _| {
13594 (
13595 movement::line_end(map, head, action.stop_at_soft_wraps),
13596 SelectionGoal::None,
13597 )
13598 });
13599 })
13600 }
13601
13602 pub fn delete_to_end_of_line(
13603 &mut self,
13604 _: &DeleteToEndOfLine,
13605 window: &mut Window,
13606 cx: &mut Context<Self>,
13607 ) {
13608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13609 self.transact(window, cx, |this, window, cx| {
13610 this.select_to_end_of_line(
13611 &SelectToEndOfLine {
13612 stop_at_soft_wraps: false,
13613 },
13614 window,
13615 cx,
13616 );
13617 this.delete(&Delete, window, cx);
13618 });
13619 }
13620
13621 pub fn cut_to_end_of_line(
13622 &mut self,
13623 action: &CutToEndOfLine,
13624 window: &mut Window,
13625 cx: &mut Context<Self>,
13626 ) {
13627 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13628 self.transact(window, cx, |this, window, cx| {
13629 this.select_to_end_of_line(
13630 &SelectToEndOfLine {
13631 stop_at_soft_wraps: false,
13632 },
13633 window,
13634 cx,
13635 );
13636 if !action.stop_at_newlines {
13637 this.change_selections(Default::default(), window, cx, |s| {
13638 s.move_with(|_, sel| {
13639 if sel.is_empty() {
13640 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13641 }
13642 });
13643 });
13644 }
13645 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13646 let item = this.cut_common(false, window, cx);
13647 cx.write_to_clipboard(item);
13648 });
13649 }
13650
13651 pub fn move_to_start_of_paragraph(
13652 &mut self,
13653 _: &MoveToStartOfParagraph,
13654 window: &mut Window,
13655 cx: &mut Context<Self>,
13656 ) {
13657 if matches!(self.mode, EditorMode::SingleLine) {
13658 cx.propagate();
13659 return;
13660 }
13661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13662 self.change_selections(Default::default(), window, cx, |s| {
13663 s.move_with(|map, selection| {
13664 selection.collapse_to(
13665 movement::start_of_paragraph(map, selection.head(), 1),
13666 SelectionGoal::None,
13667 )
13668 });
13669 })
13670 }
13671
13672 pub fn move_to_end_of_paragraph(
13673 &mut self,
13674 _: &MoveToEndOfParagraph,
13675 window: &mut Window,
13676 cx: &mut Context<Self>,
13677 ) {
13678 if matches!(self.mode, EditorMode::SingleLine) {
13679 cx.propagate();
13680 return;
13681 }
13682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13683 self.change_selections(Default::default(), window, cx, |s| {
13684 s.move_with(|map, selection| {
13685 selection.collapse_to(
13686 movement::end_of_paragraph(map, selection.head(), 1),
13687 SelectionGoal::None,
13688 )
13689 });
13690 })
13691 }
13692
13693 pub fn select_to_start_of_paragraph(
13694 &mut self,
13695 _: &SelectToStartOfParagraph,
13696 window: &mut Window,
13697 cx: &mut Context<Self>,
13698 ) {
13699 if matches!(self.mode, EditorMode::SingleLine) {
13700 cx.propagate();
13701 return;
13702 }
13703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13704 self.change_selections(Default::default(), window, cx, |s| {
13705 s.move_heads_with(|map, head, _| {
13706 (
13707 movement::start_of_paragraph(map, head, 1),
13708 SelectionGoal::None,
13709 )
13710 });
13711 })
13712 }
13713
13714 pub fn select_to_end_of_paragraph(
13715 &mut self,
13716 _: &SelectToEndOfParagraph,
13717 window: &mut Window,
13718 cx: &mut Context<Self>,
13719 ) {
13720 if matches!(self.mode, EditorMode::SingleLine) {
13721 cx.propagate();
13722 return;
13723 }
13724 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13725 self.change_selections(Default::default(), window, cx, |s| {
13726 s.move_heads_with(|map, head, _| {
13727 (
13728 movement::end_of_paragraph(map, head, 1),
13729 SelectionGoal::None,
13730 )
13731 });
13732 })
13733 }
13734
13735 pub fn move_to_start_of_excerpt(
13736 &mut self,
13737 _: &MoveToStartOfExcerpt,
13738 window: &mut Window,
13739 cx: &mut Context<Self>,
13740 ) {
13741 if matches!(self.mode, EditorMode::SingleLine) {
13742 cx.propagate();
13743 return;
13744 }
13745 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13746 self.change_selections(Default::default(), window, cx, |s| {
13747 s.move_with(|map, selection| {
13748 selection.collapse_to(
13749 movement::start_of_excerpt(
13750 map,
13751 selection.head(),
13752 workspace::searchable::Direction::Prev,
13753 ),
13754 SelectionGoal::None,
13755 )
13756 });
13757 })
13758 }
13759
13760 pub fn move_to_start_of_next_excerpt(
13761 &mut self,
13762 _: &MoveToStartOfNextExcerpt,
13763 window: &mut Window,
13764 cx: &mut Context<Self>,
13765 ) {
13766 if matches!(self.mode, EditorMode::SingleLine) {
13767 cx.propagate();
13768 return;
13769 }
13770
13771 self.change_selections(Default::default(), window, cx, |s| {
13772 s.move_with(|map, selection| {
13773 selection.collapse_to(
13774 movement::start_of_excerpt(
13775 map,
13776 selection.head(),
13777 workspace::searchable::Direction::Next,
13778 ),
13779 SelectionGoal::None,
13780 )
13781 });
13782 })
13783 }
13784
13785 pub fn move_to_end_of_excerpt(
13786 &mut self,
13787 _: &MoveToEndOfExcerpt,
13788 window: &mut Window,
13789 cx: &mut Context<Self>,
13790 ) {
13791 if matches!(self.mode, EditorMode::SingleLine) {
13792 cx.propagate();
13793 return;
13794 }
13795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13796 self.change_selections(Default::default(), window, cx, |s| {
13797 s.move_with(|map, selection| {
13798 selection.collapse_to(
13799 movement::end_of_excerpt(
13800 map,
13801 selection.head(),
13802 workspace::searchable::Direction::Next,
13803 ),
13804 SelectionGoal::None,
13805 )
13806 });
13807 })
13808 }
13809
13810 pub fn move_to_end_of_previous_excerpt(
13811 &mut self,
13812 _: &MoveToEndOfPreviousExcerpt,
13813 window: &mut Window,
13814 cx: &mut Context<Self>,
13815 ) {
13816 if matches!(self.mode, EditorMode::SingleLine) {
13817 cx.propagate();
13818 return;
13819 }
13820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13821 self.change_selections(Default::default(), window, cx, |s| {
13822 s.move_with(|map, selection| {
13823 selection.collapse_to(
13824 movement::end_of_excerpt(
13825 map,
13826 selection.head(),
13827 workspace::searchable::Direction::Prev,
13828 ),
13829 SelectionGoal::None,
13830 )
13831 });
13832 })
13833 }
13834
13835 pub fn select_to_start_of_excerpt(
13836 &mut self,
13837 _: &SelectToStartOfExcerpt,
13838 window: &mut Window,
13839 cx: &mut Context<Self>,
13840 ) {
13841 if matches!(self.mode, EditorMode::SingleLine) {
13842 cx.propagate();
13843 return;
13844 }
13845 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13846 self.change_selections(Default::default(), window, cx, |s| {
13847 s.move_heads_with(|map, head, _| {
13848 (
13849 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13850 SelectionGoal::None,
13851 )
13852 });
13853 })
13854 }
13855
13856 pub fn select_to_start_of_next_excerpt(
13857 &mut self,
13858 _: &SelectToStartOfNextExcerpt,
13859 window: &mut Window,
13860 cx: &mut Context<Self>,
13861 ) {
13862 if matches!(self.mode, EditorMode::SingleLine) {
13863 cx.propagate();
13864 return;
13865 }
13866 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13867 self.change_selections(Default::default(), window, cx, |s| {
13868 s.move_heads_with(|map, head, _| {
13869 (
13870 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13871 SelectionGoal::None,
13872 )
13873 });
13874 })
13875 }
13876
13877 pub fn select_to_end_of_excerpt(
13878 &mut self,
13879 _: &SelectToEndOfExcerpt,
13880 window: &mut Window,
13881 cx: &mut Context<Self>,
13882 ) {
13883 if matches!(self.mode, EditorMode::SingleLine) {
13884 cx.propagate();
13885 return;
13886 }
13887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13888 self.change_selections(Default::default(), window, cx, |s| {
13889 s.move_heads_with(|map, head, _| {
13890 (
13891 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13892 SelectionGoal::None,
13893 )
13894 });
13895 })
13896 }
13897
13898 pub fn select_to_end_of_previous_excerpt(
13899 &mut self,
13900 _: &SelectToEndOfPreviousExcerpt,
13901 window: &mut Window,
13902 cx: &mut Context<Self>,
13903 ) {
13904 if matches!(self.mode, EditorMode::SingleLine) {
13905 cx.propagate();
13906 return;
13907 }
13908 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13909 self.change_selections(Default::default(), window, cx, |s| {
13910 s.move_heads_with(|map, head, _| {
13911 (
13912 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13913 SelectionGoal::None,
13914 )
13915 });
13916 })
13917 }
13918
13919 pub fn move_to_beginning(
13920 &mut self,
13921 _: &MoveToBeginning,
13922 window: &mut Window,
13923 cx: &mut Context<Self>,
13924 ) {
13925 if matches!(self.mode, EditorMode::SingleLine) {
13926 cx.propagate();
13927 return;
13928 }
13929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13930 self.change_selections(Default::default(), window, cx, |s| {
13931 s.select_ranges(vec![0..0]);
13932 });
13933 }
13934
13935 pub fn select_to_beginning(
13936 &mut self,
13937 _: &SelectToBeginning,
13938 window: &mut Window,
13939 cx: &mut Context<Self>,
13940 ) {
13941 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13942 selection.set_head(Point::zero(), SelectionGoal::None);
13943 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13944 self.change_selections(Default::default(), window, cx, |s| {
13945 s.select(vec![selection]);
13946 });
13947 }
13948
13949 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13950 if matches!(self.mode, EditorMode::SingleLine) {
13951 cx.propagate();
13952 return;
13953 }
13954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13955 let cursor = self.buffer.read(cx).read(cx).len();
13956 self.change_selections(Default::default(), window, cx, |s| {
13957 s.select_ranges(vec![cursor..cursor])
13958 });
13959 }
13960
13961 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13962 self.nav_history = nav_history;
13963 }
13964
13965 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13966 self.nav_history.as_ref()
13967 }
13968
13969 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13970 self.push_to_nav_history(
13971 self.selections.newest_anchor().head(),
13972 None,
13973 false,
13974 true,
13975 cx,
13976 );
13977 }
13978
13979 fn push_to_nav_history(
13980 &mut self,
13981 cursor_anchor: Anchor,
13982 new_position: Option<Point>,
13983 is_deactivate: bool,
13984 always: bool,
13985 cx: &mut Context<Self>,
13986 ) {
13987 if let Some(nav_history) = self.nav_history.as_mut() {
13988 let buffer = self.buffer.read(cx).read(cx);
13989 let cursor_position = cursor_anchor.to_point(&buffer);
13990 let scroll_state = self.scroll_manager.anchor();
13991 let scroll_top_row = scroll_state.top_row(&buffer);
13992 drop(buffer);
13993
13994 if let Some(new_position) = new_position {
13995 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13996 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13997 return;
13998 }
13999 }
14000
14001 nav_history.push(
14002 Some(NavigationData {
14003 cursor_anchor,
14004 cursor_position,
14005 scroll_anchor: scroll_state,
14006 scroll_top_row,
14007 }),
14008 cx,
14009 );
14010 cx.emit(EditorEvent::PushedToNavHistory {
14011 anchor: cursor_anchor,
14012 is_deactivate,
14013 })
14014 }
14015 }
14016
14017 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14019 let buffer = self.buffer.read(cx).snapshot(cx);
14020 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14021 selection.set_head(buffer.len(), SelectionGoal::None);
14022 self.change_selections(Default::default(), window, cx, |s| {
14023 s.select(vec![selection]);
14024 });
14025 }
14026
14027 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14029 let end = self.buffer.read(cx).read(cx).len();
14030 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14031 s.select_ranges(vec![0..end]);
14032 });
14033 }
14034
14035 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14038 let mut selections = self.selections.all::<Point>(&display_map);
14039 let max_point = display_map.buffer_snapshot().max_point();
14040 for selection in &mut selections {
14041 let rows = selection.spanned_rows(true, &display_map);
14042 selection.start = Point::new(rows.start.0, 0);
14043 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14044 selection.reversed = false;
14045 }
14046 self.change_selections(Default::default(), window, cx, |s| {
14047 s.select(selections);
14048 });
14049 }
14050
14051 pub fn split_selection_into_lines(
14052 &mut self,
14053 action: &SplitSelectionIntoLines,
14054 window: &mut Window,
14055 cx: &mut Context<Self>,
14056 ) {
14057 let selections = self
14058 .selections
14059 .all::<Point>(&self.display_snapshot(cx))
14060 .into_iter()
14061 .map(|selection| selection.start..selection.end)
14062 .collect::<Vec<_>>();
14063 self.unfold_ranges(&selections, true, true, cx);
14064
14065 let mut new_selection_ranges = Vec::new();
14066 {
14067 let buffer = self.buffer.read(cx).read(cx);
14068 for selection in selections {
14069 for row in selection.start.row..selection.end.row {
14070 let line_start = Point::new(row, 0);
14071 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14072
14073 if action.keep_selections {
14074 // Keep the selection range for each line
14075 let selection_start = if row == selection.start.row {
14076 selection.start
14077 } else {
14078 line_start
14079 };
14080 new_selection_ranges.push(selection_start..line_end);
14081 } else {
14082 // Collapse to cursor at end of line
14083 new_selection_ranges.push(line_end..line_end);
14084 }
14085 }
14086
14087 let is_multiline_selection = selection.start.row != selection.end.row;
14088 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14089 // so this action feels more ergonomic when paired with other selection operations
14090 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14091 if !should_skip_last {
14092 if action.keep_selections {
14093 if is_multiline_selection {
14094 let line_start = Point::new(selection.end.row, 0);
14095 new_selection_ranges.push(line_start..selection.end);
14096 } else {
14097 new_selection_ranges.push(selection.start..selection.end);
14098 }
14099 } else {
14100 new_selection_ranges.push(selection.end..selection.end);
14101 }
14102 }
14103 }
14104 }
14105 self.change_selections(Default::default(), window, cx, |s| {
14106 s.select_ranges(new_selection_ranges);
14107 });
14108 }
14109
14110 pub fn add_selection_above(
14111 &mut self,
14112 action: &AddSelectionAbove,
14113 window: &mut Window,
14114 cx: &mut Context<Self>,
14115 ) {
14116 self.add_selection(true, action.skip_soft_wrap, window, cx);
14117 }
14118
14119 pub fn add_selection_below(
14120 &mut self,
14121 action: &AddSelectionBelow,
14122 window: &mut Window,
14123 cx: &mut Context<Self>,
14124 ) {
14125 self.add_selection(false, action.skip_soft_wrap, window, cx);
14126 }
14127
14128 fn add_selection(
14129 &mut self,
14130 above: bool,
14131 skip_soft_wrap: bool,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) {
14135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14136
14137 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14138 let all_selections = self.selections.all::<Point>(&display_map);
14139 let text_layout_details = self.text_layout_details(window);
14140
14141 let (mut columnar_selections, new_selections_to_columnarize) = {
14142 if let Some(state) = self.add_selections_state.as_ref() {
14143 let columnar_selection_ids: HashSet<_> = state
14144 .groups
14145 .iter()
14146 .flat_map(|group| group.stack.iter())
14147 .copied()
14148 .collect();
14149
14150 all_selections
14151 .into_iter()
14152 .partition(|s| columnar_selection_ids.contains(&s.id))
14153 } else {
14154 (Vec::new(), all_selections)
14155 }
14156 };
14157
14158 let mut state = self
14159 .add_selections_state
14160 .take()
14161 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14162
14163 for selection in new_selections_to_columnarize {
14164 let range = selection.display_range(&display_map).sorted();
14165 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14166 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14167 let positions = start_x.min(end_x)..start_x.max(end_x);
14168 let mut stack = Vec::new();
14169 for row in range.start.row().0..=range.end.row().0 {
14170 if let Some(selection) = self.selections.build_columnar_selection(
14171 &display_map,
14172 DisplayRow(row),
14173 &positions,
14174 selection.reversed,
14175 &text_layout_details,
14176 ) {
14177 stack.push(selection.id);
14178 columnar_selections.push(selection);
14179 }
14180 }
14181 if !stack.is_empty() {
14182 if above {
14183 stack.reverse();
14184 }
14185 state.groups.push(AddSelectionsGroup { above, stack });
14186 }
14187 }
14188
14189 let mut final_selections = Vec::new();
14190 let end_row = if above {
14191 DisplayRow(0)
14192 } else {
14193 display_map.max_point().row()
14194 };
14195
14196 let mut last_added_item_per_group = HashMap::default();
14197 for group in state.groups.iter_mut() {
14198 if let Some(last_id) = group.stack.last() {
14199 last_added_item_per_group.insert(*last_id, group);
14200 }
14201 }
14202
14203 for selection in columnar_selections {
14204 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14205 if above == group.above {
14206 let range = selection.display_range(&display_map).sorted();
14207 debug_assert_eq!(range.start.row(), range.end.row());
14208 let mut row = range.start.row();
14209 let positions =
14210 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14211 Pixels::from(start)..Pixels::from(end)
14212 } else {
14213 let start_x =
14214 display_map.x_for_display_point(range.start, &text_layout_details);
14215 let end_x =
14216 display_map.x_for_display_point(range.end, &text_layout_details);
14217 start_x.min(end_x)..start_x.max(end_x)
14218 };
14219
14220 let mut maybe_new_selection = None;
14221 let direction = if above { -1 } else { 1 };
14222
14223 while row != end_row {
14224 if skip_soft_wrap {
14225 row = display_map
14226 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14227 .row();
14228 } else if above {
14229 row.0 -= 1;
14230 } else {
14231 row.0 += 1;
14232 }
14233
14234 if let Some(new_selection) = self.selections.build_columnar_selection(
14235 &display_map,
14236 row,
14237 &positions,
14238 selection.reversed,
14239 &text_layout_details,
14240 ) {
14241 maybe_new_selection = Some(new_selection);
14242 break;
14243 }
14244 }
14245
14246 if let Some(new_selection) = maybe_new_selection {
14247 group.stack.push(new_selection.id);
14248 if above {
14249 final_selections.push(new_selection);
14250 final_selections.push(selection);
14251 } else {
14252 final_selections.push(selection);
14253 final_selections.push(new_selection);
14254 }
14255 } else {
14256 final_selections.push(selection);
14257 }
14258 } else {
14259 group.stack.pop();
14260 }
14261 } else {
14262 final_selections.push(selection);
14263 }
14264 }
14265
14266 self.change_selections(Default::default(), window, cx, |s| {
14267 s.select(final_selections);
14268 });
14269
14270 let final_selection_ids: HashSet<_> = self
14271 .selections
14272 .all::<Point>(&display_map)
14273 .iter()
14274 .map(|s| s.id)
14275 .collect();
14276 state.groups.retain_mut(|group| {
14277 // selections might get merged above so we remove invalid items from stacks
14278 group.stack.retain(|id| final_selection_ids.contains(id));
14279
14280 // single selection in stack can be treated as initial state
14281 group.stack.len() > 1
14282 });
14283
14284 if !state.groups.is_empty() {
14285 self.add_selections_state = Some(state);
14286 }
14287 }
14288
14289 fn select_match_ranges(
14290 &mut self,
14291 range: Range<usize>,
14292 reversed: bool,
14293 replace_newest: bool,
14294 auto_scroll: Option<Autoscroll>,
14295 window: &mut Window,
14296 cx: &mut Context<Editor>,
14297 ) {
14298 self.unfold_ranges(
14299 std::slice::from_ref(&range),
14300 false,
14301 auto_scroll.is_some(),
14302 cx,
14303 );
14304 let effects = if let Some(scroll) = auto_scroll {
14305 SelectionEffects::scroll(scroll)
14306 } else {
14307 SelectionEffects::no_scroll()
14308 };
14309 self.change_selections(effects, window, cx, |s| {
14310 if replace_newest {
14311 s.delete(s.newest_anchor().id);
14312 }
14313 if reversed {
14314 s.insert_range(range.end..range.start);
14315 } else {
14316 s.insert_range(range);
14317 }
14318 });
14319 }
14320
14321 pub fn select_next_match_internal(
14322 &mut self,
14323 display_map: &DisplaySnapshot,
14324 replace_newest: bool,
14325 autoscroll: Option<Autoscroll>,
14326 window: &mut Window,
14327 cx: &mut Context<Self>,
14328 ) -> Result<()> {
14329 let buffer = display_map.buffer_snapshot();
14330 let mut selections = self.selections.all::<usize>(&display_map);
14331 if let Some(mut select_next_state) = self.select_next_state.take() {
14332 let query = &select_next_state.query;
14333 if !select_next_state.done {
14334 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14335 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14336 let mut next_selected_range = None;
14337
14338 let bytes_after_last_selection =
14339 buffer.bytes_in_range(last_selection.end..buffer.len());
14340 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14341 let query_matches = query
14342 .stream_find_iter(bytes_after_last_selection)
14343 .map(|result| (last_selection.end, result))
14344 .chain(
14345 query
14346 .stream_find_iter(bytes_before_first_selection)
14347 .map(|result| (0, result)),
14348 );
14349
14350 for (start_offset, query_match) in query_matches {
14351 let query_match = query_match.unwrap(); // can only fail due to I/O
14352 let offset_range =
14353 start_offset + query_match.start()..start_offset + query_match.end();
14354
14355 if !select_next_state.wordwise
14356 || (!buffer.is_inside_word(offset_range.start, None)
14357 && !buffer.is_inside_word(offset_range.end, None))
14358 {
14359 let idx = selections
14360 .partition_point(|selection| selection.end <= offset_range.start);
14361 let overlaps = selections
14362 .get(idx)
14363 .map_or(false, |selection| selection.start < offset_range.end);
14364
14365 if !overlaps {
14366 next_selected_range = Some(offset_range);
14367 break;
14368 }
14369 }
14370 }
14371
14372 if let Some(next_selected_range) = next_selected_range {
14373 self.select_match_ranges(
14374 next_selected_range,
14375 last_selection.reversed,
14376 replace_newest,
14377 autoscroll,
14378 window,
14379 cx,
14380 );
14381 } else {
14382 select_next_state.done = true;
14383 }
14384 }
14385
14386 self.select_next_state = Some(select_next_state);
14387 } else {
14388 let mut only_carets = true;
14389 let mut same_text_selected = true;
14390 let mut selected_text = None;
14391
14392 let mut selections_iter = selections.iter().peekable();
14393 while let Some(selection) = selections_iter.next() {
14394 if selection.start != selection.end {
14395 only_carets = false;
14396 }
14397
14398 if same_text_selected {
14399 if selected_text.is_none() {
14400 selected_text =
14401 Some(buffer.text_for_range(selection.range()).collect::<String>());
14402 }
14403
14404 if let Some(next_selection) = selections_iter.peek() {
14405 if next_selection.range().len() == selection.range().len() {
14406 let next_selected_text = buffer
14407 .text_for_range(next_selection.range())
14408 .collect::<String>();
14409 if Some(next_selected_text) != selected_text {
14410 same_text_selected = false;
14411 selected_text = None;
14412 }
14413 } else {
14414 same_text_selected = false;
14415 selected_text = None;
14416 }
14417 }
14418 }
14419 }
14420
14421 if only_carets {
14422 for selection in &mut selections {
14423 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14424 selection.start = word_range.start;
14425 selection.end = word_range.end;
14426 selection.goal = SelectionGoal::None;
14427 selection.reversed = false;
14428 self.select_match_ranges(
14429 selection.start..selection.end,
14430 selection.reversed,
14431 replace_newest,
14432 autoscroll,
14433 window,
14434 cx,
14435 );
14436 }
14437
14438 if selections.len() == 1 {
14439 let selection = selections
14440 .last()
14441 .expect("ensured that there's only one selection");
14442 let query = buffer
14443 .text_for_range(selection.start..selection.end)
14444 .collect::<String>();
14445 let is_empty = query.is_empty();
14446 let select_state = SelectNextState {
14447 query: AhoCorasick::new(&[query])?,
14448 wordwise: true,
14449 done: is_empty,
14450 };
14451 self.select_next_state = Some(select_state);
14452 } else {
14453 self.select_next_state = None;
14454 }
14455 } else if let Some(selected_text) = selected_text {
14456 self.select_next_state = Some(SelectNextState {
14457 query: AhoCorasick::new(&[selected_text])?,
14458 wordwise: false,
14459 done: false,
14460 });
14461 self.select_next_match_internal(
14462 display_map,
14463 replace_newest,
14464 autoscroll,
14465 window,
14466 cx,
14467 )?;
14468 }
14469 }
14470 Ok(())
14471 }
14472
14473 pub fn select_all_matches(
14474 &mut self,
14475 _action: &SelectAllMatches,
14476 window: &mut Window,
14477 cx: &mut Context<Self>,
14478 ) -> Result<()> {
14479 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14480
14481 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14482
14483 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14484 let Some(select_next_state) = self.select_next_state.as_mut() else {
14485 return Ok(());
14486 };
14487 if select_next_state.done {
14488 return Ok(());
14489 }
14490
14491 let mut new_selections = Vec::new();
14492
14493 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14494 let buffer = display_map.buffer_snapshot();
14495 let query_matches = select_next_state
14496 .query
14497 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14498
14499 for query_match in query_matches.into_iter() {
14500 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14501 let offset_range = if reversed {
14502 query_match.end()..query_match.start()
14503 } else {
14504 query_match.start()..query_match.end()
14505 };
14506
14507 if !select_next_state.wordwise
14508 || (!buffer.is_inside_word(offset_range.start, None)
14509 && !buffer.is_inside_word(offset_range.end, None))
14510 {
14511 new_selections.push(offset_range.start..offset_range.end);
14512 }
14513 }
14514
14515 select_next_state.done = true;
14516
14517 if new_selections.is_empty() {
14518 log::error!("bug: new_selections is empty in select_all_matches");
14519 return Ok(());
14520 }
14521
14522 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14523 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14524 selections.select_ranges(new_selections)
14525 });
14526
14527 Ok(())
14528 }
14529
14530 pub fn select_next(
14531 &mut self,
14532 action: &SelectNext,
14533 window: &mut Window,
14534 cx: &mut Context<Self>,
14535 ) -> Result<()> {
14536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14538 self.select_next_match_internal(
14539 &display_map,
14540 action.replace_newest,
14541 Some(Autoscroll::newest()),
14542 window,
14543 cx,
14544 )?;
14545 Ok(())
14546 }
14547
14548 pub fn select_previous(
14549 &mut self,
14550 action: &SelectPrevious,
14551 window: &mut Window,
14552 cx: &mut Context<Self>,
14553 ) -> Result<()> {
14554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14555 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14556 let buffer = display_map.buffer_snapshot();
14557 let mut selections = self.selections.all::<usize>(&display_map);
14558 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14559 let query = &select_prev_state.query;
14560 if !select_prev_state.done {
14561 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14562 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14563 let mut next_selected_range = None;
14564 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14565 let bytes_before_last_selection =
14566 buffer.reversed_bytes_in_range(0..last_selection.start);
14567 let bytes_after_first_selection =
14568 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14569 let query_matches = query
14570 .stream_find_iter(bytes_before_last_selection)
14571 .map(|result| (last_selection.start, result))
14572 .chain(
14573 query
14574 .stream_find_iter(bytes_after_first_selection)
14575 .map(|result| (buffer.len(), result)),
14576 );
14577 for (end_offset, query_match) in query_matches {
14578 let query_match = query_match.unwrap(); // can only fail due to I/O
14579 let offset_range =
14580 end_offset - query_match.end()..end_offset - query_match.start();
14581
14582 if !select_prev_state.wordwise
14583 || (!buffer.is_inside_word(offset_range.start, None)
14584 && !buffer.is_inside_word(offset_range.end, None))
14585 {
14586 next_selected_range = Some(offset_range);
14587 break;
14588 }
14589 }
14590
14591 if let Some(next_selected_range) = next_selected_range {
14592 self.select_match_ranges(
14593 next_selected_range,
14594 last_selection.reversed,
14595 action.replace_newest,
14596 Some(Autoscroll::newest()),
14597 window,
14598 cx,
14599 );
14600 } else {
14601 select_prev_state.done = true;
14602 }
14603 }
14604
14605 self.select_prev_state = Some(select_prev_state);
14606 } else {
14607 let mut only_carets = true;
14608 let mut same_text_selected = true;
14609 let mut selected_text = None;
14610
14611 let mut selections_iter = selections.iter().peekable();
14612 while let Some(selection) = selections_iter.next() {
14613 if selection.start != selection.end {
14614 only_carets = false;
14615 }
14616
14617 if same_text_selected {
14618 if selected_text.is_none() {
14619 selected_text =
14620 Some(buffer.text_for_range(selection.range()).collect::<String>());
14621 }
14622
14623 if let Some(next_selection) = selections_iter.peek() {
14624 if next_selection.range().len() == selection.range().len() {
14625 let next_selected_text = buffer
14626 .text_for_range(next_selection.range())
14627 .collect::<String>();
14628 if Some(next_selected_text) != selected_text {
14629 same_text_selected = false;
14630 selected_text = None;
14631 }
14632 } else {
14633 same_text_selected = false;
14634 selected_text = None;
14635 }
14636 }
14637 }
14638 }
14639
14640 if only_carets {
14641 for selection in &mut selections {
14642 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14643 selection.start = word_range.start;
14644 selection.end = word_range.end;
14645 selection.goal = SelectionGoal::None;
14646 selection.reversed = false;
14647 self.select_match_ranges(
14648 selection.start..selection.end,
14649 selection.reversed,
14650 action.replace_newest,
14651 Some(Autoscroll::newest()),
14652 window,
14653 cx,
14654 );
14655 }
14656 if selections.len() == 1 {
14657 let selection = selections
14658 .last()
14659 .expect("ensured that there's only one selection");
14660 let query = buffer
14661 .text_for_range(selection.start..selection.end)
14662 .collect::<String>();
14663 let is_empty = query.is_empty();
14664 let select_state = SelectNextState {
14665 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14666 wordwise: true,
14667 done: is_empty,
14668 };
14669 self.select_prev_state = Some(select_state);
14670 } else {
14671 self.select_prev_state = None;
14672 }
14673 } else if let Some(selected_text) = selected_text {
14674 self.select_prev_state = Some(SelectNextState {
14675 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14676 wordwise: false,
14677 done: false,
14678 });
14679 self.select_previous(action, window, cx)?;
14680 }
14681 }
14682 Ok(())
14683 }
14684
14685 pub fn find_next_match(
14686 &mut self,
14687 _: &FindNextMatch,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 ) -> Result<()> {
14691 let selections = self.selections.disjoint_anchors_arc();
14692 match selections.first() {
14693 Some(first) if selections.len() >= 2 => {
14694 self.change_selections(Default::default(), window, cx, |s| {
14695 s.select_ranges([first.range()]);
14696 });
14697 }
14698 _ => self.select_next(
14699 &SelectNext {
14700 replace_newest: true,
14701 },
14702 window,
14703 cx,
14704 )?,
14705 }
14706 Ok(())
14707 }
14708
14709 pub fn find_previous_match(
14710 &mut self,
14711 _: &FindPreviousMatch,
14712 window: &mut Window,
14713 cx: &mut Context<Self>,
14714 ) -> Result<()> {
14715 let selections = self.selections.disjoint_anchors_arc();
14716 match selections.last() {
14717 Some(last) if selections.len() >= 2 => {
14718 self.change_selections(Default::default(), window, cx, |s| {
14719 s.select_ranges([last.range()]);
14720 });
14721 }
14722 _ => self.select_previous(
14723 &SelectPrevious {
14724 replace_newest: true,
14725 },
14726 window,
14727 cx,
14728 )?,
14729 }
14730 Ok(())
14731 }
14732
14733 pub fn toggle_comments(
14734 &mut self,
14735 action: &ToggleComments,
14736 window: &mut Window,
14737 cx: &mut Context<Self>,
14738 ) {
14739 if self.read_only(cx) {
14740 return;
14741 }
14742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14743 let text_layout_details = &self.text_layout_details(window);
14744 self.transact(window, cx, |this, window, cx| {
14745 let mut selections = this
14746 .selections
14747 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14748 let mut edits = Vec::new();
14749 let mut selection_edit_ranges = Vec::new();
14750 let mut last_toggled_row = None;
14751 let snapshot = this.buffer.read(cx).read(cx);
14752 let empty_str: Arc<str> = Arc::default();
14753 let mut suffixes_inserted = Vec::new();
14754 let ignore_indent = action.ignore_indent;
14755
14756 fn comment_prefix_range(
14757 snapshot: &MultiBufferSnapshot,
14758 row: MultiBufferRow,
14759 comment_prefix: &str,
14760 comment_prefix_whitespace: &str,
14761 ignore_indent: bool,
14762 ) -> Range<Point> {
14763 let indent_size = if ignore_indent {
14764 0
14765 } else {
14766 snapshot.indent_size_for_line(row).len
14767 };
14768
14769 let start = Point::new(row.0, indent_size);
14770
14771 let mut line_bytes = snapshot
14772 .bytes_in_range(start..snapshot.max_point())
14773 .flatten()
14774 .copied();
14775
14776 // If this line currently begins with the line comment prefix, then record
14777 // the range containing the prefix.
14778 if line_bytes
14779 .by_ref()
14780 .take(comment_prefix.len())
14781 .eq(comment_prefix.bytes())
14782 {
14783 // Include any whitespace that matches the comment prefix.
14784 let matching_whitespace_len = line_bytes
14785 .zip(comment_prefix_whitespace.bytes())
14786 .take_while(|(a, b)| a == b)
14787 .count() as u32;
14788 let end = Point::new(
14789 start.row,
14790 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14791 );
14792 start..end
14793 } else {
14794 start..start
14795 }
14796 }
14797
14798 fn comment_suffix_range(
14799 snapshot: &MultiBufferSnapshot,
14800 row: MultiBufferRow,
14801 comment_suffix: &str,
14802 comment_suffix_has_leading_space: bool,
14803 ) -> Range<Point> {
14804 let end = Point::new(row.0, snapshot.line_len(row));
14805 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14806
14807 let mut line_end_bytes = snapshot
14808 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14809 .flatten()
14810 .copied();
14811
14812 let leading_space_len = if suffix_start_column > 0
14813 && line_end_bytes.next() == Some(b' ')
14814 && comment_suffix_has_leading_space
14815 {
14816 1
14817 } else {
14818 0
14819 };
14820
14821 // If this line currently begins with the line comment prefix, then record
14822 // the range containing the prefix.
14823 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14824 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14825 start..end
14826 } else {
14827 end..end
14828 }
14829 }
14830
14831 // TODO: Handle selections that cross excerpts
14832 for selection in &mut selections {
14833 let start_column = snapshot
14834 .indent_size_for_line(MultiBufferRow(selection.start.row))
14835 .len;
14836 let language = if let Some(language) =
14837 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14838 {
14839 language
14840 } else {
14841 continue;
14842 };
14843
14844 selection_edit_ranges.clear();
14845
14846 // If multiple selections contain a given row, avoid processing that
14847 // row more than once.
14848 let mut start_row = MultiBufferRow(selection.start.row);
14849 if last_toggled_row == Some(start_row) {
14850 start_row = start_row.next_row();
14851 }
14852 let end_row =
14853 if selection.end.row > selection.start.row && selection.end.column == 0 {
14854 MultiBufferRow(selection.end.row - 1)
14855 } else {
14856 MultiBufferRow(selection.end.row)
14857 };
14858 last_toggled_row = Some(end_row);
14859
14860 if start_row > end_row {
14861 continue;
14862 }
14863
14864 // If the language has line comments, toggle those.
14865 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14866
14867 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14868 if ignore_indent {
14869 full_comment_prefixes = full_comment_prefixes
14870 .into_iter()
14871 .map(|s| Arc::from(s.trim_end()))
14872 .collect();
14873 }
14874
14875 if !full_comment_prefixes.is_empty() {
14876 let first_prefix = full_comment_prefixes
14877 .first()
14878 .expect("prefixes is non-empty");
14879 let prefix_trimmed_lengths = full_comment_prefixes
14880 .iter()
14881 .map(|p| p.trim_end_matches(' ').len())
14882 .collect::<SmallVec<[usize; 4]>>();
14883
14884 let mut all_selection_lines_are_comments = true;
14885
14886 for row in start_row.0..=end_row.0 {
14887 let row = MultiBufferRow(row);
14888 if start_row < end_row && snapshot.is_line_blank(row) {
14889 continue;
14890 }
14891
14892 let prefix_range = full_comment_prefixes
14893 .iter()
14894 .zip(prefix_trimmed_lengths.iter().copied())
14895 .map(|(prefix, trimmed_prefix_len)| {
14896 comment_prefix_range(
14897 snapshot.deref(),
14898 row,
14899 &prefix[..trimmed_prefix_len],
14900 &prefix[trimmed_prefix_len..],
14901 ignore_indent,
14902 )
14903 })
14904 .max_by_key(|range| range.end.column - range.start.column)
14905 .expect("prefixes is non-empty");
14906
14907 if prefix_range.is_empty() {
14908 all_selection_lines_are_comments = false;
14909 }
14910
14911 selection_edit_ranges.push(prefix_range);
14912 }
14913
14914 if all_selection_lines_are_comments {
14915 edits.extend(
14916 selection_edit_ranges
14917 .iter()
14918 .cloned()
14919 .map(|range| (range, empty_str.clone())),
14920 );
14921 } else {
14922 let min_column = selection_edit_ranges
14923 .iter()
14924 .map(|range| range.start.column)
14925 .min()
14926 .unwrap_or(0);
14927 edits.extend(selection_edit_ranges.iter().map(|range| {
14928 let position = Point::new(range.start.row, min_column);
14929 (position..position, first_prefix.clone())
14930 }));
14931 }
14932 } else if let Some(BlockCommentConfig {
14933 start: full_comment_prefix,
14934 end: comment_suffix,
14935 ..
14936 }) = language.block_comment()
14937 {
14938 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14939 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14940 let prefix_range = comment_prefix_range(
14941 snapshot.deref(),
14942 start_row,
14943 comment_prefix,
14944 comment_prefix_whitespace,
14945 ignore_indent,
14946 );
14947 let suffix_range = comment_suffix_range(
14948 snapshot.deref(),
14949 end_row,
14950 comment_suffix.trim_start_matches(' '),
14951 comment_suffix.starts_with(' '),
14952 );
14953
14954 if prefix_range.is_empty() || suffix_range.is_empty() {
14955 edits.push((
14956 prefix_range.start..prefix_range.start,
14957 full_comment_prefix.clone(),
14958 ));
14959 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14960 suffixes_inserted.push((end_row, comment_suffix.len()));
14961 } else {
14962 edits.push((prefix_range, empty_str.clone()));
14963 edits.push((suffix_range, empty_str.clone()));
14964 }
14965 } else {
14966 continue;
14967 }
14968 }
14969
14970 drop(snapshot);
14971 this.buffer.update(cx, |buffer, cx| {
14972 buffer.edit(edits, None, cx);
14973 });
14974
14975 // Adjust selections so that they end before any comment suffixes that
14976 // were inserted.
14977 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14978 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
14979 let snapshot = this.buffer.read(cx).read(cx);
14980 for selection in &mut selections {
14981 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14982 match row.cmp(&MultiBufferRow(selection.end.row)) {
14983 Ordering::Less => {
14984 suffixes_inserted.next();
14985 continue;
14986 }
14987 Ordering::Greater => break,
14988 Ordering::Equal => {
14989 if selection.end.column == snapshot.line_len(row) {
14990 if selection.is_empty() {
14991 selection.start.column -= suffix_len as u32;
14992 }
14993 selection.end.column -= suffix_len as u32;
14994 }
14995 break;
14996 }
14997 }
14998 }
14999 }
15000
15001 drop(snapshot);
15002 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15003
15004 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15005 let selections_on_single_row = selections.windows(2).all(|selections| {
15006 selections[0].start.row == selections[1].start.row
15007 && selections[0].end.row == selections[1].end.row
15008 && selections[0].start.row == selections[0].end.row
15009 });
15010 let selections_selecting = selections
15011 .iter()
15012 .any(|selection| selection.start != selection.end);
15013 let advance_downwards = action.advance_downwards
15014 && selections_on_single_row
15015 && !selections_selecting
15016 && !matches!(this.mode, EditorMode::SingleLine);
15017
15018 if advance_downwards {
15019 let snapshot = this.buffer.read(cx).snapshot(cx);
15020
15021 this.change_selections(Default::default(), window, cx, |s| {
15022 s.move_cursors_with(|display_snapshot, display_point, _| {
15023 let mut point = display_point.to_point(display_snapshot);
15024 point.row += 1;
15025 point = snapshot.clip_point(point, Bias::Left);
15026 let display_point = point.to_display_point(display_snapshot);
15027 let goal = SelectionGoal::HorizontalPosition(
15028 display_snapshot
15029 .x_for_display_point(display_point, text_layout_details)
15030 .into(),
15031 );
15032 (display_point, goal)
15033 })
15034 });
15035 }
15036 });
15037 }
15038
15039 pub fn select_enclosing_symbol(
15040 &mut self,
15041 _: &SelectEnclosingSymbol,
15042 window: &mut Window,
15043 cx: &mut Context<Self>,
15044 ) {
15045 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15046
15047 let buffer = self.buffer.read(cx).snapshot(cx);
15048 let old_selections = self
15049 .selections
15050 .all::<usize>(&self.display_snapshot(cx))
15051 .into_boxed_slice();
15052
15053 fn update_selection(
15054 selection: &Selection<usize>,
15055 buffer_snap: &MultiBufferSnapshot,
15056 ) -> Option<Selection<usize>> {
15057 let cursor = selection.head();
15058 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15059 for symbol in symbols.iter().rev() {
15060 let start = symbol.range.start.to_offset(buffer_snap);
15061 let end = symbol.range.end.to_offset(buffer_snap);
15062 let new_range = start..end;
15063 if start < selection.start || end > selection.end {
15064 return Some(Selection {
15065 id: selection.id,
15066 start: new_range.start,
15067 end: new_range.end,
15068 goal: SelectionGoal::None,
15069 reversed: selection.reversed,
15070 });
15071 }
15072 }
15073 None
15074 }
15075
15076 let mut selected_larger_symbol = false;
15077 let new_selections = old_selections
15078 .iter()
15079 .map(|selection| match update_selection(selection, &buffer) {
15080 Some(new_selection) => {
15081 if new_selection.range() != selection.range() {
15082 selected_larger_symbol = true;
15083 }
15084 new_selection
15085 }
15086 None => selection.clone(),
15087 })
15088 .collect::<Vec<_>>();
15089
15090 if selected_larger_symbol {
15091 self.change_selections(Default::default(), window, cx, |s| {
15092 s.select(new_selections);
15093 });
15094 }
15095 }
15096
15097 pub fn select_larger_syntax_node(
15098 &mut self,
15099 _: &SelectLargerSyntaxNode,
15100 window: &mut Window,
15101 cx: &mut Context<Self>,
15102 ) {
15103 let Some(visible_row_count) = self.visible_row_count() else {
15104 return;
15105 };
15106 let old_selections: Box<[_]> = self
15107 .selections
15108 .all::<usize>(&self.display_snapshot(cx))
15109 .into();
15110 if old_selections.is_empty() {
15111 return;
15112 }
15113
15114 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15115
15116 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15117 let buffer = self.buffer.read(cx).snapshot(cx);
15118
15119 let mut selected_larger_node = false;
15120 let mut new_selections = old_selections
15121 .iter()
15122 .map(|selection| {
15123 let old_range = selection.start..selection.end;
15124
15125 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15126 // manually select word at selection
15127 if ["string_content", "inline"].contains(&node.kind()) {
15128 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15129 // ignore if word is already selected
15130 if !word_range.is_empty() && old_range != word_range {
15131 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15132 // only select word if start and end point belongs to same word
15133 if word_range == last_word_range {
15134 selected_larger_node = true;
15135 return Selection {
15136 id: selection.id,
15137 start: word_range.start,
15138 end: word_range.end,
15139 goal: SelectionGoal::None,
15140 reversed: selection.reversed,
15141 };
15142 }
15143 }
15144 }
15145 }
15146
15147 let mut new_range = old_range.clone();
15148 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15149 new_range = range;
15150 if !node.is_named() {
15151 continue;
15152 }
15153 if !display_map.intersects_fold(new_range.start)
15154 && !display_map.intersects_fold(new_range.end)
15155 {
15156 break;
15157 }
15158 }
15159
15160 selected_larger_node |= new_range != old_range;
15161 Selection {
15162 id: selection.id,
15163 start: new_range.start,
15164 end: new_range.end,
15165 goal: SelectionGoal::None,
15166 reversed: selection.reversed,
15167 }
15168 })
15169 .collect::<Vec<_>>();
15170
15171 if !selected_larger_node {
15172 return; // don't put this call in the history
15173 }
15174
15175 // scroll based on transformation done to the last selection created by the user
15176 let (last_old, last_new) = old_selections
15177 .last()
15178 .zip(new_selections.last().cloned())
15179 .expect("old_selections isn't empty");
15180
15181 // revert selection
15182 let is_selection_reversed = {
15183 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15184 new_selections.last_mut().expect("checked above").reversed =
15185 should_newest_selection_be_reversed;
15186 should_newest_selection_be_reversed
15187 };
15188
15189 if selected_larger_node {
15190 self.select_syntax_node_history.disable_clearing = true;
15191 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15192 s.select(new_selections.clone());
15193 });
15194 self.select_syntax_node_history.disable_clearing = false;
15195 }
15196
15197 let start_row = last_new.start.to_display_point(&display_map).row().0;
15198 let end_row = last_new.end.to_display_point(&display_map).row().0;
15199 let selection_height = end_row - start_row + 1;
15200 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15201
15202 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15203 let scroll_behavior = if fits_on_the_screen {
15204 self.request_autoscroll(Autoscroll::fit(), cx);
15205 SelectSyntaxNodeScrollBehavior::FitSelection
15206 } else if is_selection_reversed {
15207 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15208 SelectSyntaxNodeScrollBehavior::CursorTop
15209 } else {
15210 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15211 SelectSyntaxNodeScrollBehavior::CursorBottom
15212 };
15213
15214 self.select_syntax_node_history.push((
15215 old_selections,
15216 scroll_behavior,
15217 is_selection_reversed,
15218 ));
15219 }
15220
15221 pub fn select_smaller_syntax_node(
15222 &mut self,
15223 _: &SelectSmallerSyntaxNode,
15224 window: &mut Window,
15225 cx: &mut Context<Self>,
15226 ) {
15227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15228
15229 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15230 self.select_syntax_node_history.pop()
15231 {
15232 if let Some(selection) = selections.last_mut() {
15233 selection.reversed = is_selection_reversed;
15234 }
15235
15236 self.select_syntax_node_history.disable_clearing = true;
15237 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15238 s.select(selections.to_vec());
15239 });
15240 self.select_syntax_node_history.disable_clearing = false;
15241
15242 match scroll_behavior {
15243 SelectSyntaxNodeScrollBehavior::CursorTop => {
15244 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15245 }
15246 SelectSyntaxNodeScrollBehavior::FitSelection => {
15247 self.request_autoscroll(Autoscroll::fit(), cx);
15248 }
15249 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15250 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15251 }
15252 }
15253 }
15254 }
15255
15256 pub fn unwrap_syntax_node(
15257 &mut self,
15258 _: &UnwrapSyntaxNode,
15259 window: &mut Window,
15260 cx: &mut Context<Self>,
15261 ) {
15262 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15263
15264 let buffer = self.buffer.read(cx).snapshot(cx);
15265 let selections = self
15266 .selections
15267 .all::<usize>(&self.display_snapshot(cx))
15268 .into_iter()
15269 // subtracting the offset requires sorting
15270 .sorted_by_key(|i| i.start);
15271
15272 let full_edits = selections
15273 .into_iter()
15274 .filter_map(|selection| {
15275 let child = if selection.is_empty()
15276 && let Some((_, ancestor_range)) =
15277 buffer.syntax_ancestor(selection.start..selection.end)
15278 {
15279 ancestor_range
15280 } else {
15281 selection.range()
15282 };
15283
15284 let mut parent = child.clone();
15285 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15286 parent = ancestor_range;
15287 if parent.start < child.start || parent.end > child.end {
15288 break;
15289 }
15290 }
15291
15292 if parent == child {
15293 return None;
15294 }
15295 let text = buffer.text_for_range(child).collect::<String>();
15296 Some((selection.id, parent, text))
15297 })
15298 .collect::<Vec<_>>();
15299 if full_edits.is_empty() {
15300 return;
15301 }
15302
15303 self.transact(window, cx, |this, window, cx| {
15304 this.buffer.update(cx, |buffer, cx| {
15305 buffer.edit(
15306 full_edits
15307 .iter()
15308 .map(|(_, p, t)| (p.clone(), t.clone()))
15309 .collect::<Vec<_>>(),
15310 None,
15311 cx,
15312 );
15313 });
15314 this.change_selections(Default::default(), window, cx, |s| {
15315 let mut offset = 0;
15316 let mut selections = vec![];
15317 for (id, parent, text) in full_edits {
15318 let start = parent.start - offset;
15319 offset += parent.len() - text.len();
15320 selections.push(Selection {
15321 id,
15322 start,
15323 end: start + text.len(),
15324 reversed: false,
15325 goal: Default::default(),
15326 });
15327 }
15328 s.select(selections);
15329 });
15330 });
15331 }
15332
15333 pub fn select_next_syntax_node(
15334 &mut self,
15335 _: &SelectNextSyntaxNode,
15336 window: &mut Window,
15337 cx: &mut Context<Self>,
15338 ) {
15339 let old_selections: Box<[_]> = self
15340 .selections
15341 .all::<usize>(&self.display_snapshot(cx))
15342 .into();
15343 if old_selections.is_empty() {
15344 return;
15345 }
15346
15347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15348
15349 let buffer = self.buffer.read(cx).snapshot(cx);
15350 let mut selected_sibling = false;
15351
15352 let new_selections = old_selections
15353 .iter()
15354 .map(|selection| {
15355 let old_range = selection.start..selection.end;
15356
15357 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15358 let new_range = node.byte_range();
15359 selected_sibling = true;
15360 Selection {
15361 id: selection.id,
15362 start: new_range.start,
15363 end: new_range.end,
15364 goal: SelectionGoal::None,
15365 reversed: selection.reversed,
15366 }
15367 } else {
15368 selection.clone()
15369 }
15370 })
15371 .collect::<Vec<_>>();
15372
15373 if selected_sibling {
15374 self.change_selections(
15375 SelectionEffects::scroll(Autoscroll::fit()),
15376 window,
15377 cx,
15378 |s| {
15379 s.select(new_selections);
15380 },
15381 );
15382 }
15383 }
15384
15385 pub fn select_prev_syntax_node(
15386 &mut self,
15387 _: &SelectPreviousSyntaxNode,
15388 window: &mut Window,
15389 cx: &mut Context<Self>,
15390 ) {
15391 let old_selections: Box<[_]> = self
15392 .selections
15393 .all::<usize>(&self.display_snapshot(cx))
15394 .into();
15395 if old_selections.is_empty() {
15396 return;
15397 }
15398
15399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15400
15401 let buffer = self.buffer.read(cx).snapshot(cx);
15402 let mut selected_sibling = false;
15403
15404 let new_selections = old_selections
15405 .iter()
15406 .map(|selection| {
15407 let old_range = selection.start..selection.end;
15408
15409 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15410 let new_range = node.byte_range();
15411 selected_sibling = true;
15412 Selection {
15413 id: selection.id,
15414 start: new_range.start,
15415 end: new_range.end,
15416 goal: SelectionGoal::None,
15417 reversed: selection.reversed,
15418 }
15419 } else {
15420 selection.clone()
15421 }
15422 })
15423 .collect::<Vec<_>>();
15424
15425 if selected_sibling {
15426 self.change_selections(
15427 SelectionEffects::scroll(Autoscroll::fit()),
15428 window,
15429 cx,
15430 |s| {
15431 s.select(new_selections);
15432 },
15433 );
15434 }
15435 }
15436
15437 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15438 if !EditorSettings::get_global(cx).gutter.runnables {
15439 self.clear_tasks();
15440 return Task::ready(());
15441 }
15442 let project = self.project().map(Entity::downgrade);
15443 let task_sources = self.lsp_task_sources(cx);
15444 let multi_buffer = self.buffer.downgrade();
15445 cx.spawn_in(window, async move |editor, cx| {
15446 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15447 let Some(project) = project.and_then(|p| p.upgrade()) else {
15448 return;
15449 };
15450 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15451 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15452 }) else {
15453 return;
15454 };
15455
15456 let hide_runnables = project
15457 .update(cx, |project, _| project.is_via_collab())
15458 .unwrap_or(true);
15459 if hide_runnables {
15460 return;
15461 }
15462 let new_rows =
15463 cx.background_spawn({
15464 let snapshot = display_snapshot.clone();
15465 async move {
15466 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15467 }
15468 })
15469 .await;
15470 let Ok(lsp_tasks) =
15471 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15472 else {
15473 return;
15474 };
15475 let lsp_tasks = lsp_tasks.await;
15476
15477 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15478 lsp_tasks
15479 .into_iter()
15480 .flat_map(|(kind, tasks)| {
15481 tasks.into_iter().filter_map(move |(location, task)| {
15482 Some((kind.clone(), location?, task))
15483 })
15484 })
15485 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15486 let buffer = location.target.buffer;
15487 let buffer_snapshot = buffer.read(cx).snapshot();
15488 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15489 |(excerpt_id, snapshot, _)| {
15490 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15491 display_snapshot
15492 .buffer_snapshot()
15493 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15494 } else {
15495 None
15496 }
15497 },
15498 );
15499 if let Some(offset) = offset {
15500 let task_buffer_range =
15501 location.target.range.to_point(&buffer_snapshot);
15502 let context_buffer_range =
15503 task_buffer_range.to_offset(&buffer_snapshot);
15504 let context_range = BufferOffset(context_buffer_range.start)
15505 ..BufferOffset(context_buffer_range.end);
15506
15507 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15508 .or_insert_with(|| RunnableTasks {
15509 templates: Vec::new(),
15510 offset,
15511 column: task_buffer_range.start.column,
15512 extra_variables: HashMap::default(),
15513 context_range,
15514 })
15515 .templates
15516 .push((kind, task.original_task().clone()));
15517 }
15518
15519 acc
15520 })
15521 }) else {
15522 return;
15523 };
15524
15525 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15526 buffer.language_settings(cx).tasks.prefer_lsp
15527 }) else {
15528 return;
15529 };
15530
15531 let rows = Self::runnable_rows(
15532 project,
15533 display_snapshot,
15534 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15535 new_rows,
15536 cx.clone(),
15537 )
15538 .await;
15539 editor
15540 .update(cx, |editor, _| {
15541 editor.clear_tasks();
15542 for (key, mut value) in rows {
15543 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15544 value.templates.extend(lsp_tasks.templates);
15545 }
15546
15547 editor.insert_tasks(key, value);
15548 }
15549 for (key, value) in lsp_tasks_by_rows {
15550 editor.insert_tasks(key, value);
15551 }
15552 })
15553 .ok();
15554 })
15555 }
15556 fn fetch_runnable_ranges(
15557 snapshot: &DisplaySnapshot,
15558 range: Range<Anchor>,
15559 ) -> Vec<language::RunnableRange> {
15560 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15561 }
15562
15563 fn runnable_rows(
15564 project: Entity<Project>,
15565 snapshot: DisplaySnapshot,
15566 prefer_lsp: bool,
15567 runnable_ranges: Vec<RunnableRange>,
15568 cx: AsyncWindowContext,
15569 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15570 cx.spawn(async move |cx| {
15571 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15572 for mut runnable in runnable_ranges {
15573 let Some(tasks) = cx
15574 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15575 .ok()
15576 else {
15577 continue;
15578 };
15579 let mut tasks = tasks.await;
15580
15581 if prefer_lsp {
15582 tasks.retain(|(task_kind, _)| {
15583 !matches!(task_kind, TaskSourceKind::Language { .. })
15584 });
15585 }
15586 if tasks.is_empty() {
15587 continue;
15588 }
15589
15590 let point = runnable
15591 .run_range
15592 .start
15593 .to_point(&snapshot.buffer_snapshot());
15594 let Some(row) = snapshot
15595 .buffer_snapshot()
15596 .buffer_line_for_row(MultiBufferRow(point.row))
15597 .map(|(_, range)| range.start.row)
15598 else {
15599 continue;
15600 };
15601
15602 let context_range =
15603 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15604 runnable_rows.push((
15605 (runnable.buffer_id, row),
15606 RunnableTasks {
15607 templates: tasks,
15608 offset: snapshot
15609 .buffer_snapshot()
15610 .anchor_before(runnable.run_range.start),
15611 context_range,
15612 column: point.column,
15613 extra_variables: runnable.extra_captures,
15614 },
15615 ));
15616 }
15617 runnable_rows
15618 })
15619 }
15620
15621 fn templates_with_tags(
15622 project: &Entity<Project>,
15623 runnable: &mut Runnable,
15624 cx: &mut App,
15625 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15626 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15627 let (worktree_id, file) = project
15628 .buffer_for_id(runnable.buffer, cx)
15629 .and_then(|buffer| buffer.read(cx).file())
15630 .map(|file| (file.worktree_id(cx), file.clone()))
15631 .unzip();
15632
15633 (
15634 project.task_store().read(cx).task_inventory().cloned(),
15635 worktree_id,
15636 file,
15637 )
15638 });
15639
15640 let tags = mem::take(&mut runnable.tags);
15641 let language = runnable.language.clone();
15642 cx.spawn(async move |cx| {
15643 let mut templates_with_tags = Vec::new();
15644 if let Some(inventory) = inventory {
15645 for RunnableTag(tag) in tags {
15646 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15647 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15648 }) else {
15649 return templates_with_tags;
15650 };
15651 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15652 move |(_, template)| {
15653 template.tags.iter().any(|source_tag| source_tag == &tag)
15654 },
15655 ));
15656 }
15657 }
15658 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15659
15660 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15661 // Strongest source wins; if we have worktree tag binding, prefer that to
15662 // global and language bindings;
15663 // if we have a global binding, prefer that to language binding.
15664 let first_mismatch = templates_with_tags
15665 .iter()
15666 .position(|(tag_source, _)| tag_source != leading_tag_source);
15667 if let Some(index) = first_mismatch {
15668 templates_with_tags.truncate(index);
15669 }
15670 }
15671
15672 templates_with_tags
15673 })
15674 }
15675
15676 pub fn move_to_enclosing_bracket(
15677 &mut self,
15678 _: &MoveToEnclosingBracket,
15679 window: &mut Window,
15680 cx: &mut Context<Self>,
15681 ) {
15682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15683 self.change_selections(Default::default(), window, cx, |s| {
15684 s.move_offsets_with(|snapshot, selection| {
15685 let Some(enclosing_bracket_ranges) =
15686 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15687 else {
15688 return;
15689 };
15690
15691 let mut best_length = usize::MAX;
15692 let mut best_inside = false;
15693 let mut best_in_bracket_range = false;
15694 let mut best_destination = None;
15695 for (open, close) in enclosing_bracket_ranges {
15696 let close = close.to_inclusive();
15697 let length = close.end() - open.start;
15698 let inside = selection.start >= open.end && selection.end <= *close.start();
15699 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15700 || close.contains(&selection.head());
15701
15702 // If best is next to a bracket and current isn't, skip
15703 if !in_bracket_range && best_in_bracket_range {
15704 continue;
15705 }
15706
15707 // Prefer smaller lengths unless best is inside and current isn't
15708 if length > best_length && (best_inside || !inside) {
15709 continue;
15710 }
15711
15712 best_length = length;
15713 best_inside = inside;
15714 best_in_bracket_range = in_bracket_range;
15715 best_destination = Some(
15716 if close.contains(&selection.start) && close.contains(&selection.end) {
15717 if inside { open.end } else { open.start }
15718 } else if inside {
15719 *close.start()
15720 } else {
15721 *close.end()
15722 },
15723 );
15724 }
15725
15726 if let Some(destination) = best_destination {
15727 selection.collapse_to(destination, SelectionGoal::None);
15728 }
15729 })
15730 });
15731 }
15732
15733 pub fn undo_selection(
15734 &mut self,
15735 _: &UndoSelection,
15736 window: &mut Window,
15737 cx: &mut Context<Self>,
15738 ) {
15739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15740 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15741 self.selection_history.mode = SelectionHistoryMode::Undoing;
15742 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15743 this.end_selection(window, cx);
15744 this.change_selections(
15745 SelectionEffects::scroll(Autoscroll::newest()),
15746 window,
15747 cx,
15748 |s| s.select_anchors(entry.selections.to_vec()),
15749 );
15750 });
15751 self.selection_history.mode = SelectionHistoryMode::Normal;
15752
15753 self.select_next_state = entry.select_next_state;
15754 self.select_prev_state = entry.select_prev_state;
15755 self.add_selections_state = entry.add_selections_state;
15756 }
15757 }
15758
15759 pub fn redo_selection(
15760 &mut self,
15761 _: &RedoSelection,
15762 window: &mut Window,
15763 cx: &mut Context<Self>,
15764 ) {
15765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15766 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15767 self.selection_history.mode = SelectionHistoryMode::Redoing;
15768 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15769 this.end_selection(window, cx);
15770 this.change_selections(
15771 SelectionEffects::scroll(Autoscroll::newest()),
15772 window,
15773 cx,
15774 |s| s.select_anchors(entry.selections.to_vec()),
15775 );
15776 });
15777 self.selection_history.mode = SelectionHistoryMode::Normal;
15778
15779 self.select_next_state = entry.select_next_state;
15780 self.select_prev_state = entry.select_prev_state;
15781 self.add_selections_state = entry.add_selections_state;
15782 }
15783 }
15784
15785 pub fn expand_excerpts(
15786 &mut self,
15787 action: &ExpandExcerpts,
15788 _: &mut Window,
15789 cx: &mut Context<Self>,
15790 ) {
15791 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15792 }
15793
15794 pub fn expand_excerpts_down(
15795 &mut self,
15796 action: &ExpandExcerptsDown,
15797 _: &mut Window,
15798 cx: &mut Context<Self>,
15799 ) {
15800 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15801 }
15802
15803 pub fn expand_excerpts_up(
15804 &mut self,
15805 action: &ExpandExcerptsUp,
15806 _: &mut Window,
15807 cx: &mut Context<Self>,
15808 ) {
15809 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15810 }
15811
15812 pub fn expand_excerpts_for_direction(
15813 &mut self,
15814 lines: u32,
15815 direction: ExpandExcerptDirection,
15816
15817 cx: &mut Context<Self>,
15818 ) {
15819 let selections = self.selections.disjoint_anchors_arc();
15820
15821 let lines = if lines == 0 {
15822 EditorSettings::get_global(cx).expand_excerpt_lines
15823 } else {
15824 lines
15825 };
15826
15827 self.buffer.update(cx, |buffer, cx| {
15828 let snapshot = buffer.snapshot(cx);
15829 let mut excerpt_ids = selections
15830 .iter()
15831 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15832 .collect::<Vec<_>>();
15833 excerpt_ids.sort();
15834 excerpt_ids.dedup();
15835 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15836 })
15837 }
15838
15839 pub fn expand_excerpt(
15840 &mut self,
15841 excerpt: ExcerptId,
15842 direction: ExpandExcerptDirection,
15843 window: &mut Window,
15844 cx: &mut Context<Self>,
15845 ) {
15846 let current_scroll_position = self.scroll_position(cx);
15847 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15848 let mut scroll = None;
15849
15850 if direction == ExpandExcerptDirection::Down {
15851 let multi_buffer = self.buffer.read(cx);
15852 let snapshot = multi_buffer.snapshot(cx);
15853 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15854 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15855 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15856 {
15857 let buffer_snapshot = buffer.read(cx).snapshot();
15858 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15859 let last_row = buffer_snapshot.max_point().row;
15860 let lines_below = last_row.saturating_sub(excerpt_end_row);
15861 if lines_below >= lines_to_expand {
15862 scroll = Some(
15863 current_scroll_position
15864 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
15865 );
15866 }
15867 }
15868 }
15869 if direction == ExpandExcerptDirection::Up
15870 && self
15871 .buffer
15872 .read(cx)
15873 .snapshot(cx)
15874 .excerpt_before(excerpt)
15875 .is_none()
15876 {
15877 scroll = Some(current_scroll_position);
15878 }
15879
15880 self.buffer.update(cx, |buffer, cx| {
15881 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15882 });
15883
15884 if let Some(new_scroll_position) = scroll {
15885 self.set_scroll_position(new_scroll_position, window, cx);
15886 }
15887 }
15888
15889 pub fn go_to_singleton_buffer_point(
15890 &mut self,
15891 point: Point,
15892 window: &mut Window,
15893 cx: &mut Context<Self>,
15894 ) {
15895 self.go_to_singleton_buffer_range(point..point, window, cx);
15896 }
15897
15898 pub fn go_to_singleton_buffer_range(
15899 &mut self,
15900 range: Range<Point>,
15901 window: &mut Window,
15902 cx: &mut Context<Self>,
15903 ) {
15904 let multibuffer = self.buffer().read(cx);
15905 let Some(buffer) = multibuffer.as_singleton() else {
15906 return;
15907 };
15908 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15909 return;
15910 };
15911 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15912 return;
15913 };
15914 self.change_selections(
15915 SelectionEffects::default().nav_history(true),
15916 window,
15917 cx,
15918 |s| s.select_anchor_ranges([start..end]),
15919 );
15920 }
15921
15922 pub fn go_to_diagnostic(
15923 &mut self,
15924 action: &GoToDiagnostic,
15925 window: &mut Window,
15926 cx: &mut Context<Self>,
15927 ) {
15928 if !self.diagnostics_enabled() {
15929 return;
15930 }
15931 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15932 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15933 }
15934
15935 pub fn go_to_prev_diagnostic(
15936 &mut self,
15937 action: &GoToPreviousDiagnostic,
15938 window: &mut Window,
15939 cx: &mut Context<Self>,
15940 ) {
15941 if !self.diagnostics_enabled() {
15942 return;
15943 }
15944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15945 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15946 }
15947
15948 pub fn go_to_diagnostic_impl(
15949 &mut self,
15950 direction: Direction,
15951 severity: GoToDiagnosticSeverityFilter,
15952 window: &mut Window,
15953 cx: &mut Context<Self>,
15954 ) {
15955 let buffer = self.buffer.read(cx).snapshot(cx);
15956 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15957
15958 let mut active_group_id = None;
15959 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15960 && active_group.active_range.start.to_offset(&buffer) == selection.start
15961 {
15962 active_group_id = Some(active_group.group_id);
15963 }
15964
15965 fn filtered<'a>(
15966 snapshot: EditorSnapshot,
15967 severity: GoToDiagnosticSeverityFilter,
15968 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15969 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15970 diagnostics
15971 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15972 .filter(|entry| entry.range.start != entry.range.end)
15973 .filter(|entry| !entry.diagnostic.is_unnecessary)
15974 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15975 }
15976
15977 let snapshot = self.snapshot(window, cx);
15978 let before = filtered(
15979 snapshot.clone(),
15980 severity,
15981 buffer
15982 .diagnostics_in_range(0..selection.start)
15983 .filter(|entry| entry.range.start <= selection.start),
15984 );
15985 let after = filtered(
15986 snapshot,
15987 severity,
15988 buffer
15989 .diagnostics_in_range(selection.start..buffer.len())
15990 .filter(|entry| entry.range.start >= selection.start),
15991 );
15992
15993 let mut found: Option<DiagnosticEntryRef<usize>> = None;
15994 if direction == Direction::Prev {
15995 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15996 {
15997 for diagnostic in prev_diagnostics.into_iter().rev() {
15998 if diagnostic.range.start != selection.start
15999 || active_group_id
16000 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16001 {
16002 found = Some(diagnostic);
16003 break 'outer;
16004 }
16005 }
16006 }
16007 } else {
16008 for diagnostic in after.chain(before) {
16009 if diagnostic.range.start != selection.start
16010 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16011 {
16012 found = Some(diagnostic);
16013 break;
16014 }
16015 }
16016 }
16017 let Some(next_diagnostic) = found else {
16018 return;
16019 };
16020
16021 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16022 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16023 return;
16024 };
16025 self.change_selections(Default::default(), window, cx, |s| {
16026 s.select_ranges(vec![
16027 next_diagnostic.range.start..next_diagnostic.range.start,
16028 ])
16029 });
16030 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16031 self.refresh_edit_prediction(false, true, window, cx);
16032 }
16033
16034 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16035 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16036 let snapshot = self.snapshot(window, cx);
16037 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16038 self.go_to_hunk_before_or_after_position(
16039 &snapshot,
16040 selection.head(),
16041 Direction::Next,
16042 window,
16043 cx,
16044 );
16045 }
16046
16047 pub fn go_to_hunk_before_or_after_position(
16048 &mut self,
16049 snapshot: &EditorSnapshot,
16050 position: Point,
16051 direction: Direction,
16052 window: &mut Window,
16053 cx: &mut Context<Editor>,
16054 ) {
16055 let row = if direction == Direction::Next {
16056 self.hunk_after_position(snapshot, position)
16057 .map(|hunk| hunk.row_range.start)
16058 } else {
16059 self.hunk_before_position(snapshot, position)
16060 };
16061
16062 if let Some(row) = row {
16063 let destination = Point::new(row.0, 0);
16064 let autoscroll = Autoscroll::center();
16065
16066 self.unfold_ranges(&[destination..destination], false, false, cx);
16067 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16068 s.select_ranges([destination..destination]);
16069 });
16070 }
16071 }
16072
16073 fn hunk_after_position(
16074 &mut self,
16075 snapshot: &EditorSnapshot,
16076 position: Point,
16077 ) -> Option<MultiBufferDiffHunk> {
16078 snapshot
16079 .buffer_snapshot()
16080 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16081 .find(|hunk| hunk.row_range.start.0 > position.row)
16082 .or_else(|| {
16083 snapshot
16084 .buffer_snapshot()
16085 .diff_hunks_in_range(Point::zero()..position)
16086 .find(|hunk| hunk.row_range.end.0 < position.row)
16087 })
16088 }
16089
16090 fn go_to_prev_hunk(
16091 &mut self,
16092 _: &GoToPreviousHunk,
16093 window: &mut Window,
16094 cx: &mut Context<Self>,
16095 ) {
16096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16097 let snapshot = self.snapshot(window, cx);
16098 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16099 self.go_to_hunk_before_or_after_position(
16100 &snapshot,
16101 selection.head(),
16102 Direction::Prev,
16103 window,
16104 cx,
16105 );
16106 }
16107
16108 fn hunk_before_position(
16109 &mut self,
16110 snapshot: &EditorSnapshot,
16111 position: Point,
16112 ) -> Option<MultiBufferRow> {
16113 snapshot
16114 .buffer_snapshot()
16115 .diff_hunk_before(position)
16116 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16117 }
16118
16119 fn go_to_next_change(
16120 &mut self,
16121 _: &GoToNextChange,
16122 window: &mut Window,
16123 cx: &mut Context<Self>,
16124 ) {
16125 if let Some(selections) = self
16126 .change_list
16127 .next_change(1, Direction::Next)
16128 .map(|s| s.to_vec())
16129 {
16130 self.change_selections(Default::default(), window, cx, |s| {
16131 let map = s.display_map();
16132 s.select_display_ranges(selections.iter().map(|a| {
16133 let point = a.to_display_point(&map);
16134 point..point
16135 }))
16136 })
16137 }
16138 }
16139
16140 fn go_to_previous_change(
16141 &mut self,
16142 _: &GoToPreviousChange,
16143 window: &mut Window,
16144 cx: &mut Context<Self>,
16145 ) {
16146 if let Some(selections) = self
16147 .change_list
16148 .next_change(1, Direction::Prev)
16149 .map(|s| s.to_vec())
16150 {
16151 self.change_selections(Default::default(), window, cx, |s| {
16152 let map = s.display_map();
16153 s.select_display_ranges(selections.iter().map(|a| {
16154 let point = a.to_display_point(&map);
16155 point..point
16156 }))
16157 })
16158 }
16159 }
16160
16161 pub fn go_to_next_document_highlight(
16162 &mut self,
16163 _: &GoToNextDocumentHighlight,
16164 window: &mut Window,
16165 cx: &mut Context<Self>,
16166 ) {
16167 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16168 }
16169
16170 pub fn go_to_prev_document_highlight(
16171 &mut self,
16172 _: &GoToPreviousDocumentHighlight,
16173 window: &mut Window,
16174 cx: &mut Context<Self>,
16175 ) {
16176 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16177 }
16178
16179 pub fn go_to_document_highlight_before_or_after_position(
16180 &mut self,
16181 direction: Direction,
16182 window: &mut Window,
16183 cx: &mut Context<Editor>,
16184 ) {
16185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16186 let snapshot = self.snapshot(window, cx);
16187 let buffer = &snapshot.buffer_snapshot();
16188 let position = self
16189 .selections
16190 .newest::<Point>(&snapshot.display_snapshot)
16191 .head();
16192 let anchor_position = buffer.anchor_after(position);
16193
16194 // Get all document highlights (both read and write)
16195 let mut all_highlights = Vec::new();
16196
16197 if let Some((_, read_highlights)) = self
16198 .background_highlights
16199 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16200 {
16201 all_highlights.extend(read_highlights.iter());
16202 }
16203
16204 if let Some((_, write_highlights)) = self
16205 .background_highlights
16206 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16207 {
16208 all_highlights.extend(write_highlights.iter());
16209 }
16210
16211 if all_highlights.is_empty() {
16212 return;
16213 }
16214
16215 // Sort highlights by position
16216 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16217
16218 let target_highlight = match direction {
16219 Direction::Next => {
16220 // Find the first highlight after the current position
16221 all_highlights
16222 .iter()
16223 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16224 }
16225 Direction::Prev => {
16226 // Find the last highlight before the current position
16227 all_highlights
16228 .iter()
16229 .rev()
16230 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16231 }
16232 };
16233
16234 if let Some(highlight) = target_highlight {
16235 let destination = highlight.start.to_point(buffer);
16236 let autoscroll = Autoscroll::center();
16237
16238 self.unfold_ranges(&[destination..destination], false, false, cx);
16239 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16240 s.select_ranges([destination..destination]);
16241 });
16242 }
16243 }
16244
16245 fn go_to_line<T: 'static>(
16246 &mut self,
16247 position: Anchor,
16248 highlight_color: Option<Hsla>,
16249 window: &mut Window,
16250 cx: &mut Context<Self>,
16251 ) {
16252 let snapshot = self.snapshot(window, cx).display_snapshot;
16253 let position = position.to_point(&snapshot.buffer_snapshot());
16254 let start = snapshot
16255 .buffer_snapshot()
16256 .clip_point(Point::new(position.row, 0), Bias::Left);
16257 let end = start + Point::new(1, 0);
16258 let start = snapshot.buffer_snapshot().anchor_before(start);
16259 let end = snapshot.buffer_snapshot().anchor_before(end);
16260
16261 self.highlight_rows::<T>(
16262 start..end,
16263 highlight_color
16264 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16265 Default::default(),
16266 cx,
16267 );
16268
16269 if self.buffer.read(cx).is_singleton() {
16270 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16271 }
16272 }
16273
16274 pub fn go_to_definition(
16275 &mut self,
16276 _: &GoToDefinition,
16277 window: &mut Window,
16278 cx: &mut Context<Self>,
16279 ) -> Task<Result<Navigated>> {
16280 let definition =
16281 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16282 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16283 cx.spawn_in(window, async move |editor, cx| {
16284 if definition.await? == Navigated::Yes {
16285 return Ok(Navigated::Yes);
16286 }
16287 match fallback_strategy {
16288 GoToDefinitionFallback::None => Ok(Navigated::No),
16289 GoToDefinitionFallback::FindAllReferences => {
16290 match editor.update_in(cx, |editor, window, cx| {
16291 editor.find_all_references(&FindAllReferences, window, cx)
16292 })? {
16293 Some(references) => references.await,
16294 None => Ok(Navigated::No),
16295 }
16296 }
16297 }
16298 })
16299 }
16300
16301 pub fn go_to_declaration(
16302 &mut self,
16303 _: &GoToDeclaration,
16304 window: &mut Window,
16305 cx: &mut Context<Self>,
16306 ) -> Task<Result<Navigated>> {
16307 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16308 }
16309
16310 pub fn go_to_declaration_split(
16311 &mut self,
16312 _: &GoToDeclaration,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) -> Task<Result<Navigated>> {
16316 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16317 }
16318
16319 pub fn go_to_implementation(
16320 &mut self,
16321 _: &GoToImplementation,
16322 window: &mut Window,
16323 cx: &mut Context<Self>,
16324 ) -> Task<Result<Navigated>> {
16325 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16326 }
16327
16328 pub fn go_to_implementation_split(
16329 &mut self,
16330 _: &GoToImplementationSplit,
16331 window: &mut Window,
16332 cx: &mut Context<Self>,
16333 ) -> Task<Result<Navigated>> {
16334 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16335 }
16336
16337 pub fn go_to_type_definition(
16338 &mut self,
16339 _: &GoToTypeDefinition,
16340 window: &mut Window,
16341 cx: &mut Context<Self>,
16342 ) -> Task<Result<Navigated>> {
16343 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16344 }
16345
16346 pub fn go_to_definition_split(
16347 &mut self,
16348 _: &GoToDefinitionSplit,
16349 window: &mut Window,
16350 cx: &mut Context<Self>,
16351 ) -> Task<Result<Navigated>> {
16352 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16353 }
16354
16355 pub fn go_to_type_definition_split(
16356 &mut self,
16357 _: &GoToTypeDefinitionSplit,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) -> Task<Result<Navigated>> {
16361 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16362 }
16363
16364 fn go_to_definition_of_kind(
16365 &mut self,
16366 kind: GotoDefinitionKind,
16367 split: bool,
16368 window: &mut Window,
16369 cx: &mut Context<Self>,
16370 ) -> Task<Result<Navigated>> {
16371 let Some(provider) = self.semantics_provider.clone() else {
16372 return Task::ready(Ok(Navigated::No));
16373 };
16374 let head = self
16375 .selections
16376 .newest::<usize>(&self.display_snapshot(cx))
16377 .head();
16378 let buffer = self.buffer.read(cx);
16379 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16380 return Task::ready(Ok(Navigated::No));
16381 };
16382 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16383 return Task::ready(Ok(Navigated::No));
16384 };
16385
16386 cx.spawn_in(window, async move |editor, cx| {
16387 let Some(definitions) = definitions.await? else {
16388 return Ok(Navigated::No);
16389 };
16390 let navigated = editor
16391 .update_in(cx, |editor, window, cx| {
16392 editor.navigate_to_hover_links(
16393 Some(kind),
16394 definitions
16395 .into_iter()
16396 .filter(|location| {
16397 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16398 })
16399 .map(HoverLink::Text)
16400 .collect::<Vec<_>>(),
16401 split,
16402 window,
16403 cx,
16404 )
16405 })?
16406 .await?;
16407 anyhow::Ok(navigated)
16408 })
16409 }
16410
16411 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16412 let selection = self.selections.newest_anchor();
16413 let head = selection.head();
16414 let tail = selection.tail();
16415
16416 let Some((buffer, start_position)) =
16417 self.buffer.read(cx).text_anchor_for_position(head, cx)
16418 else {
16419 return;
16420 };
16421
16422 let end_position = if head != tail {
16423 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16424 return;
16425 };
16426 Some(pos)
16427 } else {
16428 None
16429 };
16430
16431 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16432 let url = if let Some(end_pos) = end_position {
16433 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16434 } else {
16435 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16436 };
16437
16438 if let Some(url) = url {
16439 cx.update(|window, cx| {
16440 if parse_zed_link(&url, cx).is_some() {
16441 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16442 } else {
16443 cx.open_url(&url);
16444 }
16445 })?;
16446 }
16447
16448 anyhow::Ok(())
16449 });
16450
16451 url_finder.detach();
16452 }
16453
16454 pub fn open_selected_filename(
16455 &mut self,
16456 _: &OpenSelectedFilename,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) {
16460 let Some(workspace) = self.workspace() else {
16461 return;
16462 };
16463
16464 let position = self.selections.newest_anchor().head();
16465
16466 let Some((buffer, buffer_position)) =
16467 self.buffer.read(cx).text_anchor_for_position(position, cx)
16468 else {
16469 return;
16470 };
16471
16472 let project = self.project.clone();
16473
16474 cx.spawn_in(window, async move |_, cx| {
16475 let result = find_file(&buffer, project, buffer_position, cx).await;
16476
16477 if let Some((_, path)) = result {
16478 workspace
16479 .update_in(cx, |workspace, window, cx| {
16480 workspace.open_resolved_path(path, window, cx)
16481 })?
16482 .await?;
16483 }
16484 anyhow::Ok(())
16485 })
16486 .detach();
16487 }
16488
16489 pub(crate) fn navigate_to_hover_links(
16490 &mut self,
16491 kind: Option<GotoDefinitionKind>,
16492 definitions: Vec<HoverLink>,
16493 split: bool,
16494 window: &mut Window,
16495 cx: &mut Context<Editor>,
16496 ) -> Task<Result<Navigated>> {
16497 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16498 let mut first_url_or_file = None;
16499 let definitions: Vec<_> = definitions
16500 .into_iter()
16501 .filter_map(|def| match def {
16502 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16503 HoverLink::InlayHint(lsp_location, server_id) => {
16504 let computation =
16505 self.compute_target_location(lsp_location, server_id, window, cx);
16506 Some(cx.background_spawn(computation))
16507 }
16508 HoverLink::Url(url) => {
16509 first_url_or_file = Some(Either::Left(url));
16510 None
16511 }
16512 HoverLink::File(path) => {
16513 first_url_or_file = Some(Either::Right(path));
16514 None
16515 }
16516 })
16517 .collect();
16518
16519 let workspace = self.workspace();
16520
16521 cx.spawn_in(window, async move |editor, cx| {
16522 let locations: Vec<Location> = future::join_all(definitions)
16523 .await
16524 .into_iter()
16525 .filter_map(|location| location.transpose())
16526 .collect::<Result<_>>()
16527 .context("location tasks")?;
16528 let mut locations = cx.update(|_, cx| {
16529 locations
16530 .into_iter()
16531 .map(|location| {
16532 let buffer = location.buffer.read(cx);
16533 (location.buffer, location.range.to_point(buffer))
16534 })
16535 .into_group_map()
16536 })?;
16537 let mut num_locations = 0;
16538 for ranges in locations.values_mut() {
16539 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16540 ranges.dedup();
16541 num_locations += ranges.len();
16542 }
16543
16544 if num_locations > 1 {
16545 let Some(workspace) = workspace else {
16546 return Ok(Navigated::No);
16547 };
16548
16549 let tab_kind = match kind {
16550 Some(GotoDefinitionKind::Implementation) => "Implementations",
16551 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16552 Some(GotoDefinitionKind::Declaration) => "Declarations",
16553 Some(GotoDefinitionKind::Type) => "Types",
16554 };
16555 let title = editor
16556 .update_in(cx, |_, _, cx| {
16557 let target = locations
16558 .iter()
16559 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16560 .map(|(buffer, location)| {
16561 buffer
16562 .read(cx)
16563 .text_for_range(location.clone())
16564 .collect::<String>()
16565 })
16566 .filter(|text| !text.contains('\n'))
16567 .unique()
16568 .take(3)
16569 .join(", ");
16570 if target.is_empty() {
16571 tab_kind.to_owned()
16572 } else {
16573 format!("{tab_kind} for {target}")
16574 }
16575 })
16576 .context("buffer title")?;
16577
16578 let opened = workspace
16579 .update_in(cx, |workspace, window, cx| {
16580 Self::open_locations_in_multibuffer(
16581 workspace,
16582 locations,
16583 title,
16584 split,
16585 MultibufferSelectionMode::First,
16586 window,
16587 cx,
16588 )
16589 })
16590 .is_ok();
16591
16592 anyhow::Ok(Navigated::from_bool(opened))
16593 } else if num_locations == 0 {
16594 // If there is one url or file, open it directly
16595 match first_url_or_file {
16596 Some(Either::Left(url)) => {
16597 cx.update(|_, cx| cx.open_url(&url))?;
16598 Ok(Navigated::Yes)
16599 }
16600 Some(Either::Right(path)) => {
16601 let Some(workspace) = workspace else {
16602 return Ok(Navigated::No);
16603 };
16604
16605 workspace
16606 .update_in(cx, |workspace, window, cx| {
16607 workspace.open_resolved_path(path, window, cx)
16608 })?
16609 .await?;
16610 Ok(Navigated::Yes)
16611 }
16612 None => Ok(Navigated::No),
16613 }
16614 } else {
16615 let Some(workspace) = workspace else {
16616 return Ok(Navigated::No);
16617 };
16618
16619 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16620 let target_range = target_ranges.first().unwrap().clone();
16621
16622 editor.update_in(cx, |editor, window, cx| {
16623 let range = target_range.to_point(target_buffer.read(cx));
16624 let range = editor.range_for_match(&range);
16625 let range = collapse_multiline_range(range);
16626
16627 if !split
16628 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16629 {
16630 editor.go_to_singleton_buffer_range(range, window, cx);
16631 } else {
16632 let pane = workspace.read(cx).active_pane().clone();
16633 window.defer(cx, move |window, cx| {
16634 let target_editor: Entity<Self> =
16635 workspace.update(cx, |workspace, cx| {
16636 let pane = if split {
16637 workspace.adjacent_pane(window, cx)
16638 } else {
16639 workspace.active_pane().clone()
16640 };
16641
16642 workspace.open_project_item(
16643 pane,
16644 target_buffer.clone(),
16645 true,
16646 true,
16647 window,
16648 cx,
16649 )
16650 });
16651 target_editor.update(cx, |target_editor, cx| {
16652 // When selecting a definition in a different buffer, disable the nav history
16653 // to avoid creating a history entry at the previous cursor location.
16654 pane.update(cx, |pane, _| pane.disable_history());
16655 target_editor.go_to_singleton_buffer_range(range, window, cx);
16656 pane.update(cx, |pane, _| pane.enable_history());
16657 });
16658 });
16659 }
16660 Navigated::Yes
16661 })
16662 }
16663 })
16664 }
16665
16666 fn compute_target_location(
16667 &self,
16668 lsp_location: lsp::Location,
16669 server_id: LanguageServerId,
16670 window: &mut Window,
16671 cx: &mut Context<Self>,
16672 ) -> Task<anyhow::Result<Option<Location>>> {
16673 let Some(project) = self.project.clone() else {
16674 return Task::ready(Ok(None));
16675 };
16676
16677 cx.spawn_in(window, async move |editor, cx| {
16678 let location_task = editor.update(cx, |_, cx| {
16679 project.update(cx, |project, cx| {
16680 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16681 })
16682 })?;
16683 let location = Some({
16684 let target_buffer_handle = location_task.await.context("open local buffer")?;
16685 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16686 let target_start = target_buffer
16687 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16688 let target_end = target_buffer
16689 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16690 target_buffer.anchor_after(target_start)
16691 ..target_buffer.anchor_before(target_end)
16692 })?;
16693 Location {
16694 buffer: target_buffer_handle,
16695 range,
16696 }
16697 });
16698 Ok(location)
16699 })
16700 }
16701
16702 fn go_to_next_reference(
16703 &mut self,
16704 _: &GoToNextReference,
16705 window: &mut Window,
16706 cx: &mut Context<Self>,
16707 ) {
16708 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16709 if let Some(task) = task {
16710 task.detach();
16711 };
16712 }
16713
16714 fn go_to_prev_reference(
16715 &mut self,
16716 _: &GoToPreviousReference,
16717 window: &mut Window,
16718 cx: &mut Context<Self>,
16719 ) {
16720 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16721 if let Some(task) = task {
16722 task.detach();
16723 };
16724 }
16725
16726 pub fn go_to_reference_before_or_after_position(
16727 &mut self,
16728 direction: Direction,
16729 count: usize,
16730 window: &mut Window,
16731 cx: &mut Context<Self>,
16732 ) -> Option<Task<Result<()>>> {
16733 let selection = self.selections.newest_anchor();
16734 let head = selection.head();
16735
16736 let multi_buffer = self.buffer.read(cx);
16737
16738 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16739 let workspace = self.workspace()?;
16740 let project = workspace.read(cx).project().clone();
16741 let references =
16742 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16743 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16744 let Some(locations) = references.await? else {
16745 return Ok(());
16746 };
16747
16748 if locations.is_empty() {
16749 // totally normal - the cursor may be on something which is not
16750 // a symbol (e.g. a keyword)
16751 log::info!("no references found under cursor");
16752 return Ok(());
16753 }
16754
16755 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16756
16757 let multi_buffer_snapshot =
16758 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16759
16760 let (locations, current_location_index) =
16761 multi_buffer.update(cx, |multi_buffer, cx| {
16762 let mut locations = locations
16763 .into_iter()
16764 .filter_map(|loc| {
16765 let start = multi_buffer.buffer_anchor_to_anchor(
16766 &loc.buffer,
16767 loc.range.start,
16768 cx,
16769 )?;
16770 let end = multi_buffer.buffer_anchor_to_anchor(
16771 &loc.buffer,
16772 loc.range.end,
16773 cx,
16774 )?;
16775 Some(start..end)
16776 })
16777 .collect::<Vec<_>>();
16778
16779 // There is an O(n) implementation, but given this list will be
16780 // small (usually <100 items), the extra O(log(n)) factor isn't
16781 // worth the (surprisingly large amount of) extra complexity.
16782 locations
16783 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16784
16785 let head_offset = head.to_offset(&multi_buffer_snapshot);
16786
16787 let current_location_index = locations.iter().position(|loc| {
16788 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16789 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16790 });
16791
16792 (locations, current_location_index)
16793 })?;
16794
16795 let Some(current_location_index) = current_location_index else {
16796 // This indicates something has gone wrong, because we already
16797 // handle the "no references" case above
16798 log::error!(
16799 "failed to find current reference under cursor. Total references: {}",
16800 locations.len()
16801 );
16802 return Ok(());
16803 };
16804
16805 let destination_location_index = match direction {
16806 Direction::Next => (current_location_index + count) % locations.len(),
16807 Direction::Prev => {
16808 (current_location_index + locations.len() - count % locations.len())
16809 % locations.len()
16810 }
16811 };
16812
16813 // TODO(cameron): is this needed?
16814 // the thinking is to avoid "jumping to the current location" (avoid
16815 // polluting "jumplist" in vim terms)
16816 if current_location_index == destination_location_index {
16817 return Ok(());
16818 }
16819
16820 let Range { start, end } = locations[destination_location_index];
16821
16822 editor.update_in(cx, |editor, window, cx| {
16823 let effects = SelectionEffects::default();
16824
16825 editor.unfold_ranges(&[start..end], false, false, cx);
16826 editor.change_selections(effects, window, cx, |s| {
16827 s.select_ranges([start..start]);
16828 });
16829 })?;
16830
16831 Ok(())
16832 }))
16833 }
16834
16835 pub fn find_all_references(
16836 &mut self,
16837 _: &FindAllReferences,
16838 window: &mut Window,
16839 cx: &mut Context<Self>,
16840 ) -> Option<Task<Result<Navigated>>> {
16841 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16842 let multi_buffer = self.buffer.read(cx);
16843 let head = selection.head();
16844
16845 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16846 let head_anchor = multi_buffer_snapshot.anchor_at(
16847 head,
16848 if head < selection.tail() {
16849 Bias::Right
16850 } else {
16851 Bias::Left
16852 },
16853 );
16854
16855 match self
16856 .find_all_references_task_sources
16857 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16858 {
16859 Ok(_) => {
16860 log::info!(
16861 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16862 );
16863 return None;
16864 }
16865 Err(i) => {
16866 self.find_all_references_task_sources.insert(i, head_anchor);
16867 }
16868 }
16869
16870 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16871 let workspace = self.workspace()?;
16872 let project = workspace.read(cx).project().clone();
16873 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16874 Some(cx.spawn_in(window, async move |editor, cx| {
16875 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16876 if let Ok(i) = editor
16877 .find_all_references_task_sources
16878 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16879 {
16880 editor.find_all_references_task_sources.remove(i);
16881 }
16882 });
16883
16884 let Some(locations) = references.await? else {
16885 return anyhow::Ok(Navigated::No);
16886 };
16887 let mut locations = cx.update(|_, cx| {
16888 locations
16889 .into_iter()
16890 .map(|location| {
16891 let buffer = location.buffer.read(cx);
16892 (location.buffer, location.range.to_point(buffer))
16893 })
16894 .into_group_map()
16895 })?;
16896 if locations.is_empty() {
16897 return anyhow::Ok(Navigated::No);
16898 }
16899 for ranges in locations.values_mut() {
16900 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16901 ranges.dedup();
16902 }
16903
16904 workspace.update_in(cx, |workspace, window, cx| {
16905 let target = locations
16906 .iter()
16907 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16908 .map(|(buffer, location)| {
16909 buffer
16910 .read(cx)
16911 .text_for_range(location.clone())
16912 .collect::<String>()
16913 })
16914 .filter(|text| !text.contains('\n'))
16915 .unique()
16916 .take(3)
16917 .join(", ");
16918 let title = if target.is_empty() {
16919 "References".to_owned()
16920 } else {
16921 format!("References to {target}")
16922 };
16923 Self::open_locations_in_multibuffer(
16924 workspace,
16925 locations,
16926 title,
16927 false,
16928 MultibufferSelectionMode::First,
16929 window,
16930 cx,
16931 );
16932 Navigated::Yes
16933 })
16934 }))
16935 }
16936
16937 /// Opens a multibuffer with the given project locations in it
16938 pub fn open_locations_in_multibuffer(
16939 workspace: &mut Workspace,
16940 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16941 title: String,
16942 split: bool,
16943 multibuffer_selection_mode: MultibufferSelectionMode,
16944 window: &mut Window,
16945 cx: &mut Context<Workspace>,
16946 ) {
16947 if locations.is_empty() {
16948 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16949 return;
16950 }
16951
16952 let capability = workspace.project().read(cx).capability();
16953 let mut ranges = <Vec<Range<Anchor>>>::new();
16954
16955 // a key to find existing multibuffer editors with the same set of locations
16956 // to prevent us from opening more and more multibuffer tabs for searches and the like
16957 let mut key = (title.clone(), vec![]);
16958 let excerpt_buffer = cx.new(|cx| {
16959 let key = &mut key.1;
16960 let mut multibuffer = MultiBuffer::new(capability);
16961 for (buffer, mut ranges_for_buffer) in locations {
16962 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16963 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16964 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16965 PathKey::for_buffer(&buffer, cx),
16966 buffer.clone(),
16967 ranges_for_buffer,
16968 multibuffer_context_lines(cx),
16969 cx,
16970 );
16971 ranges.extend(new_ranges)
16972 }
16973
16974 multibuffer.with_title(title)
16975 });
16976 let existing = workspace.active_pane().update(cx, |pane, cx| {
16977 pane.items()
16978 .filter_map(|item| item.downcast::<Editor>())
16979 .find(|editor| {
16980 editor
16981 .read(cx)
16982 .lookup_key
16983 .as_ref()
16984 .and_then(|it| {
16985 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16986 })
16987 .is_some_and(|it| *it == key)
16988 })
16989 });
16990 let editor = existing.unwrap_or_else(|| {
16991 cx.new(|cx| {
16992 let mut editor = Editor::for_multibuffer(
16993 excerpt_buffer,
16994 Some(workspace.project().clone()),
16995 window,
16996 cx,
16997 );
16998 editor.lookup_key = Some(Box::new(key));
16999 editor
17000 })
17001 });
17002 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17003 MultibufferSelectionMode::First => {
17004 if let Some(first_range) = ranges.first() {
17005 editor.change_selections(
17006 SelectionEffects::no_scroll(),
17007 window,
17008 cx,
17009 |selections| {
17010 selections.clear_disjoint();
17011 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17012 },
17013 );
17014 }
17015 editor.highlight_background::<Self>(
17016 &ranges,
17017 |theme| theme.colors().editor_highlighted_line_background,
17018 cx,
17019 );
17020 }
17021 MultibufferSelectionMode::All => {
17022 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17023 selections.clear_disjoint();
17024 selections.select_anchor_ranges(ranges);
17025 });
17026 }
17027 });
17028
17029 let item = Box::new(editor);
17030 let item_id = item.item_id();
17031
17032 if split {
17033 let pane = workspace.adjacent_pane(window, cx);
17034 workspace.add_item(pane, item, None, true, true, window, cx);
17035 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17036 let (preview_item_id, preview_item_idx) =
17037 workspace.active_pane().read_with(cx, |pane, _| {
17038 (pane.preview_item_id(), pane.preview_item_idx())
17039 });
17040
17041 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17042
17043 if let Some(preview_item_id) = preview_item_id {
17044 workspace.active_pane().update(cx, |pane, cx| {
17045 pane.remove_item(preview_item_id, false, false, window, cx);
17046 });
17047 }
17048 } else {
17049 workspace.add_item_to_active_pane(item, None, true, window, cx);
17050 }
17051 workspace.active_pane().update(cx, |pane, cx| {
17052 pane.set_preview_item_id(Some(item_id), cx);
17053 });
17054 }
17055
17056 pub fn rename(
17057 &mut self,
17058 _: &Rename,
17059 window: &mut Window,
17060 cx: &mut Context<Self>,
17061 ) -> Option<Task<Result<()>>> {
17062 use language::ToOffset as _;
17063
17064 let provider = self.semantics_provider.clone()?;
17065 let selection = self.selections.newest_anchor().clone();
17066 let (cursor_buffer, cursor_buffer_position) = self
17067 .buffer
17068 .read(cx)
17069 .text_anchor_for_position(selection.head(), cx)?;
17070 let (tail_buffer, cursor_buffer_position_end) = self
17071 .buffer
17072 .read(cx)
17073 .text_anchor_for_position(selection.tail(), cx)?;
17074 if tail_buffer != cursor_buffer {
17075 return None;
17076 }
17077
17078 let snapshot = cursor_buffer.read(cx).snapshot();
17079 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17080 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17081 let prepare_rename = provider
17082 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17083 .unwrap_or_else(|| Task::ready(Ok(None)));
17084 drop(snapshot);
17085
17086 Some(cx.spawn_in(window, async move |this, cx| {
17087 let rename_range = if let Some(range) = prepare_rename.await? {
17088 Some(range)
17089 } else {
17090 this.update(cx, |this, cx| {
17091 let buffer = this.buffer.read(cx).snapshot(cx);
17092 let mut buffer_highlights = this
17093 .document_highlights_for_position(selection.head(), &buffer)
17094 .filter(|highlight| {
17095 highlight.start.excerpt_id == selection.head().excerpt_id
17096 && highlight.end.excerpt_id == selection.head().excerpt_id
17097 });
17098 buffer_highlights
17099 .next()
17100 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17101 })?
17102 };
17103 if let Some(rename_range) = rename_range {
17104 this.update_in(cx, |this, window, cx| {
17105 let snapshot = cursor_buffer.read(cx).snapshot();
17106 let rename_buffer_range = rename_range.to_offset(&snapshot);
17107 let cursor_offset_in_rename_range =
17108 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17109 let cursor_offset_in_rename_range_end =
17110 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17111
17112 this.take_rename(false, window, cx);
17113 let buffer = this.buffer.read(cx).read(cx);
17114 let cursor_offset = selection.head().to_offset(&buffer);
17115 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17116 let rename_end = rename_start + rename_buffer_range.len();
17117 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17118 let mut old_highlight_id = None;
17119 let old_name: Arc<str> = buffer
17120 .chunks(rename_start..rename_end, true)
17121 .map(|chunk| {
17122 if old_highlight_id.is_none() {
17123 old_highlight_id = chunk.syntax_highlight_id;
17124 }
17125 chunk.text
17126 })
17127 .collect::<String>()
17128 .into();
17129
17130 drop(buffer);
17131
17132 // Position the selection in the rename editor so that it matches the current selection.
17133 this.show_local_selections = false;
17134 let rename_editor = cx.new(|cx| {
17135 let mut editor = Editor::single_line(window, cx);
17136 editor.buffer.update(cx, |buffer, cx| {
17137 buffer.edit([(0..0, old_name.clone())], None, cx)
17138 });
17139 let rename_selection_range = match cursor_offset_in_rename_range
17140 .cmp(&cursor_offset_in_rename_range_end)
17141 {
17142 Ordering::Equal => {
17143 editor.select_all(&SelectAll, window, cx);
17144 return editor;
17145 }
17146 Ordering::Less => {
17147 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17148 }
17149 Ordering::Greater => {
17150 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17151 }
17152 };
17153 if rename_selection_range.end > old_name.len() {
17154 editor.select_all(&SelectAll, window, cx);
17155 } else {
17156 editor.change_selections(Default::default(), window, cx, |s| {
17157 s.select_ranges([rename_selection_range]);
17158 });
17159 }
17160 editor
17161 });
17162 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17163 if e == &EditorEvent::Focused {
17164 cx.emit(EditorEvent::FocusedIn)
17165 }
17166 })
17167 .detach();
17168
17169 let write_highlights =
17170 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17171 let read_highlights =
17172 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17173 let ranges = write_highlights
17174 .iter()
17175 .flat_map(|(_, ranges)| ranges.iter())
17176 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17177 .cloned()
17178 .collect();
17179
17180 this.highlight_text::<Rename>(
17181 ranges,
17182 HighlightStyle {
17183 fade_out: Some(0.6),
17184 ..Default::default()
17185 },
17186 cx,
17187 );
17188 let rename_focus_handle = rename_editor.focus_handle(cx);
17189 window.focus(&rename_focus_handle);
17190 let block_id = this.insert_blocks(
17191 [BlockProperties {
17192 style: BlockStyle::Flex,
17193 placement: BlockPlacement::Below(range.start),
17194 height: Some(1),
17195 render: Arc::new({
17196 let rename_editor = rename_editor.clone();
17197 move |cx: &mut BlockContext| {
17198 let mut text_style = cx.editor_style.text.clone();
17199 if let Some(highlight_style) = old_highlight_id
17200 .and_then(|h| h.style(&cx.editor_style.syntax))
17201 {
17202 text_style = text_style.highlight(highlight_style);
17203 }
17204 div()
17205 .block_mouse_except_scroll()
17206 .pl(cx.anchor_x)
17207 .child(EditorElement::new(
17208 &rename_editor,
17209 EditorStyle {
17210 background: cx.theme().system().transparent,
17211 local_player: cx.editor_style.local_player,
17212 text: text_style,
17213 scrollbar_width: cx.editor_style.scrollbar_width,
17214 syntax: cx.editor_style.syntax.clone(),
17215 status: cx.editor_style.status.clone(),
17216 inlay_hints_style: HighlightStyle {
17217 font_weight: Some(FontWeight::BOLD),
17218 ..make_inlay_hints_style(cx.app)
17219 },
17220 edit_prediction_styles: make_suggestion_styles(
17221 cx.app,
17222 ),
17223 ..EditorStyle::default()
17224 },
17225 ))
17226 .into_any_element()
17227 }
17228 }),
17229 priority: 0,
17230 }],
17231 Some(Autoscroll::fit()),
17232 cx,
17233 )[0];
17234 this.pending_rename = Some(RenameState {
17235 range,
17236 old_name,
17237 editor: rename_editor,
17238 block_id,
17239 });
17240 })?;
17241 }
17242
17243 Ok(())
17244 }))
17245 }
17246
17247 pub fn confirm_rename(
17248 &mut self,
17249 _: &ConfirmRename,
17250 window: &mut Window,
17251 cx: &mut Context<Self>,
17252 ) -> Option<Task<Result<()>>> {
17253 let rename = self.take_rename(false, window, cx)?;
17254 let workspace = self.workspace()?.downgrade();
17255 let (buffer, start) = self
17256 .buffer
17257 .read(cx)
17258 .text_anchor_for_position(rename.range.start, cx)?;
17259 let (end_buffer, _) = self
17260 .buffer
17261 .read(cx)
17262 .text_anchor_for_position(rename.range.end, cx)?;
17263 if buffer != end_buffer {
17264 return None;
17265 }
17266
17267 let old_name = rename.old_name;
17268 let new_name = rename.editor.read(cx).text(cx);
17269
17270 let rename = self.semantics_provider.as_ref()?.perform_rename(
17271 &buffer,
17272 start,
17273 new_name.clone(),
17274 cx,
17275 )?;
17276
17277 Some(cx.spawn_in(window, async move |editor, cx| {
17278 let project_transaction = rename.await?;
17279 Self::open_project_transaction(
17280 &editor,
17281 workspace,
17282 project_transaction,
17283 format!("Rename: {} → {}", old_name, new_name),
17284 cx,
17285 )
17286 .await?;
17287
17288 editor.update(cx, |editor, cx| {
17289 editor.refresh_document_highlights(cx);
17290 })?;
17291 Ok(())
17292 }))
17293 }
17294
17295 fn take_rename(
17296 &mut self,
17297 moving_cursor: bool,
17298 window: &mut Window,
17299 cx: &mut Context<Self>,
17300 ) -> Option<RenameState> {
17301 let rename = self.pending_rename.take()?;
17302 if rename.editor.focus_handle(cx).is_focused(window) {
17303 window.focus(&self.focus_handle);
17304 }
17305
17306 self.remove_blocks(
17307 [rename.block_id].into_iter().collect(),
17308 Some(Autoscroll::fit()),
17309 cx,
17310 );
17311 self.clear_highlights::<Rename>(cx);
17312 self.show_local_selections = true;
17313
17314 if moving_cursor {
17315 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17316 editor
17317 .selections
17318 .newest::<usize>(&editor.display_snapshot(cx))
17319 .head()
17320 });
17321
17322 // Update the selection to match the position of the selection inside
17323 // the rename editor.
17324 let snapshot = self.buffer.read(cx).read(cx);
17325 let rename_range = rename.range.to_offset(&snapshot);
17326 let cursor_in_editor = snapshot
17327 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17328 .min(rename_range.end);
17329 drop(snapshot);
17330
17331 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17332 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17333 });
17334 } else {
17335 self.refresh_document_highlights(cx);
17336 }
17337
17338 Some(rename)
17339 }
17340
17341 pub fn pending_rename(&self) -> Option<&RenameState> {
17342 self.pending_rename.as_ref()
17343 }
17344
17345 fn format(
17346 &mut self,
17347 _: &Format,
17348 window: &mut Window,
17349 cx: &mut Context<Self>,
17350 ) -> Option<Task<Result<()>>> {
17351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17352
17353 let project = match &self.project {
17354 Some(project) => project.clone(),
17355 None => return None,
17356 };
17357
17358 Some(self.perform_format(
17359 project,
17360 FormatTrigger::Manual,
17361 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17362 window,
17363 cx,
17364 ))
17365 }
17366
17367 fn format_selections(
17368 &mut self,
17369 _: &FormatSelections,
17370 window: &mut Window,
17371 cx: &mut Context<Self>,
17372 ) -> Option<Task<Result<()>>> {
17373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17374
17375 let project = match &self.project {
17376 Some(project) => project.clone(),
17377 None => return None,
17378 };
17379
17380 let ranges = self
17381 .selections
17382 .all_adjusted(&self.display_snapshot(cx))
17383 .into_iter()
17384 .map(|selection| selection.range())
17385 .collect_vec();
17386
17387 Some(self.perform_format(
17388 project,
17389 FormatTrigger::Manual,
17390 FormatTarget::Ranges(ranges),
17391 window,
17392 cx,
17393 ))
17394 }
17395
17396 fn perform_format(
17397 &mut self,
17398 project: Entity<Project>,
17399 trigger: FormatTrigger,
17400 target: FormatTarget,
17401 window: &mut Window,
17402 cx: &mut Context<Self>,
17403 ) -> Task<Result<()>> {
17404 let buffer = self.buffer.clone();
17405 let (buffers, target) = match target {
17406 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17407 FormatTarget::Ranges(selection_ranges) => {
17408 let multi_buffer = buffer.read(cx);
17409 let snapshot = multi_buffer.read(cx);
17410 let mut buffers = HashSet::default();
17411 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17412 BTreeMap::new();
17413 for selection_range in selection_ranges {
17414 for (buffer, buffer_range, _) in
17415 snapshot.range_to_buffer_ranges(selection_range)
17416 {
17417 let buffer_id = buffer.remote_id();
17418 let start = buffer.anchor_before(buffer_range.start);
17419 let end = buffer.anchor_after(buffer_range.end);
17420 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17421 buffer_id_to_ranges
17422 .entry(buffer_id)
17423 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17424 .or_insert_with(|| vec![start..end]);
17425 }
17426 }
17427 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17428 }
17429 };
17430
17431 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17432 let selections_prev = transaction_id_prev
17433 .and_then(|transaction_id_prev| {
17434 // default to selections as they were after the last edit, if we have them,
17435 // instead of how they are now.
17436 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17437 // will take you back to where you made the last edit, instead of staying where you scrolled
17438 self.selection_history
17439 .transaction(transaction_id_prev)
17440 .map(|t| t.0.clone())
17441 })
17442 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17443
17444 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17445 let format = project.update(cx, |project, cx| {
17446 project.format(buffers, target, true, trigger, cx)
17447 });
17448
17449 cx.spawn_in(window, async move |editor, cx| {
17450 let transaction = futures::select_biased! {
17451 transaction = format.log_err().fuse() => transaction,
17452 () = timeout => {
17453 log::warn!("timed out waiting for formatting");
17454 None
17455 }
17456 };
17457
17458 buffer
17459 .update(cx, |buffer, cx| {
17460 if let Some(transaction) = transaction
17461 && !buffer.is_singleton()
17462 {
17463 buffer.push_transaction(&transaction.0, cx);
17464 }
17465 cx.notify();
17466 })
17467 .ok();
17468
17469 if let Some(transaction_id_now) =
17470 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17471 {
17472 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17473 if has_new_transaction {
17474 _ = editor.update(cx, |editor, _| {
17475 editor
17476 .selection_history
17477 .insert_transaction(transaction_id_now, selections_prev);
17478 });
17479 }
17480 }
17481
17482 Ok(())
17483 })
17484 }
17485
17486 fn organize_imports(
17487 &mut self,
17488 _: &OrganizeImports,
17489 window: &mut Window,
17490 cx: &mut Context<Self>,
17491 ) -> Option<Task<Result<()>>> {
17492 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17493 let project = match &self.project {
17494 Some(project) => project.clone(),
17495 None => return None,
17496 };
17497 Some(self.perform_code_action_kind(
17498 project,
17499 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17500 window,
17501 cx,
17502 ))
17503 }
17504
17505 fn perform_code_action_kind(
17506 &mut self,
17507 project: Entity<Project>,
17508 kind: CodeActionKind,
17509 window: &mut Window,
17510 cx: &mut Context<Self>,
17511 ) -> Task<Result<()>> {
17512 let buffer = self.buffer.clone();
17513 let buffers = buffer.read(cx).all_buffers();
17514 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17515 let apply_action = project.update(cx, |project, cx| {
17516 project.apply_code_action_kind(buffers, kind, true, cx)
17517 });
17518 cx.spawn_in(window, async move |_, cx| {
17519 let transaction = futures::select_biased! {
17520 () = timeout => {
17521 log::warn!("timed out waiting for executing code action");
17522 None
17523 }
17524 transaction = apply_action.log_err().fuse() => transaction,
17525 };
17526 buffer
17527 .update(cx, |buffer, cx| {
17528 // check if we need this
17529 if let Some(transaction) = transaction
17530 && !buffer.is_singleton()
17531 {
17532 buffer.push_transaction(&transaction.0, cx);
17533 }
17534 cx.notify();
17535 })
17536 .ok();
17537 Ok(())
17538 })
17539 }
17540
17541 pub fn restart_language_server(
17542 &mut self,
17543 _: &RestartLanguageServer,
17544 _: &mut Window,
17545 cx: &mut Context<Self>,
17546 ) {
17547 if let Some(project) = self.project.clone() {
17548 self.buffer.update(cx, |multi_buffer, cx| {
17549 project.update(cx, |project, cx| {
17550 project.restart_language_servers_for_buffers(
17551 multi_buffer.all_buffers().into_iter().collect(),
17552 HashSet::default(),
17553 cx,
17554 );
17555 });
17556 })
17557 }
17558 }
17559
17560 pub fn stop_language_server(
17561 &mut self,
17562 _: &StopLanguageServer,
17563 _: &mut Window,
17564 cx: &mut Context<Self>,
17565 ) {
17566 if let Some(project) = self.project.clone() {
17567 self.buffer.update(cx, |multi_buffer, cx| {
17568 project.update(cx, |project, cx| {
17569 project.stop_language_servers_for_buffers(
17570 multi_buffer.all_buffers().into_iter().collect(),
17571 HashSet::default(),
17572 cx,
17573 );
17574 });
17575 });
17576 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17577 }
17578 }
17579
17580 fn cancel_language_server_work(
17581 workspace: &mut Workspace,
17582 _: &actions::CancelLanguageServerWork,
17583 _: &mut Window,
17584 cx: &mut Context<Workspace>,
17585 ) {
17586 let project = workspace.project();
17587 let buffers = workspace
17588 .active_item(cx)
17589 .and_then(|item| item.act_as::<Editor>(cx))
17590 .map_or(HashSet::default(), |editor| {
17591 editor.read(cx).buffer.read(cx).all_buffers()
17592 });
17593 project.update(cx, |project, cx| {
17594 project.cancel_language_server_work_for_buffers(buffers, cx);
17595 });
17596 }
17597
17598 fn show_character_palette(
17599 &mut self,
17600 _: &ShowCharacterPalette,
17601 window: &mut Window,
17602 _: &mut Context<Self>,
17603 ) {
17604 window.show_character_palette();
17605 }
17606
17607 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17608 if !self.diagnostics_enabled() {
17609 return;
17610 }
17611
17612 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17613 let buffer = self.buffer.read(cx).snapshot(cx);
17614 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17615 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17616 let is_valid = buffer
17617 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17618 .any(|entry| {
17619 entry.diagnostic.is_primary
17620 && !entry.range.is_empty()
17621 && entry.range.start == primary_range_start
17622 && entry.diagnostic.message == active_diagnostics.active_message
17623 });
17624
17625 if !is_valid {
17626 self.dismiss_diagnostics(cx);
17627 }
17628 }
17629 }
17630
17631 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17632 match &self.active_diagnostics {
17633 ActiveDiagnostic::Group(group) => Some(group),
17634 _ => None,
17635 }
17636 }
17637
17638 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17639 if !self.diagnostics_enabled() {
17640 return;
17641 }
17642 self.dismiss_diagnostics(cx);
17643 self.active_diagnostics = ActiveDiagnostic::All;
17644 }
17645
17646 fn activate_diagnostics(
17647 &mut self,
17648 buffer_id: BufferId,
17649 diagnostic: DiagnosticEntryRef<'_, usize>,
17650 window: &mut Window,
17651 cx: &mut Context<Self>,
17652 ) {
17653 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17654 return;
17655 }
17656 self.dismiss_diagnostics(cx);
17657 let snapshot = self.snapshot(window, cx);
17658 let buffer = self.buffer.read(cx).snapshot(cx);
17659 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17660 return;
17661 };
17662
17663 let diagnostic_group = buffer
17664 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17665 .collect::<Vec<_>>();
17666
17667 let blocks =
17668 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17669
17670 let blocks = self.display_map.update(cx, |display_map, cx| {
17671 display_map.insert_blocks(blocks, cx).into_iter().collect()
17672 });
17673 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17674 active_range: buffer.anchor_before(diagnostic.range.start)
17675 ..buffer.anchor_after(diagnostic.range.end),
17676 active_message: diagnostic.diagnostic.message.clone(),
17677 group_id: diagnostic.diagnostic.group_id,
17678 blocks,
17679 });
17680 cx.notify();
17681 }
17682
17683 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17684 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17685 return;
17686 };
17687
17688 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17689 if let ActiveDiagnostic::Group(group) = prev {
17690 self.display_map.update(cx, |display_map, cx| {
17691 display_map.remove_blocks(group.blocks, cx);
17692 });
17693 cx.notify();
17694 }
17695 }
17696
17697 /// Disable inline diagnostics rendering for this editor.
17698 pub fn disable_inline_diagnostics(&mut self) {
17699 self.inline_diagnostics_enabled = false;
17700 self.inline_diagnostics_update = Task::ready(());
17701 self.inline_diagnostics.clear();
17702 }
17703
17704 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17705 self.diagnostics_enabled = false;
17706 self.dismiss_diagnostics(cx);
17707 self.inline_diagnostics_update = Task::ready(());
17708 self.inline_diagnostics.clear();
17709 }
17710
17711 pub fn disable_word_completions(&mut self) {
17712 self.word_completions_enabled = false;
17713 }
17714
17715 pub fn diagnostics_enabled(&self) -> bool {
17716 self.diagnostics_enabled && self.mode.is_full()
17717 }
17718
17719 pub fn inline_diagnostics_enabled(&self) -> bool {
17720 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17721 }
17722
17723 pub fn show_inline_diagnostics(&self) -> bool {
17724 self.show_inline_diagnostics
17725 }
17726
17727 pub fn toggle_inline_diagnostics(
17728 &mut self,
17729 _: &ToggleInlineDiagnostics,
17730 window: &mut Window,
17731 cx: &mut Context<Editor>,
17732 ) {
17733 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17734 self.refresh_inline_diagnostics(false, window, cx);
17735 }
17736
17737 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17738 self.diagnostics_max_severity = severity;
17739 self.display_map.update(cx, |display_map, _| {
17740 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17741 });
17742 }
17743
17744 pub fn toggle_diagnostics(
17745 &mut self,
17746 _: &ToggleDiagnostics,
17747 window: &mut Window,
17748 cx: &mut Context<Editor>,
17749 ) {
17750 if !self.diagnostics_enabled() {
17751 return;
17752 }
17753
17754 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17755 EditorSettings::get_global(cx)
17756 .diagnostics_max_severity
17757 .filter(|severity| severity != &DiagnosticSeverity::Off)
17758 .unwrap_or(DiagnosticSeverity::Hint)
17759 } else {
17760 DiagnosticSeverity::Off
17761 };
17762 self.set_max_diagnostics_severity(new_severity, cx);
17763 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17764 self.active_diagnostics = ActiveDiagnostic::None;
17765 self.inline_diagnostics_update = Task::ready(());
17766 self.inline_diagnostics.clear();
17767 } else {
17768 self.refresh_inline_diagnostics(false, window, cx);
17769 }
17770
17771 cx.notify();
17772 }
17773
17774 pub fn toggle_minimap(
17775 &mut self,
17776 _: &ToggleMinimap,
17777 window: &mut Window,
17778 cx: &mut Context<Editor>,
17779 ) {
17780 if self.supports_minimap(cx) {
17781 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17782 }
17783 }
17784
17785 fn refresh_inline_diagnostics(
17786 &mut self,
17787 debounce: bool,
17788 window: &mut Window,
17789 cx: &mut Context<Self>,
17790 ) {
17791 let max_severity = ProjectSettings::get_global(cx)
17792 .diagnostics
17793 .inline
17794 .max_severity
17795 .unwrap_or(self.diagnostics_max_severity);
17796
17797 if !self.inline_diagnostics_enabled()
17798 || !self.show_inline_diagnostics
17799 || max_severity == DiagnosticSeverity::Off
17800 {
17801 self.inline_diagnostics_update = Task::ready(());
17802 self.inline_diagnostics.clear();
17803 return;
17804 }
17805
17806 let debounce_ms = ProjectSettings::get_global(cx)
17807 .diagnostics
17808 .inline
17809 .update_debounce_ms;
17810 let debounce = if debounce && debounce_ms > 0 {
17811 Some(Duration::from_millis(debounce_ms))
17812 } else {
17813 None
17814 };
17815 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17816 if let Some(debounce) = debounce {
17817 cx.background_executor().timer(debounce).await;
17818 }
17819 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17820 editor
17821 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17822 .ok()
17823 }) else {
17824 return;
17825 };
17826
17827 let new_inline_diagnostics = cx
17828 .background_spawn(async move {
17829 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17830 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17831 let message = diagnostic_entry
17832 .diagnostic
17833 .message
17834 .split_once('\n')
17835 .map(|(line, _)| line)
17836 .map(SharedString::new)
17837 .unwrap_or_else(|| {
17838 SharedString::new(&*diagnostic_entry.diagnostic.message)
17839 });
17840 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17841 let (Ok(i) | Err(i)) = inline_diagnostics
17842 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17843 inline_diagnostics.insert(
17844 i,
17845 (
17846 start_anchor,
17847 InlineDiagnostic {
17848 message,
17849 group_id: diagnostic_entry.diagnostic.group_id,
17850 start: diagnostic_entry.range.start.to_point(&snapshot),
17851 is_primary: diagnostic_entry.diagnostic.is_primary,
17852 severity: diagnostic_entry.diagnostic.severity,
17853 },
17854 ),
17855 );
17856 }
17857 inline_diagnostics
17858 })
17859 .await;
17860
17861 editor
17862 .update(cx, |editor, cx| {
17863 editor.inline_diagnostics = new_inline_diagnostics;
17864 cx.notify();
17865 })
17866 .ok();
17867 });
17868 }
17869
17870 fn pull_diagnostics(
17871 &mut self,
17872 buffer_id: Option<BufferId>,
17873 window: &Window,
17874 cx: &mut Context<Self>,
17875 ) -> Option<()> {
17876 if self.ignore_lsp_data() {
17877 return None;
17878 }
17879 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17880 .diagnostics
17881 .lsp_pull_diagnostics;
17882 if !pull_diagnostics_settings.enabled {
17883 return None;
17884 }
17885 let project = self.project()?.downgrade();
17886 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17887 let mut buffers = self.buffer.read(cx).all_buffers();
17888 buffers.retain(|buffer| {
17889 let buffer_id_to_retain = buffer.read(cx).remote_id();
17890 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17891 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17892 });
17893 if buffers.is_empty() {
17894 self.pull_diagnostics_task = Task::ready(());
17895 return None;
17896 }
17897
17898 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17899 cx.background_executor().timer(debounce).await;
17900
17901 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17902 buffers
17903 .into_iter()
17904 .filter_map(|buffer| {
17905 project
17906 .update(cx, |project, cx| {
17907 project.lsp_store().update(cx, |lsp_store, cx| {
17908 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17909 })
17910 })
17911 .ok()
17912 })
17913 .collect::<FuturesUnordered<_>>()
17914 }) else {
17915 return;
17916 };
17917
17918 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17919 match pull_task {
17920 Ok(()) => {
17921 if editor
17922 .update_in(cx, |editor, window, cx| {
17923 editor.update_diagnostics_state(window, cx);
17924 })
17925 .is_err()
17926 {
17927 return;
17928 }
17929 }
17930 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17931 }
17932 }
17933 });
17934
17935 Some(())
17936 }
17937
17938 pub fn set_selections_from_remote(
17939 &mut self,
17940 selections: Vec<Selection<Anchor>>,
17941 pending_selection: Option<Selection<Anchor>>,
17942 window: &mut Window,
17943 cx: &mut Context<Self>,
17944 ) {
17945 let old_cursor_position = self.selections.newest_anchor().head();
17946 self.selections.change_with(cx, |s| {
17947 s.select_anchors(selections);
17948 if let Some(pending_selection) = pending_selection {
17949 s.set_pending(pending_selection, SelectMode::Character);
17950 } else {
17951 s.clear_pending();
17952 }
17953 });
17954 self.selections_did_change(
17955 false,
17956 &old_cursor_position,
17957 SelectionEffects::default(),
17958 window,
17959 cx,
17960 );
17961 }
17962
17963 pub fn transact(
17964 &mut self,
17965 window: &mut Window,
17966 cx: &mut Context<Self>,
17967 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17968 ) -> Option<TransactionId> {
17969 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17970 this.start_transaction_at(Instant::now(), window, cx);
17971 update(this, window, cx);
17972 this.end_transaction_at(Instant::now(), cx)
17973 })
17974 }
17975
17976 pub fn start_transaction_at(
17977 &mut self,
17978 now: Instant,
17979 window: &mut Window,
17980 cx: &mut Context<Self>,
17981 ) -> Option<TransactionId> {
17982 self.end_selection(window, cx);
17983 if let Some(tx_id) = self
17984 .buffer
17985 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17986 {
17987 self.selection_history
17988 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17989 cx.emit(EditorEvent::TransactionBegun {
17990 transaction_id: tx_id,
17991 });
17992 Some(tx_id)
17993 } else {
17994 None
17995 }
17996 }
17997
17998 pub fn end_transaction_at(
17999 &mut self,
18000 now: Instant,
18001 cx: &mut Context<Self>,
18002 ) -> Option<TransactionId> {
18003 if let Some(transaction_id) = self
18004 .buffer
18005 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18006 {
18007 if let Some((_, end_selections)) =
18008 self.selection_history.transaction_mut(transaction_id)
18009 {
18010 *end_selections = Some(self.selections.disjoint_anchors_arc());
18011 } else {
18012 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18013 }
18014
18015 cx.emit(EditorEvent::Edited { transaction_id });
18016 Some(transaction_id)
18017 } else {
18018 None
18019 }
18020 }
18021
18022 pub fn modify_transaction_selection_history(
18023 &mut self,
18024 transaction_id: TransactionId,
18025 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18026 ) -> bool {
18027 self.selection_history
18028 .transaction_mut(transaction_id)
18029 .map(modify)
18030 .is_some()
18031 }
18032
18033 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18034 if self.selection_mark_mode {
18035 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18036 s.move_with(|_, sel| {
18037 sel.collapse_to(sel.head(), SelectionGoal::None);
18038 });
18039 })
18040 }
18041 self.selection_mark_mode = true;
18042 cx.notify();
18043 }
18044
18045 pub fn swap_selection_ends(
18046 &mut self,
18047 _: &actions::SwapSelectionEnds,
18048 window: &mut Window,
18049 cx: &mut Context<Self>,
18050 ) {
18051 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18052 s.move_with(|_, sel| {
18053 if sel.start != sel.end {
18054 sel.reversed = !sel.reversed
18055 }
18056 });
18057 });
18058 self.request_autoscroll(Autoscroll::newest(), cx);
18059 cx.notify();
18060 }
18061
18062 pub fn toggle_focus(
18063 workspace: &mut Workspace,
18064 _: &actions::ToggleFocus,
18065 window: &mut Window,
18066 cx: &mut Context<Workspace>,
18067 ) {
18068 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18069 return;
18070 };
18071 workspace.activate_item(&item, true, true, window, cx);
18072 }
18073
18074 pub fn toggle_fold(
18075 &mut self,
18076 _: &actions::ToggleFold,
18077 window: &mut Window,
18078 cx: &mut Context<Self>,
18079 ) {
18080 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18081 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18082 let selection = self.selections.newest::<Point>(&display_map);
18083
18084 let range = if selection.is_empty() {
18085 let point = selection.head().to_display_point(&display_map);
18086 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18087 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18088 .to_point(&display_map);
18089 start..end
18090 } else {
18091 selection.range()
18092 };
18093 if display_map.folds_in_range(range).next().is_some() {
18094 self.unfold_lines(&Default::default(), window, cx)
18095 } else {
18096 self.fold(&Default::default(), window, cx)
18097 }
18098 } else {
18099 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18100 let buffer_ids: HashSet<_> = self
18101 .selections
18102 .disjoint_anchor_ranges()
18103 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18104 .collect();
18105
18106 let should_unfold = buffer_ids
18107 .iter()
18108 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18109
18110 for buffer_id in buffer_ids {
18111 if should_unfold {
18112 self.unfold_buffer(buffer_id, cx);
18113 } else {
18114 self.fold_buffer(buffer_id, cx);
18115 }
18116 }
18117 }
18118 }
18119
18120 pub fn toggle_fold_recursive(
18121 &mut self,
18122 _: &actions::ToggleFoldRecursive,
18123 window: &mut Window,
18124 cx: &mut Context<Self>,
18125 ) {
18126 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18127
18128 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18129 let range = if selection.is_empty() {
18130 let point = selection.head().to_display_point(&display_map);
18131 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18132 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18133 .to_point(&display_map);
18134 start..end
18135 } else {
18136 selection.range()
18137 };
18138 if display_map.folds_in_range(range).next().is_some() {
18139 self.unfold_recursive(&Default::default(), window, cx)
18140 } else {
18141 self.fold_recursive(&Default::default(), window, cx)
18142 }
18143 }
18144
18145 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18146 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18147 let mut to_fold = Vec::new();
18148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18149 let selections = self.selections.all_adjusted(&display_map);
18150
18151 for selection in selections {
18152 let range = selection.range().sorted();
18153 let buffer_start_row = range.start.row;
18154
18155 if range.start.row != range.end.row {
18156 let mut found = false;
18157 let mut row = range.start.row;
18158 while row <= range.end.row {
18159 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18160 {
18161 found = true;
18162 row = crease.range().end.row + 1;
18163 to_fold.push(crease);
18164 } else {
18165 row += 1
18166 }
18167 }
18168 if found {
18169 continue;
18170 }
18171 }
18172
18173 for row in (0..=range.start.row).rev() {
18174 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18175 && crease.range().end.row >= buffer_start_row
18176 {
18177 to_fold.push(crease);
18178 if row <= range.start.row {
18179 break;
18180 }
18181 }
18182 }
18183 }
18184
18185 self.fold_creases(to_fold, true, window, cx);
18186 } else {
18187 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18188 let buffer_ids = self
18189 .selections
18190 .disjoint_anchor_ranges()
18191 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18192 .collect::<HashSet<_>>();
18193 for buffer_id in buffer_ids {
18194 self.fold_buffer(buffer_id, cx);
18195 }
18196 }
18197 }
18198
18199 pub fn toggle_fold_all(
18200 &mut self,
18201 _: &actions::ToggleFoldAll,
18202 window: &mut Window,
18203 cx: &mut Context<Self>,
18204 ) {
18205 if self.buffer.read(cx).is_singleton() {
18206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18207 let has_folds = display_map
18208 .folds_in_range(0..display_map.buffer_snapshot().len())
18209 .next()
18210 .is_some();
18211
18212 if has_folds {
18213 self.unfold_all(&actions::UnfoldAll, window, cx);
18214 } else {
18215 self.fold_all(&actions::FoldAll, window, cx);
18216 }
18217 } else {
18218 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18219 let should_unfold = buffer_ids
18220 .iter()
18221 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18222
18223 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18224 editor
18225 .update_in(cx, |editor, _, cx| {
18226 for buffer_id in buffer_ids {
18227 if should_unfold {
18228 editor.unfold_buffer(buffer_id, cx);
18229 } else {
18230 editor.fold_buffer(buffer_id, cx);
18231 }
18232 }
18233 })
18234 .ok();
18235 });
18236 }
18237 }
18238
18239 fn fold_at_level(
18240 &mut self,
18241 fold_at: &FoldAtLevel,
18242 window: &mut Window,
18243 cx: &mut Context<Self>,
18244 ) {
18245 if !self.buffer.read(cx).is_singleton() {
18246 return;
18247 }
18248
18249 let fold_at_level = fold_at.0;
18250 let snapshot = self.buffer.read(cx).snapshot(cx);
18251 let mut to_fold = Vec::new();
18252 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18253
18254 let row_ranges_to_keep: Vec<Range<u32>> = self
18255 .selections
18256 .all::<Point>(&self.display_snapshot(cx))
18257 .into_iter()
18258 .map(|sel| sel.start.row..sel.end.row)
18259 .collect();
18260
18261 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18262 while start_row < end_row {
18263 match self
18264 .snapshot(window, cx)
18265 .crease_for_buffer_row(MultiBufferRow(start_row))
18266 {
18267 Some(crease) => {
18268 let nested_start_row = crease.range().start.row + 1;
18269 let nested_end_row = crease.range().end.row;
18270
18271 if current_level < fold_at_level {
18272 stack.push((nested_start_row, nested_end_row, current_level + 1));
18273 } else if current_level == fold_at_level {
18274 // Fold iff there is no selection completely contained within the fold region
18275 if !row_ranges_to_keep.iter().any(|selection| {
18276 selection.end >= nested_start_row
18277 && selection.start <= nested_end_row
18278 }) {
18279 to_fold.push(crease);
18280 }
18281 }
18282
18283 start_row = nested_end_row + 1;
18284 }
18285 None => start_row += 1,
18286 }
18287 }
18288 }
18289
18290 self.fold_creases(to_fold, true, window, cx);
18291 }
18292
18293 pub fn fold_at_level_1(
18294 &mut self,
18295 _: &actions::FoldAtLevel1,
18296 window: &mut Window,
18297 cx: &mut Context<Self>,
18298 ) {
18299 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18300 }
18301
18302 pub fn fold_at_level_2(
18303 &mut self,
18304 _: &actions::FoldAtLevel2,
18305 window: &mut Window,
18306 cx: &mut Context<Self>,
18307 ) {
18308 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18309 }
18310
18311 pub fn fold_at_level_3(
18312 &mut self,
18313 _: &actions::FoldAtLevel3,
18314 window: &mut Window,
18315 cx: &mut Context<Self>,
18316 ) {
18317 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18318 }
18319
18320 pub fn fold_at_level_4(
18321 &mut self,
18322 _: &actions::FoldAtLevel4,
18323 window: &mut Window,
18324 cx: &mut Context<Self>,
18325 ) {
18326 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18327 }
18328
18329 pub fn fold_at_level_5(
18330 &mut self,
18331 _: &actions::FoldAtLevel5,
18332 window: &mut Window,
18333 cx: &mut Context<Self>,
18334 ) {
18335 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18336 }
18337
18338 pub fn fold_at_level_6(
18339 &mut self,
18340 _: &actions::FoldAtLevel6,
18341 window: &mut Window,
18342 cx: &mut Context<Self>,
18343 ) {
18344 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18345 }
18346
18347 pub fn fold_at_level_7(
18348 &mut self,
18349 _: &actions::FoldAtLevel7,
18350 window: &mut Window,
18351 cx: &mut Context<Self>,
18352 ) {
18353 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18354 }
18355
18356 pub fn fold_at_level_8(
18357 &mut self,
18358 _: &actions::FoldAtLevel8,
18359 window: &mut Window,
18360 cx: &mut Context<Self>,
18361 ) {
18362 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18363 }
18364
18365 pub fn fold_at_level_9(
18366 &mut self,
18367 _: &actions::FoldAtLevel9,
18368 window: &mut Window,
18369 cx: &mut Context<Self>,
18370 ) {
18371 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18372 }
18373
18374 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18375 if self.buffer.read(cx).is_singleton() {
18376 let mut fold_ranges = Vec::new();
18377 let snapshot = self.buffer.read(cx).snapshot(cx);
18378
18379 for row in 0..snapshot.max_row().0 {
18380 if let Some(foldable_range) = self
18381 .snapshot(window, cx)
18382 .crease_for_buffer_row(MultiBufferRow(row))
18383 {
18384 fold_ranges.push(foldable_range);
18385 }
18386 }
18387
18388 self.fold_creases(fold_ranges, true, window, cx);
18389 } else {
18390 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18391 editor
18392 .update_in(cx, |editor, _, cx| {
18393 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18394 editor.fold_buffer(buffer_id, cx);
18395 }
18396 })
18397 .ok();
18398 });
18399 }
18400 }
18401
18402 pub fn fold_function_bodies(
18403 &mut self,
18404 _: &actions::FoldFunctionBodies,
18405 window: &mut Window,
18406 cx: &mut Context<Self>,
18407 ) {
18408 let snapshot = self.buffer.read(cx).snapshot(cx);
18409
18410 let ranges = snapshot
18411 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18412 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18413 .collect::<Vec<_>>();
18414
18415 let creases = ranges
18416 .into_iter()
18417 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18418 .collect();
18419
18420 self.fold_creases(creases, true, window, cx);
18421 }
18422
18423 pub fn fold_recursive(
18424 &mut self,
18425 _: &actions::FoldRecursive,
18426 window: &mut Window,
18427 cx: &mut Context<Self>,
18428 ) {
18429 let mut to_fold = Vec::new();
18430 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18431 let selections = self.selections.all_adjusted(&display_map);
18432
18433 for selection in selections {
18434 let range = selection.range().sorted();
18435 let buffer_start_row = range.start.row;
18436
18437 if range.start.row != range.end.row {
18438 let mut found = false;
18439 for row in range.start.row..=range.end.row {
18440 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18441 found = true;
18442 to_fold.push(crease);
18443 }
18444 }
18445 if found {
18446 continue;
18447 }
18448 }
18449
18450 for row in (0..=range.start.row).rev() {
18451 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18452 if crease.range().end.row >= buffer_start_row {
18453 to_fold.push(crease);
18454 } else {
18455 break;
18456 }
18457 }
18458 }
18459 }
18460
18461 self.fold_creases(to_fold, true, window, cx);
18462 }
18463
18464 pub fn fold_at(
18465 &mut self,
18466 buffer_row: MultiBufferRow,
18467 window: &mut Window,
18468 cx: &mut Context<Self>,
18469 ) {
18470 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18471
18472 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18473 let autoscroll = self
18474 .selections
18475 .all::<Point>(&display_map)
18476 .iter()
18477 .any(|selection| crease.range().overlaps(&selection.range()));
18478
18479 self.fold_creases(vec![crease], autoscroll, window, cx);
18480 }
18481 }
18482
18483 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18484 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18485 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18486 let buffer = display_map.buffer_snapshot();
18487 let selections = self.selections.all::<Point>(&display_map);
18488 let ranges = selections
18489 .iter()
18490 .map(|s| {
18491 let range = s.display_range(&display_map).sorted();
18492 let mut start = range.start.to_point(&display_map);
18493 let mut end = range.end.to_point(&display_map);
18494 start.column = 0;
18495 end.column = buffer.line_len(MultiBufferRow(end.row));
18496 start..end
18497 })
18498 .collect::<Vec<_>>();
18499
18500 self.unfold_ranges(&ranges, true, true, cx);
18501 } else {
18502 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18503 let buffer_ids = self
18504 .selections
18505 .disjoint_anchor_ranges()
18506 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18507 .collect::<HashSet<_>>();
18508 for buffer_id in buffer_ids {
18509 self.unfold_buffer(buffer_id, cx);
18510 }
18511 }
18512 }
18513
18514 pub fn unfold_recursive(
18515 &mut self,
18516 _: &UnfoldRecursive,
18517 _window: &mut Window,
18518 cx: &mut Context<Self>,
18519 ) {
18520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18521 let selections = self.selections.all::<Point>(&display_map);
18522 let ranges = selections
18523 .iter()
18524 .map(|s| {
18525 let mut range = s.display_range(&display_map).sorted();
18526 *range.start.column_mut() = 0;
18527 *range.end.column_mut() = display_map.line_len(range.end.row());
18528 let start = range.start.to_point(&display_map);
18529 let end = range.end.to_point(&display_map);
18530 start..end
18531 })
18532 .collect::<Vec<_>>();
18533
18534 self.unfold_ranges(&ranges, true, true, cx);
18535 }
18536
18537 pub fn unfold_at(
18538 &mut self,
18539 buffer_row: MultiBufferRow,
18540 _window: &mut Window,
18541 cx: &mut Context<Self>,
18542 ) {
18543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18544
18545 let intersection_range = Point::new(buffer_row.0, 0)
18546 ..Point::new(
18547 buffer_row.0,
18548 display_map.buffer_snapshot().line_len(buffer_row),
18549 );
18550
18551 let autoscroll = self
18552 .selections
18553 .all::<Point>(&display_map)
18554 .iter()
18555 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18556
18557 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18558 }
18559
18560 pub fn unfold_all(
18561 &mut self,
18562 _: &actions::UnfoldAll,
18563 _window: &mut Window,
18564 cx: &mut Context<Self>,
18565 ) {
18566 if self.buffer.read(cx).is_singleton() {
18567 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18568 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18569 } else {
18570 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18571 editor
18572 .update(cx, |editor, cx| {
18573 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18574 editor.unfold_buffer(buffer_id, cx);
18575 }
18576 })
18577 .ok();
18578 });
18579 }
18580 }
18581
18582 pub fn fold_selected_ranges(
18583 &mut self,
18584 _: &FoldSelectedRanges,
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 selections = self.selections.all_adjusted(&display_map);
18590 let ranges = selections
18591 .into_iter()
18592 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18593 .collect::<Vec<_>>();
18594 self.fold_creases(ranges, true, window, cx);
18595 }
18596
18597 pub fn fold_ranges<T: ToOffset + Clone>(
18598 &mut self,
18599 ranges: Vec<Range<T>>,
18600 auto_scroll: bool,
18601 window: &mut Window,
18602 cx: &mut Context<Self>,
18603 ) {
18604 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18605 let ranges = ranges
18606 .into_iter()
18607 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18608 .collect::<Vec<_>>();
18609 self.fold_creases(ranges, auto_scroll, window, cx);
18610 }
18611
18612 pub fn fold_creases<T: ToOffset + Clone>(
18613 &mut self,
18614 creases: Vec<Crease<T>>,
18615 auto_scroll: bool,
18616 _window: &mut Window,
18617 cx: &mut Context<Self>,
18618 ) {
18619 if creases.is_empty() {
18620 return;
18621 }
18622
18623 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18624
18625 if auto_scroll {
18626 self.request_autoscroll(Autoscroll::fit(), cx);
18627 }
18628
18629 cx.notify();
18630
18631 self.scrollbar_marker_state.dirty = true;
18632 self.folds_did_change(cx);
18633 }
18634
18635 /// Removes any folds whose ranges intersect any of the given ranges.
18636 pub fn unfold_ranges<T: ToOffset + Clone>(
18637 &mut self,
18638 ranges: &[Range<T>],
18639 inclusive: bool,
18640 auto_scroll: bool,
18641 cx: &mut Context<Self>,
18642 ) {
18643 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18644 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18645 });
18646 self.folds_did_change(cx);
18647 }
18648
18649 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18650 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18651 return;
18652 }
18653 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18654 self.display_map.update(cx, |display_map, cx| {
18655 display_map.fold_buffers([buffer_id], cx)
18656 });
18657 cx.emit(EditorEvent::BufferFoldToggled {
18658 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18659 folded: true,
18660 });
18661 cx.notify();
18662 }
18663
18664 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18665 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18666 return;
18667 }
18668 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18669 self.display_map.update(cx, |display_map, cx| {
18670 display_map.unfold_buffers([buffer_id], cx);
18671 });
18672 cx.emit(EditorEvent::BufferFoldToggled {
18673 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18674 folded: false,
18675 });
18676 cx.notify();
18677 }
18678
18679 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18680 self.display_map.read(cx).is_buffer_folded(buffer)
18681 }
18682
18683 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18684 self.display_map.read(cx).folded_buffers()
18685 }
18686
18687 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18688 self.display_map.update(cx, |display_map, cx| {
18689 display_map.disable_header_for_buffer(buffer_id, cx);
18690 });
18691 cx.notify();
18692 }
18693
18694 /// Removes any folds with the given ranges.
18695 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18696 &mut self,
18697 ranges: &[Range<T>],
18698 type_id: TypeId,
18699 auto_scroll: bool,
18700 cx: &mut Context<Self>,
18701 ) {
18702 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18703 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18704 });
18705 self.folds_did_change(cx);
18706 }
18707
18708 fn remove_folds_with<T: ToOffset + Clone>(
18709 &mut self,
18710 ranges: &[Range<T>],
18711 auto_scroll: bool,
18712 cx: &mut Context<Self>,
18713 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18714 ) {
18715 if ranges.is_empty() {
18716 return;
18717 }
18718
18719 let mut buffers_affected = HashSet::default();
18720 let multi_buffer = self.buffer().read(cx);
18721 for range in ranges {
18722 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18723 buffers_affected.insert(buffer.read(cx).remote_id());
18724 };
18725 }
18726
18727 self.display_map.update(cx, update);
18728
18729 if auto_scroll {
18730 self.request_autoscroll(Autoscroll::fit(), cx);
18731 }
18732
18733 cx.notify();
18734 self.scrollbar_marker_state.dirty = true;
18735 self.active_indent_guides_state.dirty = true;
18736 }
18737
18738 pub fn update_renderer_widths(
18739 &mut self,
18740 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18741 cx: &mut Context<Self>,
18742 ) -> bool {
18743 self.display_map
18744 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18745 }
18746
18747 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18748 self.display_map.read(cx).fold_placeholder.clone()
18749 }
18750
18751 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18752 self.buffer.update(cx, |buffer, cx| {
18753 buffer.set_all_diff_hunks_expanded(cx);
18754 });
18755 }
18756
18757 pub fn expand_all_diff_hunks(
18758 &mut self,
18759 _: &ExpandAllDiffHunks,
18760 _window: &mut Window,
18761 cx: &mut Context<Self>,
18762 ) {
18763 self.buffer.update(cx, |buffer, cx| {
18764 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18765 });
18766 }
18767
18768 pub fn collapse_all_diff_hunks(
18769 &mut self,
18770 _: &CollapseAllDiffHunks,
18771 _window: &mut Window,
18772 cx: &mut Context<Self>,
18773 ) {
18774 self.buffer.update(cx, |buffer, cx| {
18775 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18776 });
18777 }
18778
18779 pub fn toggle_selected_diff_hunks(
18780 &mut self,
18781 _: &ToggleSelectedDiffHunks,
18782 _window: &mut Window,
18783 cx: &mut Context<Self>,
18784 ) {
18785 let ranges: Vec<_> = self
18786 .selections
18787 .disjoint_anchors()
18788 .iter()
18789 .map(|s| s.range())
18790 .collect();
18791 self.toggle_diff_hunks_in_ranges(ranges, cx);
18792 }
18793
18794 pub fn diff_hunks_in_ranges<'a>(
18795 &'a self,
18796 ranges: &'a [Range<Anchor>],
18797 buffer: &'a MultiBufferSnapshot,
18798 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18799 ranges.iter().flat_map(move |range| {
18800 let end_excerpt_id = range.end.excerpt_id;
18801 let range = range.to_point(buffer);
18802 let mut peek_end = range.end;
18803 if range.end.row < buffer.max_row().0 {
18804 peek_end = Point::new(range.end.row + 1, 0);
18805 }
18806 buffer
18807 .diff_hunks_in_range(range.start..peek_end)
18808 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18809 })
18810 }
18811
18812 pub fn has_stageable_diff_hunks_in_ranges(
18813 &self,
18814 ranges: &[Range<Anchor>],
18815 snapshot: &MultiBufferSnapshot,
18816 ) -> bool {
18817 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18818 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18819 }
18820
18821 pub fn toggle_staged_selected_diff_hunks(
18822 &mut self,
18823 _: &::git::ToggleStaged,
18824 _: &mut Window,
18825 cx: &mut Context<Self>,
18826 ) {
18827 let snapshot = self.buffer.read(cx).snapshot(cx);
18828 let ranges: Vec<_> = self
18829 .selections
18830 .disjoint_anchors()
18831 .iter()
18832 .map(|s| s.range())
18833 .collect();
18834 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18835 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18836 }
18837
18838 pub fn set_render_diff_hunk_controls(
18839 &mut self,
18840 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18841 cx: &mut Context<Self>,
18842 ) {
18843 self.render_diff_hunk_controls = render_diff_hunk_controls;
18844 cx.notify();
18845 }
18846
18847 pub fn stage_and_next(
18848 &mut self,
18849 _: &::git::StageAndNext,
18850 window: &mut Window,
18851 cx: &mut Context<Self>,
18852 ) {
18853 self.do_stage_or_unstage_and_next(true, window, cx);
18854 }
18855
18856 pub fn unstage_and_next(
18857 &mut self,
18858 _: &::git::UnstageAndNext,
18859 window: &mut Window,
18860 cx: &mut Context<Self>,
18861 ) {
18862 self.do_stage_or_unstage_and_next(false, window, cx);
18863 }
18864
18865 pub fn stage_or_unstage_diff_hunks(
18866 &mut self,
18867 stage: bool,
18868 ranges: Vec<Range<Anchor>>,
18869 cx: &mut Context<Self>,
18870 ) {
18871 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18872 cx.spawn(async move |this, cx| {
18873 task.await?;
18874 this.update(cx, |this, cx| {
18875 let snapshot = this.buffer.read(cx).snapshot(cx);
18876 let chunk_by = this
18877 .diff_hunks_in_ranges(&ranges, &snapshot)
18878 .chunk_by(|hunk| hunk.buffer_id);
18879 for (buffer_id, hunks) in &chunk_by {
18880 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18881 }
18882 })
18883 })
18884 .detach_and_log_err(cx);
18885 }
18886
18887 fn save_buffers_for_ranges_if_needed(
18888 &mut self,
18889 ranges: &[Range<Anchor>],
18890 cx: &mut Context<Editor>,
18891 ) -> Task<Result<()>> {
18892 let multibuffer = self.buffer.read(cx);
18893 let snapshot = multibuffer.read(cx);
18894 let buffer_ids: HashSet<_> = ranges
18895 .iter()
18896 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18897 .collect();
18898 drop(snapshot);
18899
18900 let mut buffers = HashSet::default();
18901 for buffer_id in buffer_ids {
18902 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18903 let buffer = buffer_entity.read(cx);
18904 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18905 {
18906 buffers.insert(buffer_entity);
18907 }
18908 }
18909 }
18910
18911 if let Some(project) = &self.project {
18912 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18913 } else {
18914 Task::ready(Ok(()))
18915 }
18916 }
18917
18918 fn do_stage_or_unstage_and_next(
18919 &mut self,
18920 stage: bool,
18921 window: &mut Window,
18922 cx: &mut Context<Self>,
18923 ) {
18924 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18925
18926 if ranges.iter().any(|range| range.start != range.end) {
18927 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18928 return;
18929 }
18930
18931 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18932 let snapshot = self.snapshot(window, cx);
18933 let position = self
18934 .selections
18935 .newest::<Point>(&snapshot.display_snapshot)
18936 .head();
18937 let mut row = snapshot
18938 .buffer_snapshot()
18939 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18940 .find(|hunk| hunk.row_range.start.0 > position.row)
18941 .map(|hunk| hunk.row_range.start);
18942
18943 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18944 // Outside of the project diff editor, wrap around to the beginning.
18945 if !all_diff_hunks_expanded {
18946 row = row.or_else(|| {
18947 snapshot
18948 .buffer_snapshot()
18949 .diff_hunks_in_range(Point::zero()..position)
18950 .find(|hunk| hunk.row_range.end.0 < position.row)
18951 .map(|hunk| hunk.row_range.start)
18952 });
18953 }
18954
18955 if let Some(row) = row {
18956 let destination = Point::new(row.0, 0);
18957 let autoscroll = Autoscroll::center();
18958
18959 self.unfold_ranges(&[destination..destination], false, false, cx);
18960 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18961 s.select_ranges([destination..destination]);
18962 });
18963 }
18964 }
18965
18966 fn do_stage_or_unstage(
18967 &self,
18968 stage: bool,
18969 buffer_id: BufferId,
18970 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18971 cx: &mut App,
18972 ) -> Option<()> {
18973 let project = self.project()?;
18974 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18975 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18976 let buffer_snapshot = buffer.read(cx).snapshot();
18977 let file_exists = buffer_snapshot
18978 .file()
18979 .is_some_and(|file| file.disk_state().exists());
18980 diff.update(cx, |diff, cx| {
18981 diff.stage_or_unstage_hunks(
18982 stage,
18983 &hunks
18984 .map(|hunk| buffer_diff::DiffHunk {
18985 buffer_range: hunk.buffer_range,
18986 diff_base_byte_range: hunk.diff_base_byte_range,
18987 secondary_status: hunk.secondary_status,
18988 range: Point::zero()..Point::zero(), // unused
18989 })
18990 .collect::<Vec<_>>(),
18991 &buffer_snapshot,
18992 file_exists,
18993 cx,
18994 )
18995 });
18996 None
18997 }
18998
18999 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19000 let ranges: Vec<_> = self
19001 .selections
19002 .disjoint_anchors()
19003 .iter()
19004 .map(|s| s.range())
19005 .collect();
19006 self.buffer
19007 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19008 }
19009
19010 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19011 self.buffer.update(cx, |buffer, cx| {
19012 let ranges = vec![Anchor::min()..Anchor::max()];
19013 if !buffer.all_diff_hunks_expanded()
19014 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19015 {
19016 buffer.collapse_diff_hunks(ranges, cx);
19017 true
19018 } else {
19019 false
19020 }
19021 })
19022 }
19023
19024 fn toggle_diff_hunks_in_ranges(
19025 &mut self,
19026 ranges: Vec<Range<Anchor>>,
19027 cx: &mut Context<Editor>,
19028 ) {
19029 self.buffer.update(cx, |buffer, cx| {
19030 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19031 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19032 })
19033 }
19034
19035 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19036 self.buffer.update(cx, |buffer, cx| {
19037 let snapshot = buffer.snapshot(cx);
19038 let excerpt_id = range.end.excerpt_id;
19039 let point_range = range.to_point(&snapshot);
19040 let expand = !buffer.single_hunk_is_expanded(range, cx);
19041 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19042 })
19043 }
19044
19045 pub(crate) fn apply_all_diff_hunks(
19046 &mut self,
19047 _: &ApplyAllDiffHunks,
19048 window: &mut Window,
19049 cx: &mut Context<Self>,
19050 ) {
19051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19052
19053 let buffers = self.buffer.read(cx).all_buffers();
19054 for branch_buffer in buffers {
19055 branch_buffer.update(cx, |branch_buffer, cx| {
19056 branch_buffer.merge_into_base(Vec::new(), cx);
19057 });
19058 }
19059
19060 if let Some(project) = self.project.clone() {
19061 self.save(
19062 SaveOptions {
19063 format: true,
19064 autosave: false,
19065 },
19066 project,
19067 window,
19068 cx,
19069 )
19070 .detach_and_log_err(cx);
19071 }
19072 }
19073
19074 pub(crate) fn apply_selected_diff_hunks(
19075 &mut self,
19076 _: &ApplyDiffHunk,
19077 window: &mut Window,
19078 cx: &mut Context<Self>,
19079 ) {
19080 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19081 let snapshot = self.snapshot(window, cx);
19082 let hunks = snapshot.hunks_for_ranges(
19083 self.selections
19084 .all(&snapshot.display_snapshot)
19085 .into_iter()
19086 .map(|selection| selection.range()),
19087 );
19088 let mut ranges_by_buffer = HashMap::default();
19089 self.transact(window, cx, |editor, _window, cx| {
19090 for hunk in hunks {
19091 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19092 ranges_by_buffer
19093 .entry(buffer.clone())
19094 .or_insert_with(Vec::new)
19095 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19096 }
19097 }
19098
19099 for (buffer, ranges) in ranges_by_buffer {
19100 buffer.update(cx, |buffer, cx| {
19101 buffer.merge_into_base(ranges, cx);
19102 });
19103 }
19104 });
19105
19106 if let Some(project) = self.project.clone() {
19107 self.save(
19108 SaveOptions {
19109 format: true,
19110 autosave: false,
19111 },
19112 project,
19113 window,
19114 cx,
19115 )
19116 .detach_and_log_err(cx);
19117 }
19118 }
19119
19120 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19121 if hovered != self.gutter_hovered {
19122 self.gutter_hovered = hovered;
19123 cx.notify();
19124 }
19125 }
19126
19127 pub fn insert_blocks(
19128 &mut self,
19129 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19130 autoscroll: Option<Autoscroll>,
19131 cx: &mut Context<Self>,
19132 ) -> Vec<CustomBlockId> {
19133 let blocks = self
19134 .display_map
19135 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19136 if let Some(autoscroll) = autoscroll {
19137 self.request_autoscroll(autoscroll, cx);
19138 }
19139 cx.notify();
19140 blocks
19141 }
19142
19143 pub fn resize_blocks(
19144 &mut self,
19145 heights: HashMap<CustomBlockId, u32>,
19146 autoscroll: Option<Autoscroll>,
19147 cx: &mut Context<Self>,
19148 ) {
19149 self.display_map
19150 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19151 if let Some(autoscroll) = autoscroll {
19152 self.request_autoscroll(autoscroll, cx);
19153 }
19154 cx.notify();
19155 }
19156
19157 pub fn replace_blocks(
19158 &mut self,
19159 renderers: HashMap<CustomBlockId, RenderBlock>,
19160 autoscroll: Option<Autoscroll>,
19161 cx: &mut Context<Self>,
19162 ) {
19163 self.display_map
19164 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19165 if let Some(autoscroll) = autoscroll {
19166 self.request_autoscroll(autoscroll, cx);
19167 }
19168 cx.notify();
19169 }
19170
19171 pub fn remove_blocks(
19172 &mut self,
19173 block_ids: HashSet<CustomBlockId>,
19174 autoscroll: Option<Autoscroll>,
19175 cx: &mut Context<Self>,
19176 ) {
19177 self.display_map.update(cx, |display_map, cx| {
19178 display_map.remove_blocks(block_ids, cx)
19179 });
19180 if let Some(autoscroll) = autoscroll {
19181 self.request_autoscroll(autoscroll, cx);
19182 }
19183 cx.notify();
19184 }
19185
19186 pub fn row_for_block(
19187 &self,
19188 block_id: CustomBlockId,
19189 cx: &mut Context<Self>,
19190 ) -> Option<DisplayRow> {
19191 self.display_map
19192 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19193 }
19194
19195 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19196 self.focused_block = Some(focused_block);
19197 }
19198
19199 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19200 self.focused_block.take()
19201 }
19202
19203 pub fn insert_creases(
19204 &mut self,
19205 creases: impl IntoIterator<Item = Crease<Anchor>>,
19206 cx: &mut Context<Self>,
19207 ) -> Vec<CreaseId> {
19208 self.display_map
19209 .update(cx, |map, cx| map.insert_creases(creases, cx))
19210 }
19211
19212 pub fn remove_creases(
19213 &mut self,
19214 ids: impl IntoIterator<Item = CreaseId>,
19215 cx: &mut Context<Self>,
19216 ) -> Vec<(CreaseId, Range<Anchor>)> {
19217 self.display_map
19218 .update(cx, |map, cx| map.remove_creases(ids, cx))
19219 }
19220
19221 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19222 self.display_map
19223 .update(cx, |map, cx| map.snapshot(cx))
19224 .longest_row()
19225 }
19226
19227 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19228 self.display_map
19229 .update(cx, |map, cx| map.snapshot(cx))
19230 .max_point()
19231 }
19232
19233 pub fn text(&self, cx: &App) -> String {
19234 self.buffer.read(cx).read(cx).text()
19235 }
19236
19237 pub fn is_empty(&self, cx: &App) -> bool {
19238 self.buffer.read(cx).read(cx).is_empty()
19239 }
19240
19241 pub fn text_option(&self, cx: &App) -> Option<String> {
19242 let text = self.text(cx);
19243 let text = text.trim();
19244
19245 if text.is_empty() {
19246 return None;
19247 }
19248
19249 Some(text.to_string())
19250 }
19251
19252 pub fn set_text(
19253 &mut self,
19254 text: impl Into<Arc<str>>,
19255 window: &mut Window,
19256 cx: &mut Context<Self>,
19257 ) {
19258 self.transact(window, cx, |this, _, cx| {
19259 this.buffer
19260 .read(cx)
19261 .as_singleton()
19262 .expect("you can only call set_text on editors for singleton buffers")
19263 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19264 });
19265 }
19266
19267 pub fn display_text(&self, cx: &mut App) -> String {
19268 self.display_map
19269 .update(cx, |map, cx| map.snapshot(cx))
19270 .text()
19271 }
19272
19273 fn create_minimap(
19274 &self,
19275 minimap_settings: MinimapSettings,
19276 window: &mut Window,
19277 cx: &mut Context<Self>,
19278 ) -> Option<Entity<Self>> {
19279 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19280 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19281 }
19282
19283 fn initialize_new_minimap(
19284 &self,
19285 minimap_settings: MinimapSettings,
19286 window: &mut Window,
19287 cx: &mut Context<Self>,
19288 ) -> Entity<Self> {
19289 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19290
19291 let mut minimap = Editor::new_internal(
19292 EditorMode::Minimap {
19293 parent: cx.weak_entity(),
19294 },
19295 self.buffer.clone(),
19296 None,
19297 Some(self.display_map.clone()),
19298 window,
19299 cx,
19300 );
19301 minimap.scroll_manager.clone_state(&self.scroll_manager);
19302 minimap.set_text_style_refinement(TextStyleRefinement {
19303 font_size: Some(MINIMAP_FONT_SIZE),
19304 font_weight: Some(MINIMAP_FONT_WEIGHT),
19305 ..Default::default()
19306 });
19307 minimap.update_minimap_configuration(minimap_settings, cx);
19308 cx.new(|_| minimap)
19309 }
19310
19311 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19312 let current_line_highlight = minimap_settings
19313 .current_line_highlight
19314 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19315 self.set_current_line_highlight(Some(current_line_highlight));
19316 }
19317
19318 pub fn minimap(&self) -> Option<&Entity<Self>> {
19319 self.minimap
19320 .as_ref()
19321 .filter(|_| self.minimap_visibility.visible())
19322 }
19323
19324 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19325 let mut wrap_guides = smallvec![];
19326
19327 if self.show_wrap_guides == Some(false) {
19328 return wrap_guides;
19329 }
19330
19331 let settings = self.buffer.read(cx).language_settings(cx);
19332 if settings.show_wrap_guides {
19333 match self.soft_wrap_mode(cx) {
19334 SoftWrap::Column(soft_wrap) => {
19335 wrap_guides.push((soft_wrap as usize, true));
19336 }
19337 SoftWrap::Bounded(soft_wrap) => {
19338 wrap_guides.push((soft_wrap as usize, true));
19339 }
19340 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19341 }
19342 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19343 }
19344
19345 wrap_guides
19346 }
19347
19348 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19349 let settings = self.buffer.read(cx).language_settings(cx);
19350 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19351 match mode {
19352 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19353 SoftWrap::None
19354 }
19355 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19356 language_settings::SoftWrap::PreferredLineLength => {
19357 SoftWrap::Column(settings.preferred_line_length)
19358 }
19359 language_settings::SoftWrap::Bounded => {
19360 SoftWrap::Bounded(settings.preferred_line_length)
19361 }
19362 }
19363 }
19364
19365 pub fn set_soft_wrap_mode(
19366 &mut self,
19367 mode: language_settings::SoftWrap,
19368
19369 cx: &mut Context<Self>,
19370 ) {
19371 self.soft_wrap_mode_override = Some(mode);
19372 cx.notify();
19373 }
19374
19375 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19376 self.hard_wrap = hard_wrap;
19377 cx.notify();
19378 }
19379
19380 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19381 self.text_style_refinement = Some(style);
19382 }
19383
19384 /// called by the Element so we know what style we were most recently rendered with.
19385 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19386 // We intentionally do not inform the display map about the minimap style
19387 // so that wrapping is not recalculated and stays consistent for the editor
19388 // and its linked minimap.
19389 if !self.mode.is_minimap() {
19390 let font = style.text.font();
19391 let font_size = style.text.font_size.to_pixels(window.rem_size());
19392 let display_map = self
19393 .placeholder_display_map
19394 .as_ref()
19395 .filter(|_| self.is_empty(cx))
19396 .unwrap_or(&self.display_map);
19397
19398 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19399 }
19400 self.style = Some(style);
19401 }
19402
19403 pub fn style(&self) -> Option<&EditorStyle> {
19404 self.style.as_ref()
19405 }
19406
19407 // Called by the element. This method is not designed to be called outside of the editor
19408 // element's layout code because it does not notify when rewrapping is computed synchronously.
19409 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19410 if self.is_empty(cx) {
19411 self.placeholder_display_map
19412 .as_ref()
19413 .map_or(false, |display_map| {
19414 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19415 })
19416 } else {
19417 self.display_map
19418 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19419 }
19420 }
19421
19422 pub fn set_soft_wrap(&mut self) {
19423 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19424 }
19425
19426 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19427 if self.soft_wrap_mode_override.is_some() {
19428 self.soft_wrap_mode_override.take();
19429 } else {
19430 let soft_wrap = match self.soft_wrap_mode(cx) {
19431 SoftWrap::GitDiff => return,
19432 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19433 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19434 language_settings::SoftWrap::None
19435 }
19436 };
19437 self.soft_wrap_mode_override = Some(soft_wrap);
19438 }
19439 cx.notify();
19440 }
19441
19442 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19443 let Some(workspace) = self.workspace() else {
19444 return;
19445 };
19446 let fs = workspace.read(cx).app_state().fs.clone();
19447 let current_show = TabBarSettings::get_global(cx).show;
19448 update_settings_file(fs, cx, move |setting, _| {
19449 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19450 });
19451 }
19452
19453 pub fn toggle_indent_guides(
19454 &mut self,
19455 _: &ToggleIndentGuides,
19456 _: &mut Window,
19457 cx: &mut Context<Self>,
19458 ) {
19459 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19460 self.buffer
19461 .read(cx)
19462 .language_settings(cx)
19463 .indent_guides
19464 .enabled
19465 });
19466 self.show_indent_guides = Some(!currently_enabled);
19467 cx.notify();
19468 }
19469
19470 fn should_show_indent_guides(&self) -> Option<bool> {
19471 self.show_indent_guides
19472 }
19473
19474 pub fn toggle_line_numbers(
19475 &mut self,
19476 _: &ToggleLineNumbers,
19477 _: &mut Window,
19478 cx: &mut Context<Self>,
19479 ) {
19480 let mut editor_settings = EditorSettings::get_global(cx).clone();
19481 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19482 EditorSettings::override_global(editor_settings, cx);
19483 }
19484
19485 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19486 if let Some(show_line_numbers) = self.show_line_numbers {
19487 return show_line_numbers;
19488 }
19489 EditorSettings::get_global(cx).gutter.line_numbers
19490 }
19491
19492 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19493 match (
19494 self.use_relative_line_numbers,
19495 EditorSettings::get_global(cx).relative_line_numbers,
19496 ) {
19497 (None, setting) => setting,
19498 (Some(false), _) => RelativeLineNumbers::Disabled,
19499 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19500 (Some(true), _) => RelativeLineNumbers::Enabled,
19501 }
19502 }
19503
19504 pub fn toggle_relative_line_numbers(
19505 &mut self,
19506 _: &ToggleRelativeLineNumbers,
19507 _: &mut Window,
19508 cx: &mut Context<Self>,
19509 ) {
19510 let is_relative = self.relative_line_numbers(cx);
19511 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19512 }
19513
19514 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19515 self.use_relative_line_numbers = is_relative;
19516 cx.notify();
19517 }
19518
19519 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19520 self.show_gutter = show_gutter;
19521 cx.notify();
19522 }
19523
19524 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19525 self.show_scrollbars = ScrollbarAxes {
19526 horizontal: show,
19527 vertical: show,
19528 };
19529 cx.notify();
19530 }
19531
19532 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19533 self.show_scrollbars.vertical = show;
19534 cx.notify();
19535 }
19536
19537 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19538 self.show_scrollbars.horizontal = show;
19539 cx.notify();
19540 }
19541
19542 pub fn set_minimap_visibility(
19543 &mut self,
19544 minimap_visibility: MinimapVisibility,
19545 window: &mut Window,
19546 cx: &mut Context<Self>,
19547 ) {
19548 if self.minimap_visibility != minimap_visibility {
19549 if minimap_visibility.visible() && self.minimap.is_none() {
19550 let minimap_settings = EditorSettings::get_global(cx).minimap;
19551 self.minimap =
19552 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19553 }
19554 self.minimap_visibility = minimap_visibility;
19555 cx.notify();
19556 }
19557 }
19558
19559 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19560 self.set_show_scrollbars(false, cx);
19561 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19562 }
19563
19564 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19565 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19566 }
19567
19568 /// Normally the text in full mode and auto height editors is padded on the
19569 /// left side by roughly half a character width for improved hit testing.
19570 ///
19571 /// Use this method to disable this for cases where this is not wanted (e.g.
19572 /// if you want to align the editor text with some other text above or below)
19573 /// or if you want to add this padding to single-line editors.
19574 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19575 self.offset_content = offset_content;
19576 cx.notify();
19577 }
19578
19579 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19580 self.show_line_numbers = Some(show_line_numbers);
19581 cx.notify();
19582 }
19583
19584 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19585 self.disable_expand_excerpt_buttons = true;
19586 cx.notify();
19587 }
19588
19589 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19590 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19591 cx.notify();
19592 }
19593
19594 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19595 self.show_code_actions = Some(show_code_actions);
19596 cx.notify();
19597 }
19598
19599 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19600 self.show_runnables = Some(show_runnables);
19601 cx.notify();
19602 }
19603
19604 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19605 self.show_breakpoints = Some(show_breakpoints);
19606 cx.notify();
19607 }
19608
19609 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19610 if self.display_map.read(cx).masked != masked {
19611 self.display_map.update(cx, |map, _| map.masked = masked);
19612 }
19613 cx.notify()
19614 }
19615
19616 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19617 self.show_wrap_guides = Some(show_wrap_guides);
19618 cx.notify();
19619 }
19620
19621 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19622 self.show_indent_guides = Some(show_indent_guides);
19623 cx.notify();
19624 }
19625
19626 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19627 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19628 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19629 && let Some(dir) = file.abs_path(cx).parent()
19630 {
19631 return Some(dir.to_owned());
19632 }
19633 }
19634
19635 None
19636 }
19637
19638 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19639 self.active_excerpt(cx)?
19640 .1
19641 .read(cx)
19642 .file()
19643 .and_then(|f| f.as_local())
19644 }
19645
19646 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19647 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19648 let buffer = buffer.read(cx);
19649 if let Some(project_path) = buffer.project_path(cx) {
19650 let project = self.project()?.read(cx);
19651 project.absolute_path(&project_path, cx)
19652 } else {
19653 buffer
19654 .file()
19655 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19656 }
19657 })
19658 }
19659
19660 pub fn reveal_in_finder(
19661 &mut self,
19662 _: &RevealInFileManager,
19663 _window: &mut Window,
19664 cx: &mut Context<Self>,
19665 ) {
19666 if let Some(target) = self.target_file(cx) {
19667 cx.reveal_path(&target.abs_path(cx));
19668 }
19669 }
19670
19671 pub fn copy_path(
19672 &mut self,
19673 _: &zed_actions::workspace::CopyPath,
19674 _window: &mut Window,
19675 cx: &mut Context<Self>,
19676 ) {
19677 if let Some(path) = self.target_file_abs_path(cx)
19678 && let Some(path) = path.to_str()
19679 {
19680 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19681 } else {
19682 cx.propagate();
19683 }
19684 }
19685
19686 pub fn copy_relative_path(
19687 &mut self,
19688 _: &zed_actions::workspace::CopyRelativePath,
19689 _window: &mut Window,
19690 cx: &mut Context<Self>,
19691 ) {
19692 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19693 let project = self.project()?.read(cx);
19694 let path = buffer.read(cx).file()?.path();
19695 let path = path.display(project.path_style(cx));
19696 Some(path)
19697 }) {
19698 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19699 } else {
19700 cx.propagate();
19701 }
19702 }
19703
19704 /// Returns the project path for the editor's buffer, if any buffer is
19705 /// opened in the editor.
19706 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19707 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19708 buffer.read(cx).project_path(cx)
19709 } else {
19710 None
19711 }
19712 }
19713
19714 // Returns true if the editor handled a go-to-line request
19715 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19716 maybe!({
19717 let breakpoint_store = self.breakpoint_store.as_ref()?;
19718
19719 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19720 else {
19721 self.clear_row_highlights::<ActiveDebugLine>();
19722 return None;
19723 };
19724
19725 let position = active_stack_frame.position;
19726 let buffer_id = position.buffer_id?;
19727 let snapshot = self
19728 .project
19729 .as_ref()?
19730 .read(cx)
19731 .buffer_for_id(buffer_id, cx)?
19732 .read(cx)
19733 .snapshot();
19734
19735 let mut handled = false;
19736 for (id, ExcerptRange { context, .. }) in
19737 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19738 {
19739 if context.start.cmp(&position, &snapshot).is_ge()
19740 || context.end.cmp(&position, &snapshot).is_lt()
19741 {
19742 continue;
19743 }
19744 let snapshot = self.buffer.read(cx).snapshot(cx);
19745 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19746
19747 handled = true;
19748 self.clear_row_highlights::<ActiveDebugLine>();
19749
19750 self.go_to_line::<ActiveDebugLine>(
19751 multibuffer_anchor,
19752 Some(cx.theme().colors().editor_debugger_active_line_background),
19753 window,
19754 cx,
19755 );
19756
19757 cx.notify();
19758 }
19759
19760 handled.then_some(())
19761 })
19762 .is_some()
19763 }
19764
19765 pub fn copy_file_name_without_extension(
19766 &mut self,
19767 _: &CopyFileNameWithoutExtension,
19768 _: &mut Window,
19769 cx: &mut Context<Self>,
19770 ) {
19771 if let Some(file) = self.target_file(cx)
19772 && let Some(file_stem) = file.path().file_stem()
19773 {
19774 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19775 }
19776 }
19777
19778 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19779 if let Some(file) = self.target_file(cx)
19780 && let Some(name) = file.path().file_name()
19781 {
19782 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19783 }
19784 }
19785
19786 pub fn toggle_git_blame(
19787 &mut self,
19788 _: &::git::Blame,
19789 window: &mut Window,
19790 cx: &mut Context<Self>,
19791 ) {
19792 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19793
19794 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19795 self.start_git_blame(true, window, cx);
19796 }
19797
19798 cx.notify();
19799 }
19800
19801 pub fn toggle_git_blame_inline(
19802 &mut self,
19803 _: &ToggleGitBlameInline,
19804 window: &mut Window,
19805 cx: &mut Context<Self>,
19806 ) {
19807 self.toggle_git_blame_inline_internal(true, window, cx);
19808 cx.notify();
19809 }
19810
19811 pub fn open_git_blame_commit(
19812 &mut self,
19813 _: &OpenGitBlameCommit,
19814 window: &mut Window,
19815 cx: &mut Context<Self>,
19816 ) {
19817 self.open_git_blame_commit_internal(window, cx);
19818 }
19819
19820 fn open_git_blame_commit_internal(
19821 &mut self,
19822 window: &mut Window,
19823 cx: &mut Context<Self>,
19824 ) -> Option<()> {
19825 let blame = self.blame.as_ref()?;
19826 let snapshot = self.snapshot(window, cx);
19827 let cursor = self
19828 .selections
19829 .newest::<Point>(&snapshot.display_snapshot)
19830 .head();
19831 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19832 let (_, blame_entry) = blame
19833 .update(cx, |blame, cx| {
19834 blame
19835 .blame_for_rows(
19836 &[RowInfo {
19837 buffer_id: Some(buffer.remote_id()),
19838 buffer_row: Some(point.row),
19839 ..Default::default()
19840 }],
19841 cx,
19842 )
19843 .next()
19844 })
19845 .flatten()?;
19846 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19847 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19848 let workspace = self.workspace()?.downgrade();
19849 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19850 None
19851 }
19852
19853 pub fn git_blame_inline_enabled(&self) -> bool {
19854 self.git_blame_inline_enabled
19855 }
19856
19857 pub fn toggle_selection_menu(
19858 &mut self,
19859 _: &ToggleSelectionMenu,
19860 _: &mut Window,
19861 cx: &mut Context<Self>,
19862 ) {
19863 self.show_selection_menu = self
19864 .show_selection_menu
19865 .map(|show_selections_menu| !show_selections_menu)
19866 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19867
19868 cx.notify();
19869 }
19870
19871 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19872 self.show_selection_menu
19873 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19874 }
19875
19876 fn start_git_blame(
19877 &mut self,
19878 user_triggered: bool,
19879 window: &mut Window,
19880 cx: &mut Context<Self>,
19881 ) {
19882 if let Some(project) = self.project() {
19883 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19884 && buffer.read(cx).file().is_none()
19885 {
19886 return;
19887 }
19888
19889 let focused = self.focus_handle(cx).contains_focused(window, cx);
19890
19891 let project = project.clone();
19892 let blame = cx
19893 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19894 self.blame_subscription =
19895 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19896 self.blame = Some(blame);
19897 }
19898 }
19899
19900 fn toggle_git_blame_inline_internal(
19901 &mut self,
19902 user_triggered: bool,
19903 window: &mut Window,
19904 cx: &mut Context<Self>,
19905 ) {
19906 if self.git_blame_inline_enabled {
19907 self.git_blame_inline_enabled = false;
19908 self.show_git_blame_inline = false;
19909 self.show_git_blame_inline_delay_task.take();
19910 } else {
19911 self.git_blame_inline_enabled = true;
19912 self.start_git_blame_inline(user_triggered, window, cx);
19913 }
19914
19915 cx.notify();
19916 }
19917
19918 fn start_git_blame_inline(
19919 &mut self,
19920 user_triggered: bool,
19921 window: &mut Window,
19922 cx: &mut Context<Self>,
19923 ) {
19924 self.start_git_blame(user_triggered, window, cx);
19925
19926 if ProjectSettings::get_global(cx)
19927 .git
19928 .inline_blame_delay()
19929 .is_some()
19930 {
19931 self.start_inline_blame_timer(window, cx);
19932 } else {
19933 self.show_git_blame_inline = true
19934 }
19935 }
19936
19937 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19938 self.blame.as_ref()
19939 }
19940
19941 pub fn show_git_blame_gutter(&self) -> bool {
19942 self.show_git_blame_gutter
19943 }
19944
19945 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19946 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19947 }
19948
19949 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19950 self.show_git_blame_inline
19951 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19952 && !self.newest_selection_head_on_empty_line(cx)
19953 && self.has_blame_entries(cx)
19954 }
19955
19956 fn has_blame_entries(&self, cx: &App) -> bool {
19957 self.blame()
19958 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19959 }
19960
19961 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19962 let cursor_anchor = self.selections.newest_anchor().head();
19963
19964 let snapshot = self.buffer.read(cx).snapshot(cx);
19965 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19966
19967 snapshot.line_len(buffer_row) == 0
19968 }
19969
19970 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19971 let buffer_and_selection = maybe!({
19972 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19973 let selection_range = selection.range();
19974
19975 let multi_buffer = self.buffer().read(cx);
19976 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19977 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19978
19979 let (buffer, range, _) = if selection.reversed {
19980 buffer_ranges.first()
19981 } else {
19982 buffer_ranges.last()
19983 }?;
19984
19985 let selection = text::ToPoint::to_point(&range.start, buffer).row
19986 ..text::ToPoint::to_point(&range.end, buffer).row;
19987 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19988 });
19989
19990 let Some((buffer, selection)) = buffer_and_selection else {
19991 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19992 };
19993
19994 let Some(project) = self.project() else {
19995 return Task::ready(Err(anyhow!("editor does not have project")));
19996 };
19997
19998 project.update(cx, |project, cx| {
19999 project.get_permalink_to_line(&buffer, selection, cx)
20000 })
20001 }
20002
20003 pub fn copy_permalink_to_line(
20004 &mut self,
20005 _: &CopyPermalinkToLine,
20006 window: &mut Window,
20007 cx: &mut Context<Self>,
20008 ) {
20009 let permalink_task = self.get_permalink_to_line(cx);
20010 let workspace = self.workspace();
20011
20012 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20013 Ok(permalink) => {
20014 cx.update(|_, cx| {
20015 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20016 })
20017 .ok();
20018 }
20019 Err(err) => {
20020 let message = format!("Failed to copy permalink: {err}");
20021
20022 anyhow::Result::<()>::Err(err).log_err();
20023
20024 if let Some(workspace) = workspace {
20025 workspace
20026 .update_in(cx, |workspace, _, cx| {
20027 struct CopyPermalinkToLine;
20028
20029 workspace.show_toast(
20030 Toast::new(
20031 NotificationId::unique::<CopyPermalinkToLine>(),
20032 message,
20033 ),
20034 cx,
20035 )
20036 })
20037 .ok();
20038 }
20039 }
20040 })
20041 .detach();
20042 }
20043
20044 pub fn copy_file_location(
20045 &mut self,
20046 _: &CopyFileLocation,
20047 _: &mut Window,
20048 cx: &mut Context<Self>,
20049 ) {
20050 let selection = self
20051 .selections
20052 .newest::<Point>(&self.display_snapshot(cx))
20053 .start
20054 .row
20055 + 1;
20056 if let Some(file) = self.target_file(cx) {
20057 let path = file.path().display(file.path_style(cx));
20058 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20059 }
20060 }
20061
20062 pub fn open_permalink_to_line(
20063 &mut self,
20064 _: &OpenPermalinkToLine,
20065 window: &mut Window,
20066 cx: &mut Context<Self>,
20067 ) {
20068 let permalink_task = self.get_permalink_to_line(cx);
20069 let workspace = self.workspace();
20070
20071 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20072 Ok(permalink) => {
20073 cx.update(|_, cx| {
20074 cx.open_url(permalink.as_ref());
20075 })
20076 .ok();
20077 }
20078 Err(err) => {
20079 let message = format!("Failed to open permalink: {err}");
20080
20081 anyhow::Result::<()>::Err(err).log_err();
20082
20083 if let Some(workspace) = workspace {
20084 workspace
20085 .update(cx, |workspace, cx| {
20086 struct OpenPermalinkToLine;
20087
20088 workspace.show_toast(
20089 Toast::new(
20090 NotificationId::unique::<OpenPermalinkToLine>(),
20091 message,
20092 ),
20093 cx,
20094 )
20095 })
20096 .ok();
20097 }
20098 }
20099 })
20100 .detach();
20101 }
20102
20103 pub fn insert_uuid_v4(
20104 &mut self,
20105 _: &InsertUuidV4,
20106 window: &mut Window,
20107 cx: &mut Context<Self>,
20108 ) {
20109 self.insert_uuid(UuidVersion::V4, window, cx);
20110 }
20111
20112 pub fn insert_uuid_v7(
20113 &mut self,
20114 _: &InsertUuidV7,
20115 window: &mut Window,
20116 cx: &mut Context<Self>,
20117 ) {
20118 self.insert_uuid(UuidVersion::V7, window, cx);
20119 }
20120
20121 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20122 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20123 self.transact(window, cx, |this, window, cx| {
20124 let edits = this
20125 .selections
20126 .all::<Point>(&this.display_snapshot(cx))
20127 .into_iter()
20128 .map(|selection| {
20129 let uuid = match version {
20130 UuidVersion::V4 => uuid::Uuid::new_v4(),
20131 UuidVersion::V7 => uuid::Uuid::now_v7(),
20132 };
20133
20134 (selection.range(), uuid.to_string())
20135 });
20136 this.edit(edits, cx);
20137 this.refresh_edit_prediction(true, false, window, cx);
20138 });
20139 }
20140
20141 pub fn open_selections_in_multibuffer(
20142 &mut self,
20143 _: &OpenSelectionsInMultibuffer,
20144 window: &mut Window,
20145 cx: &mut Context<Self>,
20146 ) {
20147 let multibuffer = self.buffer.read(cx);
20148
20149 let Some(buffer) = multibuffer.as_singleton() else {
20150 return;
20151 };
20152
20153 let Some(workspace) = self.workspace() else {
20154 return;
20155 };
20156
20157 let title = multibuffer.title(cx).to_string();
20158
20159 let locations = self
20160 .selections
20161 .all_anchors(cx)
20162 .iter()
20163 .map(|selection| {
20164 (
20165 buffer.clone(),
20166 (selection.start.text_anchor..selection.end.text_anchor)
20167 .to_point(buffer.read(cx)),
20168 )
20169 })
20170 .into_group_map();
20171
20172 cx.spawn_in(window, async move |_, cx| {
20173 workspace.update_in(cx, |workspace, window, cx| {
20174 Self::open_locations_in_multibuffer(
20175 workspace,
20176 locations,
20177 format!("Selections for '{title}'"),
20178 false,
20179 MultibufferSelectionMode::All,
20180 window,
20181 cx,
20182 );
20183 })
20184 })
20185 .detach();
20186 }
20187
20188 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20189 /// last highlight added will be used.
20190 ///
20191 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20192 pub fn highlight_rows<T: 'static>(
20193 &mut self,
20194 range: Range<Anchor>,
20195 color: Hsla,
20196 options: RowHighlightOptions,
20197 cx: &mut Context<Self>,
20198 ) {
20199 let snapshot = self.buffer().read(cx).snapshot(cx);
20200 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20201 let ix = row_highlights.binary_search_by(|highlight| {
20202 Ordering::Equal
20203 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20204 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20205 });
20206
20207 if let Err(mut ix) = ix {
20208 let index = post_inc(&mut self.highlight_order);
20209
20210 // If this range intersects with the preceding highlight, then merge it with
20211 // the preceding highlight. Otherwise insert a new highlight.
20212 let mut merged = false;
20213 if ix > 0 {
20214 let prev_highlight = &mut row_highlights[ix - 1];
20215 if prev_highlight
20216 .range
20217 .end
20218 .cmp(&range.start, &snapshot)
20219 .is_ge()
20220 {
20221 ix -= 1;
20222 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20223 prev_highlight.range.end = range.end;
20224 }
20225 merged = true;
20226 prev_highlight.index = index;
20227 prev_highlight.color = color;
20228 prev_highlight.options = options;
20229 }
20230 }
20231
20232 if !merged {
20233 row_highlights.insert(
20234 ix,
20235 RowHighlight {
20236 range,
20237 index,
20238 color,
20239 options,
20240 type_id: TypeId::of::<T>(),
20241 },
20242 );
20243 }
20244
20245 // If any of the following highlights intersect with this one, merge them.
20246 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20247 let highlight = &row_highlights[ix];
20248 if next_highlight
20249 .range
20250 .start
20251 .cmp(&highlight.range.end, &snapshot)
20252 .is_le()
20253 {
20254 if next_highlight
20255 .range
20256 .end
20257 .cmp(&highlight.range.end, &snapshot)
20258 .is_gt()
20259 {
20260 row_highlights[ix].range.end = next_highlight.range.end;
20261 }
20262 row_highlights.remove(ix + 1);
20263 } else {
20264 break;
20265 }
20266 }
20267 }
20268 }
20269
20270 /// Remove any highlighted row ranges of the given type that intersect the
20271 /// given ranges.
20272 pub fn remove_highlighted_rows<T: 'static>(
20273 &mut self,
20274 ranges_to_remove: Vec<Range<Anchor>>,
20275 cx: &mut Context<Self>,
20276 ) {
20277 let snapshot = self.buffer().read(cx).snapshot(cx);
20278 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20279 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20280 row_highlights.retain(|highlight| {
20281 while let Some(range_to_remove) = ranges_to_remove.peek() {
20282 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20283 Ordering::Less | Ordering::Equal => {
20284 ranges_to_remove.next();
20285 }
20286 Ordering::Greater => {
20287 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20288 Ordering::Less | Ordering::Equal => {
20289 return false;
20290 }
20291 Ordering::Greater => break,
20292 }
20293 }
20294 }
20295 }
20296
20297 true
20298 })
20299 }
20300
20301 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20302 pub fn clear_row_highlights<T: 'static>(&mut self) {
20303 self.highlighted_rows.remove(&TypeId::of::<T>());
20304 }
20305
20306 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20307 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20308 self.highlighted_rows
20309 .get(&TypeId::of::<T>())
20310 .map_or(&[] as &[_], |vec| vec.as_slice())
20311 .iter()
20312 .map(|highlight| (highlight.range.clone(), highlight.color))
20313 }
20314
20315 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20316 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20317 /// Allows to ignore certain kinds of highlights.
20318 pub fn highlighted_display_rows(
20319 &self,
20320 window: &mut Window,
20321 cx: &mut App,
20322 ) -> BTreeMap<DisplayRow, LineHighlight> {
20323 let snapshot = self.snapshot(window, cx);
20324 let mut used_highlight_orders = HashMap::default();
20325 self.highlighted_rows
20326 .iter()
20327 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20328 .fold(
20329 BTreeMap::<DisplayRow, LineHighlight>::new(),
20330 |mut unique_rows, highlight| {
20331 let start = highlight.range.start.to_display_point(&snapshot);
20332 let end = highlight.range.end.to_display_point(&snapshot);
20333 let start_row = start.row().0;
20334 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20335 && end.column() == 0
20336 {
20337 end.row().0.saturating_sub(1)
20338 } else {
20339 end.row().0
20340 };
20341 for row in start_row..=end_row {
20342 let used_index =
20343 used_highlight_orders.entry(row).or_insert(highlight.index);
20344 if highlight.index >= *used_index {
20345 *used_index = highlight.index;
20346 unique_rows.insert(
20347 DisplayRow(row),
20348 LineHighlight {
20349 include_gutter: highlight.options.include_gutter,
20350 border: None,
20351 background: highlight.color.into(),
20352 type_id: Some(highlight.type_id),
20353 },
20354 );
20355 }
20356 }
20357 unique_rows
20358 },
20359 )
20360 }
20361
20362 pub fn highlighted_display_row_for_autoscroll(
20363 &self,
20364 snapshot: &DisplaySnapshot,
20365 ) -> Option<DisplayRow> {
20366 self.highlighted_rows
20367 .values()
20368 .flat_map(|highlighted_rows| highlighted_rows.iter())
20369 .filter_map(|highlight| {
20370 if highlight.options.autoscroll {
20371 Some(highlight.range.start.to_display_point(snapshot).row())
20372 } else {
20373 None
20374 }
20375 })
20376 .min()
20377 }
20378
20379 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20380 self.highlight_background::<SearchWithinRange>(
20381 ranges,
20382 |colors| colors.colors().editor_document_highlight_read_background,
20383 cx,
20384 )
20385 }
20386
20387 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20388 self.breadcrumb_header = Some(new_header);
20389 }
20390
20391 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20392 self.clear_background_highlights::<SearchWithinRange>(cx);
20393 }
20394
20395 pub fn highlight_background<T: 'static>(
20396 &mut self,
20397 ranges: &[Range<Anchor>],
20398 color_fetcher: fn(&Theme) -> Hsla,
20399 cx: &mut Context<Self>,
20400 ) {
20401 self.background_highlights.insert(
20402 HighlightKey::Type(TypeId::of::<T>()),
20403 (color_fetcher, Arc::from(ranges)),
20404 );
20405 self.scrollbar_marker_state.dirty = true;
20406 cx.notify();
20407 }
20408
20409 pub fn highlight_background_key<T: 'static>(
20410 &mut self,
20411 key: usize,
20412 ranges: &[Range<Anchor>],
20413 color_fetcher: fn(&Theme) -> Hsla,
20414 cx: &mut Context<Self>,
20415 ) {
20416 self.background_highlights.insert(
20417 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20418 (color_fetcher, Arc::from(ranges)),
20419 );
20420 self.scrollbar_marker_state.dirty = true;
20421 cx.notify();
20422 }
20423
20424 pub fn clear_background_highlights<T: 'static>(
20425 &mut self,
20426 cx: &mut Context<Self>,
20427 ) -> Option<BackgroundHighlight> {
20428 let text_highlights = self
20429 .background_highlights
20430 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20431 if !text_highlights.1.is_empty() {
20432 self.scrollbar_marker_state.dirty = true;
20433 cx.notify();
20434 }
20435 Some(text_highlights)
20436 }
20437
20438 pub fn highlight_gutter<T: 'static>(
20439 &mut self,
20440 ranges: impl Into<Vec<Range<Anchor>>>,
20441 color_fetcher: fn(&App) -> Hsla,
20442 cx: &mut Context<Self>,
20443 ) {
20444 self.gutter_highlights
20445 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20446 cx.notify();
20447 }
20448
20449 pub fn clear_gutter_highlights<T: 'static>(
20450 &mut self,
20451 cx: &mut Context<Self>,
20452 ) -> Option<GutterHighlight> {
20453 cx.notify();
20454 self.gutter_highlights.remove(&TypeId::of::<T>())
20455 }
20456
20457 pub fn insert_gutter_highlight<T: 'static>(
20458 &mut self,
20459 range: Range<Anchor>,
20460 color_fetcher: fn(&App) -> Hsla,
20461 cx: &mut Context<Self>,
20462 ) {
20463 let snapshot = self.buffer().read(cx).snapshot(cx);
20464 let mut highlights = self
20465 .gutter_highlights
20466 .remove(&TypeId::of::<T>())
20467 .map(|(_, highlights)| highlights)
20468 .unwrap_or_default();
20469 let ix = highlights.binary_search_by(|highlight| {
20470 Ordering::Equal
20471 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20472 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20473 });
20474 if let Err(ix) = ix {
20475 highlights.insert(ix, range);
20476 }
20477 self.gutter_highlights
20478 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20479 }
20480
20481 pub fn remove_gutter_highlights<T: 'static>(
20482 &mut self,
20483 ranges_to_remove: Vec<Range<Anchor>>,
20484 cx: &mut Context<Self>,
20485 ) {
20486 let snapshot = self.buffer().read(cx).snapshot(cx);
20487 let Some((color_fetcher, mut gutter_highlights)) =
20488 self.gutter_highlights.remove(&TypeId::of::<T>())
20489 else {
20490 return;
20491 };
20492 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20493 gutter_highlights.retain(|highlight| {
20494 while let Some(range_to_remove) = ranges_to_remove.peek() {
20495 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20496 Ordering::Less | Ordering::Equal => {
20497 ranges_to_remove.next();
20498 }
20499 Ordering::Greater => {
20500 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20501 Ordering::Less | Ordering::Equal => {
20502 return false;
20503 }
20504 Ordering::Greater => break,
20505 }
20506 }
20507 }
20508 }
20509
20510 true
20511 });
20512 self.gutter_highlights
20513 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20514 }
20515
20516 #[cfg(feature = "test-support")]
20517 pub fn all_text_highlights(
20518 &self,
20519 window: &mut Window,
20520 cx: &mut Context<Self>,
20521 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20522 let snapshot = self.snapshot(window, cx);
20523 self.display_map.update(cx, |display_map, _| {
20524 display_map
20525 .all_text_highlights()
20526 .map(|highlight| {
20527 let (style, ranges) = highlight.as_ref();
20528 (
20529 *style,
20530 ranges
20531 .iter()
20532 .map(|range| range.clone().to_display_points(&snapshot))
20533 .collect(),
20534 )
20535 })
20536 .collect()
20537 })
20538 }
20539
20540 #[cfg(feature = "test-support")]
20541 pub fn all_text_background_highlights(
20542 &self,
20543 window: &mut Window,
20544 cx: &mut Context<Self>,
20545 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20546 let snapshot = self.snapshot(window, cx);
20547 let buffer = &snapshot.buffer_snapshot();
20548 let start = buffer.anchor_before(0);
20549 let end = buffer.anchor_after(buffer.len());
20550 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20551 }
20552
20553 #[cfg(any(test, feature = "test-support"))]
20554 pub fn sorted_background_highlights_in_range(
20555 &self,
20556 search_range: Range<Anchor>,
20557 display_snapshot: &DisplaySnapshot,
20558 theme: &Theme,
20559 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20560 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20561 res.sort_by(|a, b| {
20562 a.0.start
20563 .cmp(&b.0.start)
20564 .then_with(|| a.0.end.cmp(&b.0.end))
20565 .then_with(|| a.1.cmp(&b.1))
20566 });
20567 res
20568 }
20569
20570 #[cfg(feature = "test-support")]
20571 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20572 let snapshot = self.buffer().read(cx).snapshot(cx);
20573
20574 let highlights = self
20575 .background_highlights
20576 .get(&HighlightKey::Type(TypeId::of::<
20577 items::BufferSearchHighlights,
20578 >()));
20579
20580 if let Some((_color, ranges)) = highlights {
20581 ranges
20582 .iter()
20583 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20584 .collect_vec()
20585 } else {
20586 vec![]
20587 }
20588 }
20589
20590 fn document_highlights_for_position<'a>(
20591 &'a self,
20592 position: Anchor,
20593 buffer: &'a MultiBufferSnapshot,
20594 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20595 let read_highlights = self
20596 .background_highlights
20597 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20598 .map(|h| &h.1);
20599 let write_highlights = self
20600 .background_highlights
20601 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20602 .map(|h| &h.1);
20603 let left_position = position.bias_left(buffer);
20604 let right_position = position.bias_right(buffer);
20605 read_highlights
20606 .into_iter()
20607 .chain(write_highlights)
20608 .flat_map(move |ranges| {
20609 let start_ix = match ranges.binary_search_by(|probe| {
20610 let cmp = probe.end.cmp(&left_position, buffer);
20611 if cmp.is_ge() {
20612 Ordering::Greater
20613 } else {
20614 Ordering::Less
20615 }
20616 }) {
20617 Ok(i) | Err(i) => i,
20618 };
20619
20620 ranges[start_ix..]
20621 .iter()
20622 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20623 })
20624 }
20625
20626 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20627 self.background_highlights
20628 .get(&HighlightKey::Type(TypeId::of::<T>()))
20629 .is_some_and(|(_, highlights)| !highlights.is_empty())
20630 }
20631
20632 /// Returns all background highlights for a given range.
20633 ///
20634 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20635 pub fn background_highlights_in_range(
20636 &self,
20637 search_range: Range<Anchor>,
20638 display_snapshot: &DisplaySnapshot,
20639 theme: &Theme,
20640 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20641 let mut results = Vec::new();
20642 for (color_fetcher, ranges) in self.background_highlights.values() {
20643 let color = color_fetcher(theme);
20644 let start_ix = match ranges.binary_search_by(|probe| {
20645 let cmp = probe
20646 .end
20647 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20648 if cmp.is_gt() {
20649 Ordering::Greater
20650 } else {
20651 Ordering::Less
20652 }
20653 }) {
20654 Ok(i) | Err(i) => i,
20655 };
20656 for range in &ranges[start_ix..] {
20657 if range
20658 .start
20659 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20660 .is_ge()
20661 {
20662 break;
20663 }
20664
20665 let start = range.start.to_display_point(display_snapshot);
20666 let end = range.end.to_display_point(display_snapshot);
20667 results.push((start..end, color))
20668 }
20669 }
20670 results
20671 }
20672
20673 pub fn gutter_highlights_in_range(
20674 &self,
20675 search_range: Range<Anchor>,
20676 display_snapshot: &DisplaySnapshot,
20677 cx: &App,
20678 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20679 let mut results = Vec::new();
20680 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20681 let color = color_fetcher(cx);
20682 let start_ix = match ranges.binary_search_by(|probe| {
20683 let cmp = probe
20684 .end
20685 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20686 if cmp.is_gt() {
20687 Ordering::Greater
20688 } else {
20689 Ordering::Less
20690 }
20691 }) {
20692 Ok(i) | Err(i) => i,
20693 };
20694 for range in &ranges[start_ix..] {
20695 if range
20696 .start
20697 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20698 .is_ge()
20699 {
20700 break;
20701 }
20702
20703 let start = range.start.to_display_point(display_snapshot);
20704 let end = range.end.to_display_point(display_snapshot);
20705 results.push((start..end, color))
20706 }
20707 }
20708 results
20709 }
20710
20711 /// Get the text ranges corresponding to the redaction query
20712 pub fn redacted_ranges(
20713 &self,
20714 search_range: Range<Anchor>,
20715 display_snapshot: &DisplaySnapshot,
20716 cx: &App,
20717 ) -> Vec<Range<DisplayPoint>> {
20718 display_snapshot
20719 .buffer_snapshot()
20720 .redacted_ranges(search_range, |file| {
20721 if let Some(file) = file {
20722 file.is_private()
20723 && EditorSettings::get(
20724 Some(SettingsLocation {
20725 worktree_id: file.worktree_id(cx),
20726 path: file.path().as_ref(),
20727 }),
20728 cx,
20729 )
20730 .redact_private_values
20731 } else {
20732 false
20733 }
20734 })
20735 .map(|range| {
20736 range.start.to_display_point(display_snapshot)
20737 ..range.end.to_display_point(display_snapshot)
20738 })
20739 .collect()
20740 }
20741
20742 pub fn highlight_text_key<T: 'static>(
20743 &mut self,
20744 key: usize,
20745 ranges: Vec<Range<Anchor>>,
20746 style: HighlightStyle,
20747 cx: &mut Context<Self>,
20748 ) {
20749 self.display_map.update(cx, |map, _| {
20750 map.highlight_text(
20751 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20752 ranges,
20753 style,
20754 );
20755 });
20756 cx.notify();
20757 }
20758
20759 pub fn highlight_text<T: 'static>(
20760 &mut self,
20761 ranges: Vec<Range<Anchor>>,
20762 style: HighlightStyle,
20763 cx: &mut Context<Self>,
20764 ) {
20765 self.display_map.update(cx, |map, _| {
20766 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20767 });
20768 cx.notify();
20769 }
20770
20771 pub fn text_highlights<'a, T: 'static>(
20772 &'a self,
20773 cx: &'a App,
20774 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20775 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20776 }
20777
20778 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20779 let cleared = self
20780 .display_map
20781 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20782 if cleared {
20783 cx.notify();
20784 }
20785 }
20786
20787 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20788 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20789 && self.focus_handle.is_focused(window)
20790 }
20791
20792 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20793 self.show_cursor_when_unfocused = is_enabled;
20794 cx.notify();
20795 }
20796
20797 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20798 cx.notify();
20799 }
20800
20801 fn on_debug_session_event(
20802 &mut self,
20803 _session: Entity<Session>,
20804 event: &SessionEvent,
20805 cx: &mut Context<Self>,
20806 ) {
20807 if let SessionEvent::InvalidateInlineValue = event {
20808 self.refresh_inline_values(cx);
20809 }
20810 }
20811
20812 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20813 let Some(project) = self.project.clone() else {
20814 return;
20815 };
20816
20817 if !self.inline_value_cache.enabled {
20818 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20819 self.splice_inlays(&inlays, Vec::new(), cx);
20820 return;
20821 }
20822
20823 let current_execution_position = self
20824 .highlighted_rows
20825 .get(&TypeId::of::<ActiveDebugLine>())
20826 .and_then(|lines| lines.last().map(|line| line.range.end));
20827
20828 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20829 let inline_values = editor
20830 .update(cx, |editor, cx| {
20831 let Some(current_execution_position) = current_execution_position else {
20832 return Some(Task::ready(Ok(Vec::new())));
20833 };
20834
20835 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20836 let snapshot = buffer.snapshot(cx);
20837
20838 let excerpt = snapshot.excerpt_containing(
20839 current_execution_position..current_execution_position,
20840 )?;
20841
20842 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20843 })?;
20844
20845 let range =
20846 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20847
20848 project.inline_values(buffer, range, cx)
20849 })
20850 .ok()
20851 .flatten()?
20852 .await
20853 .context("refreshing debugger inlays")
20854 .log_err()?;
20855
20856 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20857
20858 for (buffer_id, inline_value) in inline_values
20859 .into_iter()
20860 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20861 {
20862 buffer_inline_values
20863 .entry(buffer_id)
20864 .or_default()
20865 .push(inline_value);
20866 }
20867
20868 editor
20869 .update(cx, |editor, cx| {
20870 let snapshot = editor.buffer.read(cx).snapshot(cx);
20871 let mut new_inlays = Vec::default();
20872
20873 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20874 let buffer_id = buffer_snapshot.remote_id();
20875 buffer_inline_values
20876 .get(&buffer_id)
20877 .into_iter()
20878 .flatten()
20879 .for_each(|hint| {
20880 let inlay = Inlay::debugger(
20881 post_inc(&mut editor.next_inlay_id),
20882 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20883 hint.text(),
20884 );
20885 if !inlay.text().chars().contains(&'\n') {
20886 new_inlays.push(inlay);
20887 }
20888 });
20889 }
20890
20891 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20892 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20893
20894 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20895 })
20896 .ok()?;
20897 Some(())
20898 });
20899 }
20900
20901 fn on_buffer_event(
20902 &mut self,
20903 multibuffer: &Entity<MultiBuffer>,
20904 event: &multi_buffer::Event,
20905 window: &mut Window,
20906 cx: &mut Context<Self>,
20907 ) {
20908 match event {
20909 multi_buffer::Event::Edited { edited_buffer } => {
20910 self.scrollbar_marker_state.dirty = true;
20911 self.active_indent_guides_state.dirty = true;
20912 self.refresh_active_diagnostics(cx);
20913 self.refresh_code_actions(window, cx);
20914 self.refresh_selected_text_highlights(true, window, cx);
20915 self.refresh_single_line_folds(window, cx);
20916 self.refresh_matching_bracket_highlights(window, cx);
20917 if self.has_active_edit_prediction() {
20918 self.update_visible_edit_prediction(window, cx);
20919 }
20920
20921 if let Some(buffer) = edited_buffer {
20922 if buffer.read(cx).file().is_none() {
20923 cx.emit(EditorEvent::TitleChanged);
20924 }
20925
20926 if self.project.is_some() {
20927 let buffer_id = buffer.read(cx).remote_id();
20928 self.register_buffer(buffer_id, cx);
20929 self.update_lsp_data(Some(buffer_id), window, cx);
20930 self.refresh_inlay_hints(
20931 InlayHintRefreshReason::BufferEdited(buffer_id),
20932 cx,
20933 );
20934 }
20935 }
20936
20937 cx.emit(EditorEvent::BufferEdited);
20938 cx.emit(SearchEvent::MatchesInvalidated);
20939
20940 let Some(project) = &self.project else { return };
20941 let (telemetry, is_via_ssh) = {
20942 let project = project.read(cx);
20943 let telemetry = project.client().telemetry().clone();
20944 let is_via_ssh = project.is_via_remote_server();
20945 (telemetry, is_via_ssh)
20946 };
20947 telemetry.log_edit_event("editor", is_via_ssh);
20948 }
20949 multi_buffer::Event::ExcerptsAdded {
20950 buffer,
20951 predecessor,
20952 excerpts,
20953 } => {
20954 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20955 let buffer_id = buffer.read(cx).remote_id();
20956 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20957 && let Some(project) = &self.project
20958 {
20959 update_uncommitted_diff_for_buffer(
20960 cx.entity(),
20961 project,
20962 [buffer.clone()],
20963 self.buffer.clone(),
20964 cx,
20965 )
20966 .detach();
20967 }
20968 self.update_lsp_data(Some(buffer_id), window, cx);
20969 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20970 cx.emit(EditorEvent::ExcerptsAdded {
20971 buffer: buffer.clone(),
20972 predecessor: *predecessor,
20973 excerpts: excerpts.clone(),
20974 });
20975 }
20976 multi_buffer::Event::ExcerptsRemoved {
20977 ids,
20978 removed_buffer_ids,
20979 } => {
20980 if let Some(inlay_hints) = &mut self.inlay_hints {
20981 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
20982 }
20983 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20984 for buffer_id in removed_buffer_ids {
20985 self.registered_buffers.remove(buffer_id);
20986 }
20987 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20988 cx.emit(EditorEvent::ExcerptsRemoved {
20989 ids: ids.clone(),
20990 removed_buffer_ids: removed_buffer_ids.clone(),
20991 });
20992 }
20993 multi_buffer::Event::ExcerptsEdited {
20994 excerpt_ids,
20995 buffer_ids,
20996 } => {
20997 self.display_map.update(cx, |map, cx| {
20998 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20999 });
21000 cx.emit(EditorEvent::ExcerptsEdited {
21001 ids: excerpt_ids.clone(),
21002 });
21003 }
21004 multi_buffer::Event::ExcerptsExpanded { ids } => {
21005 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21006 self.refresh_document_highlights(cx);
21007 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21008 }
21009 multi_buffer::Event::Reparsed(buffer_id) => {
21010 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21011 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21012
21013 cx.emit(EditorEvent::Reparsed(*buffer_id));
21014 }
21015 multi_buffer::Event::DiffHunksToggled => {
21016 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21017 }
21018 multi_buffer::Event::LanguageChanged(buffer_id) => {
21019 self.registered_buffers.remove(&buffer_id);
21020 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21021 cx.emit(EditorEvent::Reparsed(*buffer_id));
21022 cx.notify();
21023 }
21024 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21025 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21026 multi_buffer::Event::FileHandleChanged
21027 | multi_buffer::Event::Reloaded
21028 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21029 multi_buffer::Event::DiagnosticsUpdated => {
21030 self.update_diagnostics_state(window, cx);
21031 }
21032 _ => {}
21033 };
21034 }
21035
21036 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21037 if !self.diagnostics_enabled() {
21038 return;
21039 }
21040 self.refresh_active_diagnostics(cx);
21041 self.refresh_inline_diagnostics(true, window, cx);
21042 self.scrollbar_marker_state.dirty = true;
21043 cx.notify();
21044 }
21045
21046 pub fn start_temporary_diff_override(&mut self) {
21047 self.load_diff_task.take();
21048 self.temporary_diff_override = true;
21049 }
21050
21051 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21052 self.temporary_diff_override = false;
21053 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21054 self.buffer.update(cx, |buffer, cx| {
21055 buffer.set_all_diff_hunks_collapsed(cx);
21056 });
21057
21058 if let Some(project) = self.project.clone() {
21059 self.load_diff_task = Some(
21060 update_uncommitted_diff_for_buffer(
21061 cx.entity(),
21062 &project,
21063 self.buffer.read(cx).all_buffers(),
21064 self.buffer.clone(),
21065 cx,
21066 )
21067 .shared(),
21068 );
21069 }
21070 }
21071
21072 fn on_display_map_changed(
21073 &mut self,
21074 _: Entity<DisplayMap>,
21075 _: &mut Window,
21076 cx: &mut Context<Self>,
21077 ) {
21078 cx.notify();
21079 }
21080
21081 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21082 if self.diagnostics_enabled() {
21083 let new_severity = EditorSettings::get_global(cx)
21084 .diagnostics_max_severity
21085 .unwrap_or(DiagnosticSeverity::Hint);
21086 self.set_max_diagnostics_severity(new_severity, cx);
21087 }
21088 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21089 self.update_edit_prediction_settings(cx);
21090 self.refresh_edit_prediction(true, false, window, cx);
21091 self.refresh_inline_values(cx);
21092 self.refresh_inlay_hints(
21093 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21094 self.selections.newest_anchor().head(),
21095 &self.buffer.read(cx).snapshot(cx),
21096 cx,
21097 )),
21098 cx,
21099 );
21100
21101 let old_cursor_shape = self.cursor_shape;
21102 let old_show_breadcrumbs = self.show_breadcrumbs;
21103
21104 {
21105 let editor_settings = EditorSettings::get_global(cx);
21106 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21107 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21108 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21109 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21110 }
21111
21112 if old_cursor_shape != self.cursor_shape {
21113 cx.emit(EditorEvent::CursorShapeChanged);
21114 }
21115
21116 if old_show_breadcrumbs != self.show_breadcrumbs {
21117 cx.emit(EditorEvent::BreadcrumbsChanged);
21118 }
21119
21120 let project_settings = ProjectSettings::get_global(cx);
21121 self.serialize_dirty_buffers =
21122 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21123
21124 if self.mode.is_full() {
21125 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21126 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21127 if self.show_inline_diagnostics != show_inline_diagnostics {
21128 self.show_inline_diagnostics = show_inline_diagnostics;
21129 self.refresh_inline_diagnostics(false, window, cx);
21130 }
21131
21132 if self.git_blame_inline_enabled != inline_blame_enabled {
21133 self.toggle_git_blame_inline_internal(false, window, cx);
21134 }
21135
21136 let minimap_settings = EditorSettings::get_global(cx).minimap;
21137 if self.minimap_visibility != MinimapVisibility::Disabled {
21138 if self.minimap_visibility.settings_visibility()
21139 != minimap_settings.minimap_enabled()
21140 {
21141 self.set_minimap_visibility(
21142 MinimapVisibility::for_mode(self.mode(), cx),
21143 window,
21144 cx,
21145 );
21146 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21147 minimap_entity.update(cx, |minimap_editor, cx| {
21148 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21149 })
21150 }
21151 }
21152 }
21153
21154 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21155 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21156 }) {
21157 if !inlay_splice.is_empty() {
21158 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21159 }
21160 self.refresh_colors_for_visible_range(None, window, cx);
21161 }
21162
21163 cx.notify();
21164 }
21165
21166 pub fn set_searchable(&mut self, searchable: bool) {
21167 self.searchable = searchable;
21168 }
21169
21170 pub fn searchable(&self) -> bool {
21171 self.searchable
21172 }
21173
21174 pub fn open_excerpts_in_split(
21175 &mut self,
21176 _: &OpenExcerptsSplit,
21177 window: &mut Window,
21178 cx: &mut Context<Self>,
21179 ) {
21180 self.open_excerpts_common(None, true, window, cx)
21181 }
21182
21183 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21184 self.open_excerpts_common(None, false, window, cx)
21185 }
21186
21187 fn open_excerpts_common(
21188 &mut self,
21189 jump_data: Option<JumpData>,
21190 split: bool,
21191 window: &mut Window,
21192 cx: &mut Context<Self>,
21193 ) {
21194 let Some(workspace) = self.workspace() else {
21195 cx.propagate();
21196 return;
21197 };
21198
21199 if self.buffer.read(cx).is_singleton() {
21200 cx.propagate();
21201 return;
21202 }
21203
21204 let mut new_selections_by_buffer = HashMap::default();
21205 match &jump_data {
21206 Some(JumpData::MultiBufferPoint {
21207 excerpt_id,
21208 position,
21209 anchor,
21210 line_offset_from_top,
21211 }) => {
21212 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21213 if let Some(buffer) = multi_buffer_snapshot
21214 .buffer_id_for_excerpt(*excerpt_id)
21215 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21216 {
21217 let buffer_snapshot = buffer.read(cx).snapshot();
21218 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21219 language::ToPoint::to_point(anchor, &buffer_snapshot)
21220 } else {
21221 buffer_snapshot.clip_point(*position, Bias::Left)
21222 };
21223 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21224 new_selections_by_buffer.insert(
21225 buffer,
21226 (
21227 vec![jump_to_offset..jump_to_offset],
21228 Some(*line_offset_from_top),
21229 ),
21230 );
21231 }
21232 }
21233 Some(JumpData::MultiBufferRow {
21234 row,
21235 line_offset_from_top,
21236 }) => {
21237 let point = MultiBufferPoint::new(row.0, 0);
21238 if let Some((buffer, buffer_point, _)) =
21239 self.buffer.read(cx).point_to_buffer_point(point, cx)
21240 {
21241 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21242 new_selections_by_buffer
21243 .entry(buffer)
21244 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21245 .0
21246 .push(buffer_offset..buffer_offset)
21247 }
21248 }
21249 None => {
21250 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21251 let multi_buffer = self.buffer.read(cx);
21252 for selection in selections {
21253 for (snapshot, range, _, anchor) in multi_buffer
21254 .snapshot(cx)
21255 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21256 {
21257 if let Some(anchor) = anchor {
21258 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21259 else {
21260 continue;
21261 };
21262 let offset = text::ToOffset::to_offset(
21263 &anchor.text_anchor,
21264 &buffer_handle.read(cx).snapshot(),
21265 );
21266 let range = offset..offset;
21267 new_selections_by_buffer
21268 .entry(buffer_handle)
21269 .or_insert((Vec::new(), None))
21270 .0
21271 .push(range)
21272 } else {
21273 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21274 else {
21275 continue;
21276 };
21277 new_selections_by_buffer
21278 .entry(buffer_handle)
21279 .or_insert((Vec::new(), None))
21280 .0
21281 .push(range)
21282 }
21283 }
21284 }
21285 }
21286 }
21287
21288 new_selections_by_buffer
21289 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21290
21291 if new_selections_by_buffer.is_empty() {
21292 return;
21293 }
21294
21295 // We defer the pane interaction because we ourselves are a workspace item
21296 // and activating a new item causes the pane to call a method on us reentrantly,
21297 // which panics if we're on the stack.
21298 window.defer(cx, move |window, cx| {
21299 workspace.update(cx, |workspace, cx| {
21300 let pane = if split {
21301 workspace.adjacent_pane(window, cx)
21302 } else {
21303 workspace.active_pane().clone()
21304 };
21305
21306 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21307 let editor = buffer
21308 .read(cx)
21309 .file()
21310 .is_none()
21311 .then(|| {
21312 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21313 // so `workspace.open_project_item` will never find them, always opening a new editor.
21314 // Instead, we try to activate the existing editor in the pane first.
21315 let (editor, pane_item_index) =
21316 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21317 let editor = item.downcast::<Editor>()?;
21318 let singleton_buffer =
21319 editor.read(cx).buffer().read(cx).as_singleton()?;
21320 if singleton_buffer == buffer {
21321 Some((editor, i))
21322 } else {
21323 None
21324 }
21325 })?;
21326 pane.update(cx, |pane, cx| {
21327 pane.activate_item(pane_item_index, true, true, window, cx)
21328 });
21329 Some(editor)
21330 })
21331 .flatten()
21332 .unwrap_or_else(|| {
21333 workspace.open_project_item::<Self>(
21334 pane.clone(),
21335 buffer,
21336 true,
21337 true,
21338 window,
21339 cx,
21340 )
21341 });
21342
21343 editor.update(cx, |editor, cx| {
21344 let autoscroll = match scroll_offset {
21345 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21346 None => Autoscroll::newest(),
21347 };
21348 let nav_history = editor.nav_history.take();
21349 editor.change_selections(
21350 SelectionEffects::scroll(autoscroll),
21351 window,
21352 cx,
21353 |s| {
21354 s.select_ranges(ranges);
21355 },
21356 );
21357 editor.nav_history = nav_history;
21358 });
21359 }
21360 })
21361 });
21362 }
21363
21364 // For now, don't allow opening excerpts in buffers that aren't backed by
21365 // regular project files.
21366 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21367 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21368 }
21369
21370 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21371 let snapshot = self.buffer.read(cx).read(cx);
21372 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21373 Some(
21374 ranges
21375 .iter()
21376 .map(move |range| {
21377 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21378 })
21379 .collect(),
21380 )
21381 }
21382
21383 fn selection_replacement_ranges(
21384 &self,
21385 range: Range<OffsetUtf16>,
21386 cx: &mut App,
21387 ) -> Vec<Range<OffsetUtf16>> {
21388 let selections = self
21389 .selections
21390 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21391 let newest_selection = selections
21392 .iter()
21393 .max_by_key(|selection| selection.id)
21394 .unwrap();
21395 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21396 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21397 let snapshot = self.buffer.read(cx).read(cx);
21398 selections
21399 .into_iter()
21400 .map(|mut selection| {
21401 selection.start.0 =
21402 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21403 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21404 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21405 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21406 })
21407 .collect()
21408 }
21409
21410 fn report_editor_event(
21411 &self,
21412 reported_event: ReportEditorEvent,
21413 file_extension: Option<String>,
21414 cx: &App,
21415 ) {
21416 if cfg!(any(test, feature = "test-support")) {
21417 return;
21418 }
21419
21420 let Some(project) = &self.project else { return };
21421
21422 // If None, we are in a file without an extension
21423 let file = self
21424 .buffer
21425 .read(cx)
21426 .as_singleton()
21427 .and_then(|b| b.read(cx).file());
21428 let file_extension = file_extension.or(file
21429 .as_ref()
21430 .and_then(|file| Path::new(file.file_name(cx)).extension())
21431 .and_then(|e| e.to_str())
21432 .map(|a| a.to_string()));
21433
21434 let vim_mode = vim_enabled(cx);
21435
21436 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21437 let copilot_enabled = edit_predictions_provider
21438 == language::language_settings::EditPredictionProvider::Copilot;
21439 let copilot_enabled_for_language = self
21440 .buffer
21441 .read(cx)
21442 .language_settings(cx)
21443 .show_edit_predictions;
21444
21445 let project = project.read(cx);
21446 let event_type = reported_event.event_type();
21447
21448 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21449 telemetry::event!(
21450 event_type,
21451 type = if auto_saved {"autosave"} else {"manual"},
21452 file_extension,
21453 vim_mode,
21454 copilot_enabled,
21455 copilot_enabled_for_language,
21456 edit_predictions_provider,
21457 is_via_ssh = project.is_via_remote_server(),
21458 );
21459 } else {
21460 telemetry::event!(
21461 event_type,
21462 file_extension,
21463 vim_mode,
21464 copilot_enabled,
21465 copilot_enabled_for_language,
21466 edit_predictions_provider,
21467 is_via_ssh = project.is_via_remote_server(),
21468 );
21469 };
21470 }
21471
21472 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21473 /// with each line being an array of {text, highlight} objects.
21474 fn copy_highlight_json(
21475 &mut self,
21476 _: &CopyHighlightJson,
21477 window: &mut Window,
21478 cx: &mut Context<Self>,
21479 ) {
21480 #[derive(Serialize)]
21481 struct Chunk<'a> {
21482 text: String,
21483 highlight: Option<&'a str>,
21484 }
21485
21486 let snapshot = self.buffer.read(cx).snapshot(cx);
21487 let range = self
21488 .selected_text_range(false, window, cx)
21489 .and_then(|selection| {
21490 if selection.range.is_empty() {
21491 None
21492 } else {
21493 Some(
21494 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21495 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21496 )
21497 }
21498 })
21499 .unwrap_or_else(|| 0..snapshot.len());
21500
21501 let chunks = snapshot.chunks(range, true);
21502 let mut lines = Vec::new();
21503 let mut line: VecDeque<Chunk> = VecDeque::new();
21504
21505 let Some(style) = self.style.as_ref() else {
21506 return;
21507 };
21508
21509 for chunk in chunks {
21510 let highlight = chunk
21511 .syntax_highlight_id
21512 .and_then(|id| id.name(&style.syntax));
21513 let mut chunk_lines = chunk.text.split('\n').peekable();
21514 while let Some(text) = chunk_lines.next() {
21515 let mut merged_with_last_token = false;
21516 if let Some(last_token) = line.back_mut()
21517 && last_token.highlight == highlight
21518 {
21519 last_token.text.push_str(text);
21520 merged_with_last_token = true;
21521 }
21522
21523 if !merged_with_last_token {
21524 line.push_back(Chunk {
21525 text: text.into(),
21526 highlight,
21527 });
21528 }
21529
21530 if chunk_lines.peek().is_some() {
21531 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21532 line.pop_front();
21533 }
21534 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21535 line.pop_back();
21536 }
21537
21538 lines.push(mem::take(&mut line));
21539 }
21540 }
21541 }
21542
21543 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21544 return;
21545 };
21546 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21547 }
21548
21549 pub fn open_context_menu(
21550 &mut self,
21551 _: &OpenContextMenu,
21552 window: &mut Window,
21553 cx: &mut Context<Self>,
21554 ) {
21555 self.request_autoscroll(Autoscroll::newest(), cx);
21556 let position = self
21557 .selections
21558 .newest_display(&self.display_snapshot(cx))
21559 .start;
21560 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21561 }
21562
21563 pub fn replay_insert_event(
21564 &mut self,
21565 text: &str,
21566 relative_utf16_range: Option<Range<isize>>,
21567 window: &mut Window,
21568 cx: &mut Context<Self>,
21569 ) {
21570 if !self.input_enabled {
21571 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21572 return;
21573 }
21574 if let Some(relative_utf16_range) = relative_utf16_range {
21575 let selections = self
21576 .selections
21577 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21578 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21579 let new_ranges = selections.into_iter().map(|range| {
21580 let start = OffsetUtf16(
21581 range
21582 .head()
21583 .0
21584 .saturating_add_signed(relative_utf16_range.start),
21585 );
21586 let end = OffsetUtf16(
21587 range
21588 .head()
21589 .0
21590 .saturating_add_signed(relative_utf16_range.end),
21591 );
21592 start..end
21593 });
21594 s.select_ranges(new_ranges);
21595 });
21596 }
21597
21598 self.handle_input(text, window, cx);
21599 }
21600
21601 pub fn is_focused(&self, window: &Window) -> bool {
21602 self.focus_handle.is_focused(window)
21603 }
21604
21605 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21606 cx.emit(EditorEvent::Focused);
21607
21608 if let Some(descendant) = self
21609 .last_focused_descendant
21610 .take()
21611 .and_then(|descendant| descendant.upgrade())
21612 {
21613 window.focus(&descendant);
21614 } else {
21615 if let Some(blame) = self.blame.as_ref() {
21616 blame.update(cx, GitBlame::focus)
21617 }
21618
21619 self.blink_manager.update(cx, BlinkManager::enable);
21620 self.show_cursor_names(window, cx);
21621 self.buffer.update(cx, |buffer, cx| {
21622 buffer.finalize_last_transaction(cx);
21623 if self.leader_id.is_none() {
21624 buffer.set_active_selections(
21625 &self.selections.disjoint_anchors_arc(),
21626 self.selections.line_mode(),
21627 self.cursor_shape,
21628 cx,
21629 );
21630 }
21631 });
21632 }
21633 }
21634
21635 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21636 cx.emit(EditorEvent::FocusedIn)
21637 }
21638
21639 fn handle_focus_out(
21640 &mut self,
21641 event: FocusOutEvent,
21642 _window: &mut Window,
21643 cx: &mut Context<Self>,
21644 ) {
21645 if event.blurred != self.focus_handle {
21646 self.last_focused_descendant = Some(event.blurred);
21647 }
21648 self.selection_drag_state = SelectionDragState::None;
21649 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21650 }
21651
21652 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21653 self.blink_manager.update(cx, BlinkManager::disable);
21654 self.buffer
21655 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21656
21657 if let Some(blame) = self.blame.as_ref() {
21658 blame.update(cx, GitBlame::blur)
21659 }
21660 if !self.hover_state.focused(window, cx) {
21661 hide_hover(self, cx);
21662 }
21663 if !self
21664 .context_menu
21665 .borrow()
21666 .as_ref()
21667 .is_some_and(|context_menu| context_menu.focused(window, cx))
21668 {
21669 self.hide_context_menu(window, cx);
21670 }
21671 self.take_active_edit_prediction(cx);
21672 cx.emit(EditorEvent::Blurred);
21673 cx.notify();
21674 }
21675
21676 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21677 let mut pending: String = window
21678 .pending_input_keystrokes()
21679 .into_iter()
21680 .flatten()
21681 .filter_map(|keystroke| {
21682 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21683 keystroke.key_char.clone()
21684 } else {
21685 None
21686 }
21687 })
21688 .collect();
21689
21690 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21691 pending = "".to_string();
21692 }
21693
21694 let existing_pending = self
21695 .text_highlights::<PendingInput>(cx)
21696 .map(|(_, ranges)| ranges.to_vec());
21697 if existing_pending.is_none() && pending.is_empty() {
21698 return;
21699 }
21700 let transaction =
21701 self.transact(window, cx, |this, window, cx| {
21702 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21703 let edits = selections
21704 .iter()
21705 .map(|selection| (selection.end..selection.end, pending.clone()));
21706 this.edit(edits, cx);
21707 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21708 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21709 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21710 }));
21711 });
21712 if let Some(existing_ranges) = existing_pending {
21713 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21714 this.edit(edits, cx);
21715 }
21716 });
21717
21718 let snapshot = self.snapshot(window, cx);
21719 let ranges = self
21720 .selections
21721 .all::<usize>(&snapshot.display_snapshot)
21722 .into_iter()
21723 .map(|selection| {
21724 snapshot.buffer_snapshot().anchor_after(selection.end)
21725 ..snapshot
21726 .buffer_snapshot()
21727 .anchor_before(selection.end + pending.len())
21728 })
21729 .collect();
21730
21731 if pending.is_empty() {
21732 self.clear_highlights::<PendingInput>(cx);
21733 } else {
21734 self.highlight_text::<PendingInput>(
21735 ranges,
21736 HighlightStyle {
21737 underline: Some(UnderlineStyle {
21738 thickness: px(1.),
21739 color: None,
21740 wavy: false,
21741 }),
21742 ..Default::default()
21743 },
21744 cx,
21745 );
21746 }
21747
21748 self.ime_transaction = self.ime_transaction.or(transaction);
21749 if let Some(transaction) = self.ime_transaction {
21750 self.buffer.update(cx, |buffer, cx| {
21751 buffer.group_until_transaction(transaction, cx);
21752 });
21753 }
21754
21755 if self.text_highlights::<PendingInput>(cx).is_none() {
21756 self.ime_transaction.take();
21757 }
21758 }
21759
21760 pub fn register_action_renderer(
21761 &mut self,
21762 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21763 ) -> Subscription {
21764 let id = self.next_editor_action_id.post_inc();
21765 self.editor_actions
21766 .borrow_mut()
21767 .insert(id, Box::new(listener));
21768
21769 let editor_actions = self.editor_actions.clone();
21770 Subscription::new(move || {
21771 editor_actions.borrow_mut().remove(&id);
21772 })
21773 }
21774
21775 pub fn register_action<A: Action>(
21776 &mut self,
21777 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21778 ) -> Subscription {
21779 let id = self.next_editor_action_id.post_inc();
21780 let listener = Arc::new(listener);
21781 self.editor_actions.borrow_mut().insert(
21782 id,
21783 Box::new(move |_, window, _| {
21784 let listener = listener.clone();
21785 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21786 let action = action.downcast_ref().unwrap();
21787 if phase == DispatchPhase::Bubble {
21788 listener(action, window, cx)
21789 }
21790 })
21791 }),
21792 );
21793
21794 let editor_actions = self.editor_actions.clone();
21795 Subscription::new(move || {
21796 editor_actions.borrow_mut().remove(&id);
21797 })
21798 }
21799
21800 pub fn file_header_size(&self) -> u32 {
21801 FILE_HEADER_HEIGHT
21802 }
21803
21804 pub fn restore(
21805 &mut self,
21806 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21807 window: &mut Window,
21808 cx: &mut Context<Self>,
21809 ) {
21810 let workspace = self.workspace();
21811 let project = self.project();
21812 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21813 let mut tasks = Vec::new();
21814 for (buffer_id, changes) in revert_changes {
21815 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21816 buffer.update(cx, |buffer, cx| {
21817 buffer.edit(
21818 changes
21819 .into_iter()
21820 .map(|(range, text)| (range, text.to_string())),
21821 None,
21822 cx,
21823 );
21824 });
21825
21826 if let Some(project) =
21827 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21828 {
21829 project.update(cx, |project, cx| {
21830 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21831 })
21832 }
21833 }
21834 }
21835 tasks
21836 });
21837 cx.spawn_in(window, async move |_, cx| {
21838 for (buffer, task) in save_tasks {
21839 let result = task.await;
21840 if result.is_err() {
21841 let Some(path) = buffer
21842 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21843 .ok()
21844 else {
21845 continue;
21846 };
21847 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21848 let Some(task) = cx
21849 .update_window_entity(workspace, |workspace, window, cx| {
21850 workspace
21851 .open_path_preview(path, None, false, false, false, window, cx)
21852 })
21853 .ok()
21854 else {
21855 continue;
21856 };
21857 task.await.log_err();
21858 }
21859 }
21860 }
21861 })
21862 .detach();
21863 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21864 selections.refresh()
21865 });
21866 }
21867
21868 pub fn to_pixel_point(
21869 &self,
21870 source: multi_buffer::Anchor,
21871 editor_snapshot: &EditorSnapshot,
21872 window: &mut Window,
21873 ) -> Option<gpui::Point<Pixels>> {
21874 let source_point = source.to_display_point(editor_snapshot);
21875 self.display_to_pixel_point(source_point, editor_snapshot, window)
21876 }
21877
21878 pub fn display_to_pixel_point(
21879 &self,
21880 source: DisplayPoint,
21881 editor_snapshot: &EditorSnapshot,
21882 window: &mut Window,
21883 ) -> Option<gpui::Point<Pixels>> {
21884 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21885 let text_layout_details = self.text_layout_details(window);
21886 let scroll_top = text_layout_details
21887 .scroll_anchor
21888 .scroll_position(editor_snapshot)
21889 .y;
21890
21891 if source.row().as_f64() < scroll_top.floor() {
21892 return None;
21893 }
21894 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21895 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21896 Some(gpui::Point::new(source_x, source_y))
21897 }
21898
21899 pub fn has_visible_completions_menu(&self) -> bool {
21900 !self.edit_prediction_preview_is_active()
21901 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21902 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21903 })
21904 }
21905
21906 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21907 if self.mode.is_minimap() {
21908 return;
21909 }
21910 self.addons
21911 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21912 }
21913
21914 pub fn unregister_addon<T: Addon>(&mut self) {
21915 self.addons.remove(&std::any::TypeId::of::<T>());
21916 }
21917
21918 pub fn addon<T: Addon>(&self) -> Option<&T> {
21919 let type_id = std::any::TypeId::of::<T>();
21920 self.addons
21921 .get(&type_id)
21922 .and_then(|item| item.to_any().downcast_ref::<T>())
21923 }
21924
21925 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21926 let type_id = std::any::TypeId::of::<T>();
21927 self.addons
21928 .get_mut(&type_id)
21929 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21930 }
21931
21932 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21933 let text_layout_details = self.text_layout_details(window);
21934 let style = &text_layout_details.editor_style;
21935 let font_id = window.text_system().resolve_font(&style.text.font());
21936 let font_size = style.text.font_size.to_pixels(window.rem_size());
21937 let line_height = style.text.line_height_in_pixels(window.rem_size());
21938 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21939 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21940
21941 CharacterDimensions {
21942 em_width,
21943 em_advance,
21944 line_height,
21945 }
21946 }
21947
21948 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21949 self.load_diff_task.clone()
21950 }
21951
21952 fn read_metadata_from_db(
21953 &mut self,
21954 item_id: u64,
21955 workspace_id: WorkspaceId,
21956 window: &mut Window,
21957 cx: &mut Context<Editor>,
21958 ) {
21959 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21960 && !self.mode.is_minimap()
21961 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21962 {
21963 let buffer_snapshot = OnceCell::new();
21964
21965 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21966 && !folds.is_empty()
21967 {
21968 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21969 self.fold_ranges(
21970 folds
21971 .into_iter()
21972 .map(|(start, end)| {
21973 snapshot.clip_offset(start, Bias::Left)
21974 ..snapshot.clip_offset(end, Bias::Right)
21975 })
21976 .collect(),
21977 false,
21978 window,
21979 cx,
21980 );
21981 }
21982
21983 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21984 && !selections.is_empty()
21985 {
21986 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21987 // skip adding the initial selection to selection history
21988 self.selection_history.mode = SelectionHistoryMode::Skipping;
21989 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21990 s.select_ranges(selections.into_iter().map(|(start, end)| {
21991 snapshot.clip_offset(start, Bias::Left)
21992 ..snapshot.clip_offset(end, Bias::Right)
21993 }));
21994 });
21995 self.selection_history.mode = SelectionHistoryMode::Normal;
21996 };
21997 }
21998
21999 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22000 }
22001
22002 fn update_lsp_data(
22003 &mut self,
22004 for_buffer: Option<BufferId>,
22005 window: &mut Window,
22006 cx: &mut Context<'_, Self>,
22007 ) {
22008 self.pull_diagnostics(for_buffer, window, cx);
22009 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22010 }
22011
22012 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22013 if self.ignore_lsp_data() {
22014 return;
22015 }
22016 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22017 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22018 }
22019 }
22020
22021 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22022 if !self.registered_buffers.contains_key(&buffer_id)
22023 && let Some(project) = self.project.as_ref()
22024 {
22025 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22026 project.update(cx, |project, cx| {
22027 self.registered_buffers.insert(
22028 buffer_id,
22029 project.register_buffer_with_language_servers(&buffer, cx),
22030 );
22031 });
22032 } else {
22033 self.registered_buffers.remove(&buffer_id);
22034 }
22035 }
22036 }
22037
22038 fn ignore_lsp_data(&self) -> bool {
22039 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22040 // skip any LSP updates for it.
22041 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22042 }
22043}
22044
22045fn edit_for_markdown_paste<'a>(
22046 buffer: &MultiBufferSnapshot,
22047 range: Range<usize>,
22048 to_insert: &'a str,
22049 url: Option<url::Url>,
22050) -> (Range<usize>, Cow<'a, str>) {
22051 if url.is_none() {
22052 return (range, Cow::Borrowed(to_insert));
22053 };
22054
22055 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22056
22057 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22058 Cow::Borrowed(to_insert)
22059 } else {
22060 Cow::Owned(format!("[{old_text}]({to_insert})"))
22061 };
22062 (range, new_text)
22063}
22064
22065fn vim_enabled(cx: &App) -> bool {
22066 vim_mode_setting::VimModeSetting::try_get(cx)
22067 .map(|vim_mode| vim_mode.0)
22068 .unwrap_or(false)
22069}
22070
22071fn process_completion_for_edit(
22072 completion: &Completion,
22073 intent: CompletionIntent,
22074 buffer: &Entity<Buffer>,
22075 cursor_position: &text::Anchor,
22076 cx: &mut Context<Editor>,
22077) -> CompletionEdit {
22078 let buffer = buffer.read(cx);
22079 let buffer_snapshot = buffer.snapshot();
22080 let (snippet, new_text) = if completion.is_snippet() {
22081 let mut snippet_source = completion.new_text.clone();
22082 // Workaround for typescript language server issues so that methods don't expand within
22083 // strings and functions with type expressions. The previous point is used because the query
22084 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22085 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22086 let previous_point = if previous_point.column > 0 {
22087 cursor_position.to_previous_offset(&buffer_snapshot)
22088 } else {
22089 cursor_position.to_offset(&buffer_snapshot)
22090 };
22091 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22092 && scope.prefers_label_for_snippet_in_completion()
22093 && let Some(label) = completion.label()
22094 && matches!(
22095 completion.kind(),
22096 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22097 )
22098 {
22099 snippet_source = label;
22100 }
22101 match Snippet::parse(&snippet_source).log_err() {
22102 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22103 None => (None, completion.new_text.clone()),
22104 }
22105 } else {
22106 (None, completion.new_text.clone())
22107 };
22108
22109 let mut range_to_replace = {
22110 let replace_range = &completion.replace_range;
22111 if let CompletionSource::Lsp {
22112 insert_range: Some(insert_range),
22113 ..
22114 } = &completion.source
22115 {
22116 debug_assert_eq!(
22117 insert_range.start, replace_range.start,
22118 "insert_range and replace_range should start at the same position"
22119 );
22120 debug_assert!(
22121 insert_range
22122 .start
22123 .cmp(cursor_position, &buffer_snapshot)
22124 .is_le(),
22125 "insert_range should start before or at cursor position"
22126 );
22127 debug_assert!(
22128 replace_range
22129 .start
22130 .cmp(cursor_position, &buffer_snapshot)
22131 .is_le(),
22132 "replace_range should start before or at cursor position"
22133 );
22134
22135 let should_replace = match intent {
22136 CompletionIntent::CompleteWithInsert => false,
22137 CompletionIntent::CompleteWithReplace => true,
22138 CompletionIntent::Complete | CompletionIntent::Compose => {
22139 let insert_mode =
22140 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22141 .completions
22142 .lsp_insert_mode;
22143 match insert_mode {
22144 LspInsertMode::Insert => false,
22145 LspInsertMode::Replace => true,
22146 LspInsertMode::ReplaceSubsequence => {
22147 let mut text_to_replace = buffer.chars_for_range(
22148 buffer.anchor_before(replace_range.start)
22149 ..buffer.anchor_after(replace_range.end),
22150 );
22151 let mut current_needle = text_to_replace.next();
22152 for haystack_ch in completion.label.text.chars() {
22153 if let Some(needle_ch) = current_needle
22154 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22155 {
22156 current_needle = text_to_replace.next();
22157 }
22158 }
22159 current_needle.is_none()
22160 }
22161 LspInsertMode::ReplaceSuffix => {
22162 if replace_range
22163 .end
22164 .cmp(cursor_position, &buffer_snapshot)
22165 .is_gt()
22166 {
22167 let range_after_cursor = *cursor_position..replace_range.end;
22168 let text_after_cursor = buffer
22169 .text_for_range(
22170 buffer.anchor_before(range_after_cursor.start)
22171 ..buffer.anchor_after(range_after_cursor.end),
22172 )
22173 .collect::<String>()
22174 .to_ascii_lowercase();
22175 completion
22176 .label
22177 .text
22178 .to_ascii_lowercase()
22179 .ends_with(&text_after_cursor)
22180 } else {
22181 true
22182 }
22183 }
22184 }
22185 }
22186 };
22187
22188 if should_replace {
22189 replace_range.clone()
22190 } else {
22191 insert_range.clone()
22192 }
22193 } else {
22194 replace_range.clone()
22195 }
22196 };
22197
22198 if range_to_replace
22199 .end
22200 .cmp(cursor_position, &buffer_snapshot)
22201 .is_lt()
22202 {
22203 range_to_replace.end = *cursor_position;
22204 }
22205
22206 CompletionEdit {
22207 new_text,
22208 replace_range: range_to_replace.to_offset(buffer),
22209 snippet,
22210 }
22211}
22212
22213struct CompletionEdit {
22214 new_text: String,
22215 replace_range: Range<usize>,
22216 snippet: Option<Snippet>,
22217}
22218
22219fn insert_extra_newline_brackets(
22220 buffer: &MultiBufferSnapshot,
22221 range: Range<usize>,
22222 language: &language::LanguageScope,
22223) -> bool {
22224 let leading_whitespace_len = buffer
22225 .reversed_chars_at(range.start)
22226 .take_while(|c| c.is_whitespace() && *c != '\n')
22227 .map(|c| c.len_utf8())
22228 .sum::<usize>();
22229 let trailing_whitespace_len = buffer
22230 .chars_at(range.end)
22231 .take_while(|c| c.is_whitespace() && *c != '\n')
22232 .map(|c| c.len_utf8())
22233 .sum::<usize>();
22234 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22235
22236 language.brackets().any(|(pair, enabled)| {
22237 let pair_start = pair.start.trim_end();
22238 let pair_end = pair.end.trim_start();
22239
22240 enabled
22241 && pair.newline
22242 && buffer.contains_str_at(range.end, pair_end)
22243 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22244 })
22245}
22246
22247fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22248 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22249 [(buffer, range, _)] => (*buffer, range.clone()),
22250 _ => return false,
22251 };
22252 let pair = {
22253 let mut result: Option<BracketMatch> = None;
22254
22255 for pair in buffer
22256 .all_bracket_ranges(range.clone())
22257 .filter(move |pair| {
22258 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22259 })
22260 {
22261 let len = pair.close_range.end - pair.open_range.start;
22262
22263 if let Some(existing) = &result {
22264 let existing_len = existing.close_range.end - existing.open_range.start;
22265 if len > existing_len {
22266 continue;
22267 }
22268 }
22269
22270 result = Some(pair);
22271 }
22272
22273 result
22274 };
22275 let Some(pair) = pair else {
22276 return false;
22277 };
22278 pair.newline_only
22279 && buffer
22280 .chars_for_range(pair.open_range.end..range.start)
22281 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22282 .all(|c| c.is_whitespace() && c != '\n')
22283}
22284
22285fn update_uncommitted_diff_for_buffer(
22286 editor: Entity<Editor>,
22287 project: &Entity<Project>,
22288 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22289 buffer: Entity<MultiBuffer>,
22290 cx: &mut App,
22291) -> Task<()> {
22292 let mut tasks = Vec::new();
22293 project.update(cx, |project, cx| {
22294 for buffer in buffers {
22295 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22296 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22297 }
22298 }
22299 });
22300 cx.spawn(async move |cx| {
22301 let diffs = future::join_all(tasks).await;
22302 if editor
22303 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22304 .unwrap_or(false)
22305 {
22306 return;
22307 }
22308
22309 buffer
22310 .update(cx, |buffer, cx| {
22311 for diff in diffs.into_iter().flatten() {
22312 buffer.add_diff(diff, cx);
22313 }
22314 })
22315 .ok();
22316 })
22317}
22318
22319fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22320 let tab_size = tab_size.get() as usize;
22321 let mut width = offset;
22322
22323 for ch in text.chars() {
22324 width += if ch == '\t' {
22325 tab_size - (width % tab_size)
22326 } else {
22327 1
22328 };
22329 }
22330
22331 width - offset
22332}
22333
22334#[cfg(test)]
22335mod tests {
22336 use super::*;
22337
22338 #[test]
22339 fn test_string_size_with_expanded_tabs() {
22340 let nz = |val| NonZeroU32::new(val).unwrap();
22341 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22342 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22343 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22344 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22345 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22346 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22347 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22348 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22349 }
22350}
22351
22352/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22353struct WordBreakingTokenizer<'a> {
22354 input: &'a str,
22355}
22356
22357impl<'a> WordBreakingTokenizer<'a> {
22358 fn new(input: &'a str) -> Self {
22359 Self { input }
22360 }
22361}
22362
22363fn is_char_ideographic(ch: char) -> bool {
22364 use unicode_script::Script::*;
22365 use unicode_script::UnicodeScript;
22366 matches!(ch.script(), Han | Tangut | Yi)
22367}
22368
22369fn is_grapheme_ideographic(text: &str) -> bool {
22370 text.chars().any(is_char_ideographic)
22371}
22372
22373fn is_grapheme_whitespace(text: &str) -> bool {
22374 text.chars().any(|x| x.is_whitespace())
22375}
22376
22377fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22378 text.chars()
22379 .next()
22380 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22381}
22382
22383#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22384enum WordBreakToken<'a> {
22385 Word { token: &'a str, grapheme_len: usize },
22386 InlineWhitespace { token: &'a str, grapheme_len: usize },
22387 Newline,
22388}
22389
22390impl<'a> Iterator for WordBreakingTokenizer<'a> {
22391 /// Yields a span, the count of graphemes in the token, and whether it was
22392 /// whitespace. Note that it also breaks at word boundaries.
22393 type Item = WordBreakToken<'a>;
22394
22395 fn next(&mut self) -> Option<Self::Item> {
22396 use unicode_segmentation::UnicodeSegmentation;
22397 if self.input.is_empty() {
22398 return None;
22399 }
22400
22401 let mut iter = self.input.graphemes(true).peekable();
22402 let mut offset = 0;
22403 let mut grapheme_len = 0;
22404 if let Some(first_grapheme) = iter.next() {
22405 let is_newline = first_grapheme == "\n";
22406 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22407 offset += first_grapheme.len();
22408 grapheme_len += 1;
22409 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22410 if let Some(grapheme) = iter.peek().copied()
22411 && should_stay_with_preceding_ideograph(grapheme)
22412 {
22413 offset += grapheme.len();
22414 grapheme_len += 1;
22415 }
22416 } else {
22417 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22418 let mut next_word_bound = words.peek().copied();
22419 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22420 next_word_bound = words.next();
22421 }
22422 while let Some(grapheme) = iter.peek().copied() {
22423 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22424 break;
22425 };
22426 if is_grapheme_whitespace(grapheme) != is_whitespace
22427 || (grapheme == "\n") != is_newline
22428 {
22429 break;
22430 };
22431 offset += grapheme.len();
22432 grapheme_len += 1;
22433 iter.next();
22434 }
22435 }
22436 let token = &self.input[..offset];
22437 self.input = &self.input[offset..];
22438 if token == "\n" {
22439 Some(WordBreakToken::Newline)
22440 } else if is_whitespace {
22441 Some(WordBreakToken::InlineWhitespace {
22442 token,
22443 grapheme_len,
22444 })
22445 } else {
22446 Some(WordBreakToken::Word {
22447 token,
22448 grapheme_len,
22449 })
22450 }
22451 } else {
22452 None
22453 }
22454 }
22455}
22456
22457#[test]
22458fn test_word_breaking_tokenizer() {
22459 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22460 ("", &[]),
22461 (" ", &[whitespace(" ", 2)]),
22462 ("Ʒ", &[word("Ʒ", 1)]),
22463 ("Ǽ", &[word("Ǽ", 1)]),
22464 ("⋑", &[word("⋑", 1)]),
22465 ("⋑⋑", &[word("⋑⋑", 2)]),
22466 (
22467 "原理,进而",
22468 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22469 ),
22470 (
22471 "hello world",
22472 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22473 ),
22474 (
22475 "hello, world",
22476 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22477 ),
22478 (
22479 " hello world",
22480 &[
22481 whitespace(" ", 2),
22482 word("hello", 5),
22483 whitespace(" ", 1),
22484 word("world", 5),
22485 ],
22486 ),
22487 (
22488 "这是什么 \n 钢笔",
22489 &[
22490 word("这", 1),
22491 word("是", 1),
22492 word("什", 1),
22493 word("么", 1),
22494 whitespace(" ", 1),
22495 newline(),
22496 whitespace(" ", 1),
22497 word("钢", 1),
22498 word("笔", 1),
22499 ],
22500 ),
22501 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22502 ];
22503
22504 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22505 WordBreakToken::Word {
22506 token,
22507 grapheme_len,
22508 }
22509 }
22510
22511 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22512 WordBreakToken::InlineWhitespace {
22513 token,
22514 grapheme_len,
22515 }
22516 }
22517
22518 fn newline() -> WordBreakToken<'static> {
22519 WordBreakToken::Newline
22520 }
22521
22522 for (input, result) in tests {
22523 assert_eq!(
22524 WordBreakingTokenizer::new(input)
22525 .collect::<Vec<_>>()
22526 .as_slice(),
22527 *result,
22528 );
22529 }
22530}
22531
22532fn wrap_with_prefix(
22533 first_line_prefix: String,
22534 subsequent_lines_prefix: String,
22535 unwrapped_text: String,
22536 wrap_column: usize,
22537 tab_size: NonZeroU32,
22538 preserve_existing_whitespace: bool,
22539) -> String {
22540 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22541 let subsequent_lines_prefix_len =
22542 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22543 let mut wrapped_text = String::new();
22544 let mut current_line = first_line_prefix;
22545 let mut is_first_line = true;
22546
22547 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22548 let mut current_line_len = first_line_prefix_len;
22549 let mut in_whitespace = false;
22550 for token in tokenizer {
22551 let have_preceding_whitespace = in_whitespace;
22552 match token {
22553 WordBreakToken::Word {
22554 token,
22555 grapheme_len,
22556 } => {
22557 in_whitespace = false;
22558 let current_prefix_len = if is_first_line {
22559 first_line_prefix_len
22560 } else {
22561 subsequent_lines_prefix_len
22562 };
22563 if current_line_len + grapheme_len > wrap_column
22564 && current_line_len != current_prefix_len
22565 {
22566 wrapped_text.push_str(current_line.trim_end());
22567 wrapped_text.push('\n');
22568 is_first_line = false;
22569 current_line = subsequent_lines_prefix.clone();
22570 current_line_len = subsequent_lines_prefix_len;
22571 }
22572 current_line.push_str(token);
22573 current_line_len += grapheme_len;
22574 }
22575 WordBreakToken::InlineWhitespace {
22576 mut token,
22577 mut grapheme_len,
22578 } => {
22579 in_whitespace = true;
22580 if have_preceding_whitespace && !preserve_existing_whitespace {
22581 continue;
22582 }
22583 if !preserve_existing_whitespace {
22584 // Keep a single whitespace grapheme as-is
22585 if let Some(first) =
22586 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22587 {
22588 token = first;
22589 } else {
22590 token = " ";
22591 }
22592 grapheme_len = 1;
22593 }
22594 let current_prefix_len = if is_first_line {
22595 first_line_prefix_len
22596 } else {
22597 subsequent_lines_prefix_len
22598 };
22599 if current_line_len + grapheme_len > wrap_column {
22600 wrapped_text.push_str(current_line.trim_end());
22601 wrapped_text.push('\n');
22602 is_first_line = false;
22603 current_line = subsequent_lines_prefix.clone();
22604 current_line_len = subsequent_lines_prefix_len;
22605 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22606 current_line.push_str(token);
22607 current_line_len += grapheme_len;
22608 }
22609 }
22610 WordBreakToken::Newline => {
22611 in_whitespace = true;
22612 let current_prefix_len = if is_first_line {
22613 first_line_prefix_len
22614 } else {
22615 subsequent_lines_prefix_len
22616 };
22617 if preserve_existing_whitespace {
22618 wrapped_text.push_str(current_line.trim_end());
22619 wrapped_text.push('\n');
22620 is_first_line = false;
22621 current_line = subsequent_lines_prefix.clone();
22622 current_line_len = subsequent_lines_prefix_len;
22623 } else if have_preceding_whitespace {
22624 continue;
22625 } else if current_line_len + 1 > wrap_column
22626 && current_line_len != current_prefix_len
22627 {
22628 wrapped_text.push_str(current_line.trim_end());
22629 wrapped_text.push('\n');
22630 is_first_line = false;
22631 current_line = subsequent_lines_prefix.clone();
22632 current_line_len = subsequent_lines_prefix_len;
22633 } else if current_line_len != current_prefix_len {
22634 current_line.push(' ');
22635 current_line_len += 1;
22636 }
22637 }
22638 }
22639 }
22640
22641 if !current_line.is_empty() {
22642 wrapped_text.push_str(¤t_line);
22643 }
22644 wrapped_text
22645}
22646
22647#[test]
22648fn test_wrap_with_prefix() {
22649 assert_eq!(
22650 wrap_with_prefix(
22651 "# ".to_string(),
22652 "# ".to_string(),
22653 "abcdefg".to_string(),
22654 4,
22655 NonZeroU32::new(4).unwrap(),
22656 false,
22657 ),
22658 "# abcdefg"
22659 );
22660 assert_eq!(
22661 wrap_with_prefix(
22662 "".to_string(),
22663 "".to_string(),
22664 "\thello world".to_string(),
22665 8,
22666 NonZeroU32::new(4).unwrap(),
22667 false,
22668 ),
22669 "hello\nworld"
22670 );
22671 assert_eq!(
22672 wrap_with_prefix(
22673 "// ".to_string(),
22674 "// ".to_string(),
22675 "xx \nyy zz aa bb cc".to_string(),
22676 12,
22677 NonZeroU32::new(4).unwrap(),
22678 false,
22679 ),
22680 "// xx yy zz\n// aa bb cc"
22681 );
22682 assert_eq!(
22683 wrap_with_prefix(
22684 String::new(),
22685 String::new(),
22686 "这是什么 \n 钢笔".to_string(),
22687 3,
22688 NonZeroU32::new(4).unwrap(),
22689 false,
22690 ),
22691 "这是什\n么 钢\n笔"
22692 );
22693 assert_eq!(
22694 wrap_with_prefix(
22695 String::new(),
22696 String::new(),
22697 format!("foo{}bar", '\u{2009}'), // thin space
22698 80,
22699 NonZeroU32::new(4).unwrap(),
22700 false,
22701 ),
22702 format!("foo{}bar", '\u{2009}')
22703 );
22704}
22705
22706pub trait CollaborationHub {
22707 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22708 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22709 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22710}
22711
22712impl CollaborationHub for Entity<Project> {
22713 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22714 self.read(cx).collaborators()
22715 }
22716
22717 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22718 self.read(cx).user_store().read(cx).participant_indices()
22719 }
22720
22721 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22722 let this = self.read(cx);
22723 let user_ids = this.collaborators().values().map(|c| c.user_id);
22724 this.user_store().read(cx).participant_names(user_ids, cx)
22725 }
22726}
22727
22728pub trait SemanticsProvider {
22729 fn hover(
22730 &self,
22731 buffer: &Entity<Buffer>,
22732 position: text::Anchor,
22733 cx: &mut App,
22734 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22735
22736 fn inline_values(
22737 &self,
22738 buffer_handle: Entity<Buffer>,
22739 range: Range<text::Anchor>,
22740 cx: &mut App,
22741 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22742
22743 fn applicable_inlay_chunks(
22744 &self,
22745 buffer: &Entity<Buffer>,
22746 ranges: &[Range<text::Anchor>],
22747 cx: &mut App,
22748 ) -> Vec<Range<BufferRow>>;
22749
22750 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22751
22752 fn inlay_hints(
22753 &self,
22754 invalidate: InvalidationStrategy,
22755 buffer: Entity<Buffer>,
22756 ranges: Vec<Range<text::Anchor>>,
22757 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22758 cx: &mut App,
22759 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22760
22761 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22762
22763 fn document_highlights(
22764 &self,
22765 buffer: &Entity<Buffer>,
22766 position: text::Anchor,
22767 cx: &mut App,
22768 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22769
22770 fn definitions(
22771 &self,
22772 buffer: &Entity<Buffer>,
22773 position: text::Anchor,
22774 kind: GotoDefinitionKind,
22775 cx: &mut App,
22776 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22777
22778 fn range_for_rename(
22779 &self,
22780 buffer: &Entity<Buffer>,
22781 position: text::Anchor,
22782 cx: &mut App,
22783 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22784
22785 fn perform_rename(
22786 &self,
22787 buffer: &Entity<Buffer>,
22788 position: text::Anchor,
22789 new_name: String,
22790 cx: &mut App,
22791 ) -> Option<Task<Result<ProjectTransaction>>>;
22792}
22793
22794pub trait CompletionProvider {
22795 fn completions(
22796 &self,
22797 excerpt_id: ExcerptId,
22798 buffer: &Entity<Buffer>,
22799 buffer_position: text::Anchor,
22800 trigger: CompletionContext,
22801 window: &mut Window,
22802 cx: &mut Context<Editor>,
22803 ) -> Task<Result<Vec<CompletionResponse>>>;
22804
22805 fn resolve_completions(
22806 &self,
22807 _buffer: Entity<Buffer>,
22808 _completion_indices: Vec<usize>,
22809 _completions: Rc<RefCell<Box<[Completion]>>>,
22810 _cx: &mut Context<Editor>,
22811 ) -> Task<Result<bool>> {
22812 Task::ready(Ok(false))
22813 }
22814
22815 fn apply_additional_edits_for_completion(
22816 &self,
22817 _buffer: Entity<Buffer>,
22818 _completions: Rc<RefCell<Box<[Completion]>>>,
22819 _completion_index: usize,
22820 _push_to_history: bool,
22821 _cx: &mut Context<Editor>,
22822 ) -> Task<Result<Option<language::Transaction>>> {
22823 Task::ready(Ok(None))
22824 }
22825
22826 fn is_completion_trigger(
22827 &self,
22828 buffer: &Entity<Buffer>,
22829 position: language::Anchor,
22830 text: &str,
22831 trigger_in_words: bool,
22832 menu_is_open: bool,
22833 cx: &mut Context<Editor>,
22834 ) -> bool;
22835
22836 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22837
22838 fn sort_completions(&self) -> bool {
22839 true
22840 }
22841
22842 fn filter_completions(&self) -> bool {
22843 true
22844 }
22845}
22846
22847pub trait CodeActionProvider {
22848 fn id(&self) -> Arc<str>;
22849
22850 fn code_actions(
22851 &self,
22852 buffer: &Entity<Buffer>,
22853 range: Range<text::Anchor>,
22854 window: &mut Window,
22855 cx: &mut App,
22856 ) -> Task<Result<Vec<CodeAction>>>;
22857
22858 fn apply_code_action(
22859 &self,
22860 buffer_handle: Entity<Buffer>,
22861 action: CodeAction,
22862 excerpt_id: ExcerptId,
22863 push_to_history: bool,
22864 window: &mut Window,
22865 cx: &mut App,
22866 ) -> Task<Result<ProjectTransaction>>;
22867}
22868
22869impl CodeActionProvider for Entity<Project> {
22870 fn id(&self) -> Arc<str> {
22871 "project".into()
22872 }
22873
22874 fn code_actions(
22875 &self,
22876 buffer: &Entity<Buffer>,
22877 range: Range<text::Anchor>,
22878 _window: &mut Window,
22879 cx: &mut App,
22880 ) -> Task<Result<Vec<CodeAction>>> {
22881 self.update(cx, |project, cx| {
22882 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22883 let code_actions = project.code_actions(buffer, range, None, cx);
22884 cx.background_spawn(async move {
22885 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22886 Ok(code_lens_actions
22887 .context("code lens fetch")?
22888 .into_iter()
22889 .flatten()
22890 .chain(
22891 code_actions
22892 .context("code action fetch")?
22893 .into_iter()
22894 .flatten(),
22895 )
22896 .collect())
22897 })
22898 })
22899 }
22900
22901 fn apply_code_action(
22902 &self,
22903 buffer_handle: Entity<Buffer>,
22904 action: CodeAction,
22905 _excerpt_id: ExcerptId,
22906 push_to_history: bool,
22907 _window: &mut Window,
22908 cx: &mut App,
22909 ) -> Task<Result<ProjectTransaction>> {
22910 self.update(cx, |project, cx| {
22911 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22912 })
22913 }
22914}
22915
22916fn snippet_completions(
22917 project: &Project,
22918 buffer: &Entity<Buffer>,
22919 buffer_position: text::Anchor,
22920 cx: &mut App,
22921) -> Task<Result<CompletionResponse>> {
22922 let languages = buffer.read(cx).languages_at(buffer_position);
22923 let snippet_store = project.snippets().read(cx);
22924
22925 let scopes: Vec<_> = languages
22926 .iter()
22927 .filter_map(|language| {
22928 let language_name = language.lsp_id();
22929 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22930
22931 if snippets.is_empty() {
22932 None
22933 } else {
22934 Some((language.default_scope(), snippets))
22935 }
22936 })
22937 .collect();
22938
22939 if scopes.is_empty() {
22940 return Task::ready(Ok(CompletionResponse {
22941 completions: vec![],
22942 display_options: CompletionDisplayOptions::default(),
22943 is_incomplete: false,
22944 }));
22945 }
22946
22947 let snapshot = buffer.read(cx).text_snapshot();
22948 let executor = cx.background_executor().clone();
22949
22950 cx.background_spawn(async move {
22951 let mut is_incomplete = false;
22952 let mut completions: Vec<Completion> = Vec::new();
22953 for (scope, snippets) in scopes.into_iter() {
22954 let classifier =
22955 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22956
22957 const MAX_WORD_PREFIX_LEN: usize = 128;
22958 let last_word: String = snapshot
22959 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22960 .take(MAX_WORD_PREFIX_LEN)
22961 .take_while(|c| classifier.is_word(*c))
22962 .collect::<String>()
22963 .chars()
22964 .rev()
22965 .collect();
22966
22967 if last_word.is_empty() {
22968 return Ok(CompletionResponse {
22969 completions: vec![],
22970 display_options: CompletionDisplayOptions::default(),
22971 is_incomplete: true,
22972 });
22973 }
22974
22975 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22976 let to_lsp = |point: &text::Anchor| {
22977 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22978 point_to_lsp(end)
22979 };
22980 let lsp_end = to_lsp(&buffer_position);
22981
22982 let candidates = snippets
22983 .iter()
22984 .enumerate()
22985 .flat_map(|(ix, snippet)| {
22986 snippet
22987 .prefix
22988 .iter()
22989 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22990 })
22991 .collect::<Vec<StringMatchCandidate>>();
22992
22993 const MAX_RESULTS: usize = 100;
22994 let mut matches = fuzzy::match_strings(
22995 &candidates,
22996 &last_word,
22997 last_word.chars().any(|c| c.is_uppercase()),
22998 true,
22999 MAX_RESULTS,
23000 &Default::default(),
23001 executor.clone(),
23002 )
23003 .await;
23004
23005 if matches.len() >= MAX_RESULTS {
23006 is_incomplete = true;
23007 }
23008
23009 // Remove all candidates where the query's start does not match the start of any word in the candidate
23010 if let Some(query_start) = last_word.chars().next() {
23011 matches.retain(|string_match| {
23012 split_words(&string_match.string).any(|word| {
23013 // Check that the first codepoint of the word as lowercase matches the first
23014 // codepoint of the query as lowercase
23015 word.chars()
23016 .flat_map(|codepoint| codepoint.to_lowercase())
23017 .zip(query_start.to_lowercase())
23018 .all(|(word_cp, query_cp)| word_cp == query_cp)
23019 })
23020 });
23021 }
23022
23023 let matched_strings = matches
23024 .into_iter()
23025 .map(|m| m.string)
23026 .collect::<HashSet<_>>();
23027
23028 completions.extend(snippets.iter().filter_map(|snippet| {
23029 let matching_prefix = snippet
23030 .prefix
23031 .iter()
23032 .find(|prefix| matched_strings.contains(*prefix))?;
23033 let start = as_offset - last_word.len();
23034 let start = snapshot.anchor_before(start);
23035 let range = start..buffer_position;
23036 let lsp_start = to_lsp(&start);
23037 let lsp_range = lsp::Range {
23038 start: lsp_start,
23039 end: lsp_end,
23040 };
23041 Some(Completion {
23042 replace_range: range,
23043 new_text: snippet.body.clone(),
23044 source: CompletionSource::Lsp {
23045 insert_range: None,
23046 server_id: LanguageServerId(usize::MAX),
23047 resolved: true,
23048 lsp_completion: Box::new(lsp::CompletionItem {
23049 label: snippet.prefix.first().unwrap().clone(),
23050 kind: Some(CompletionItemKind::SNIPPET),
23051 label_details: snippet.description.as_ref().map(|description| {
23052 lsp::CompletionItemLabelDetails {
23053 detail: Some(description.clone()),
23054 description: None,
23055 }
23056 }),
23057 insert_text_format: Some(InsertTextFormat::SNIPPET),
23058 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23059 lsp::InsertReplaceEdit {
23060 new_text: snippet.body.clone(),
23061 insert: lsp_range,
23062 replace: lsp_range,
23063 },
23064 )),
23065 filter_text: Some(snippet.body.clone()),
23066 sort_text: Some(char::MAX.to_string()),
23067 ..lsp::CompletionItem::default()
23068 }),
23069 lsp_defaults: None,
23070 },
23071 label: CodeLabel::plain(matching_prefix.clone(), None),
23072 icon_path: None,
23073 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23074 single_line: snippet.name.clone().into(),
23075 plain_text: snippet
23076 .description
23077 .clone()
23078 .map(|description| description.into()),
23079 }),
23080 insert_text_mode: None,
23081 confirm: None,
23082 })
23083 }))
23084 }
23085
23086 Ok(CompletionResponse {
23087 completions,
23088 display_options: CompletionDisplayOptions::default(),
23089 is_incomplete,
23090 })
23091 })
23092}
23093
23094impl CompletionProvider for Entity<Project> {
23095 fn completions(
23096 &self,
23097 _excerpt_id: ExcerptId,
23098 buffer: &Entity<Buffer>,
23099 buffer_position: text::Anchor,
23100 options: CompletionContext,
23101 _window: &mut Window,
23102 cx: &mut Context<Editor>,
23103 ) -> Task<Result<Vec<CompletionResponse>>> {
23104 self.update(cx, |project, cx| {
23105 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23106 let project_completions = project.completions(buffer, buffer_position, options, cx);
23107 cx.background_spawn(async move {
23108 let mut responses = project_completions.await?;
23109 let snippets = snippets.await?;
23110 if !snippets.completions.is_empty() {
23111 responses.push(snippets);
23112 }
23113 Ok(responses)
23114 })
23115 })
23116 }
23117
23118 fn resolve_completions(
23119 &self,
23120 buffer: Entity<Buffer>,
23121 completion_indices: Vec<usize>,
23122 completions: Rc<RefCell<Box<[Completion]>>>,
23123 cx: &mut Context<Editor>,
23124 ) -> Task<Result<bool>> {
23125 self.update(cx, |project, cx| {
23126 project.lsp_store().update(cx, |lsp_store, cx| {
23127 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23128 })
23129 })
23130 }
23131
23132 fn apply_additional_edits_for_completion(
23133 &self,
23134 buffer: Entity<Buffer>,
23135 completions: Rc<RefCell<Box<[Completion]>>>,
23136 completion_index: usize,
23137 push_to_history: bool,
23138 cx: &mut Context<Editor>,
23139 ) -> Task<Result<Option<language::Transaction>>> {
23140 self.update(cx, |project, cx| {
23141 project.lsp_store().update(cx, |lsp_store, cx| {
23142 lsp_store.apply_additional_edits_for_completion(
23143 buffer,
23144 completions,
23145 completion_index,
23146 push_to_history,
23147 cx,
23148 )
23149 })
23150 })
23151 }
23152
23153 fn is_completion_trigger(
23154 &self,
23155 buffer: &Entity<Buffer>,
23156 position: language::Anchor,
23157 text: &str,
23158 trigger_in_words: bool,
23159 menu_is_open: bool,
23160 cx: &mut Context<Editor>,
23161 ) -> bool {
23162 let mut chars = text.chars();
23163 let char = if let Some(char) = chars.next() {
23164 char
23165 } else {
23166 return false;
23167 };
23168 if chars.next().is_some() {
23169 return false;
23170 }
23171
23172 let buffer = buffer.read(cx);
23173 let snapshot = buffer.snapshot();
23174 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23175 return false;
23176 }
23177 let classifier = snapshot
23178 .char_classifier_at(position)
23179 .scope_context(Some(CharScopeContext::Completion));
23180 if trigger_in_words && classifier.is_word(char) {
23181 return true;
23182 }
23183
23184 buffer.completion_triggers().contains(text)
23185 }
23186}
23187
23188impl SemanticsProvider for Entity<Project> {
23189 fn hover(
23190 &self,
23191 buffer: &Entity<Buffer>,
23192 position: text::Anchor,
23193 cx: &mut App,
23194 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23195 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23196 }
23197
23198 fn document_highlights(
23199 &self,
23200 buffer: &Entity<Buffer>,
23201 position: text::Anchor,
23202 cx: &mut App,
23203 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23204 Some(self.update(cx, |project, cx| {
23205 project.document_highlights(buffer, position, cx)
23206 }))
23207 }
23208
23209 fn definitions(
23210 &self,
23211 buffer: &Entity<Buffer>,
23212 position: text::Anchor,
23213 kind: GotoDefinitionKind,
23214 cx: &mut App,
23215 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23216 Some(self.update(cx, |project, cx| match kind {
23217 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23218 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23219 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23220 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23221 }))
23222 }
23223
23224 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23225 self.update(cx, |project, cx| {
23226 if project
23227 .active_debug_session(cx)
23228 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23229 {
23230 return true;
23231 }
23232
23233 buffer.update(cx, |buffer, cx| {
23234 project.any_language_server_supports_inlay_hints(buffer, cx)
23235 })
23236 })
23237 }
23238
23239 fn inline_values(
23240 &self,
23241 buffer_handle: Entity<Buffer>,
23242 range: Range<text::Anchor>,
23243 cx: &mut App,
23244 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23245 self.update(cx, |project, cx| {
23246 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23247
23248 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23249 })
23250 }
23251
23252 fn applicable_inlay_chunks(
23253 &self,
23254 buffer: &Entity<Buffer>,
23255 ranges: &[Range<text::Anchor>],
23256 cx: &mut App,
23257 ) -> Vec<Range<BufferRow>> {
23258 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23259 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23260 })
23261 }
23262
23263 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23264 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23265 lsp_store.invalidate_inlay_hints(for_buffers)
23266 });
23267 }
23268
23269 fn inlay_hints(
23270 &self,
23271 invalidate: InvalidationStrategy,
23272 buffer: Entity<Buffer>,
23273 ranges: Vec<Range<text::Anchor>>,
23274 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23275 cx: &mut App,
23276 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23277 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23278 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23279 }))
23280 }
23281
23282 fn range_for_rename(
23283 &self,
23284 buffer: &Entity<Buffer>,
23285 position: text::Anchor,
23286 cx: &mut App,
23287 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23288 Some(self.update(cx, |project, cx| {
23289 let buffer = buffer.clone();
23290 let task = project.prepare_rename(buffer.clone(), position, cx);
23291 cx.spawn(async move |_, cx| {
23292 Ok(match task.await? {
23293 PrepareRenameResponse::Success(range) => Some(range),
23294 PrepareRenameResponse::InvalidPosition => None,
23295 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23296 // Fallback on using TreeSitter info to determine identifier range
23297 buffer.read_with(cx, |buffer, _| {
23298 let snapshot = buffer.snapshot();
23299 let (range, kind) = snapshot.surrounding_word(position, None);
23300 if kind != Some(CharKind::Word) {
23301 return None;
23302 }
23303 Some(
23304 snapshot.anchor_before(range.start)
23305 ..snapshot.anchor_after(range.end),
23306 )
23307 })?
23308 }
23309 })
23310 })
23311 }))
23312 }
23313
23314 fn perform_rename(
23315 &self,
23316 buffer: &Entity<Buffer>,
23317 position: text::Anchor,
23318 new_name: String,
23319 cx: &mut App,
23320 ) -> Option<Task<Result<ProjectTransaction>>> {
23321 Some(self.update(cx, |project, cx| {
23322 project.perform_rename(buffer.clone(), position, new_name, cx)
23323 }))
23324 }
23325}
23326
23327fn consume_contiguous_rows(
23328 contiguous_row_selections: &mut Vec<Selection<Point>>,
23329 selection: &Selection<Point>,
23330 display_map: &DisplaySnapshot,
23331 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23332) -> (MultiBufferRow, MultiBufferRow) {
23333 contiguous_row_selections.push(selection.clone());
23334 let start_row = starting_row(selection, display_map);
23335 let mut end_row = ending_row(selection, display_map);
23336
23337 while let Some(next_selection) = selections.peek() {
23338 if next_selection.start.row <= end_row.0 {
23339 end_row = ending_row(next_selection, display_map);
23340 contiguous_row_selections.push(selections.next().unwrap().clone());
23341 } else {
23342 break;
23343 }
23344 }
23345 (start_row, end_row)
23346}
23347
23348fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23349 if selection.start.column > 0 {
23350 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23351 } else {
23352 MultiBufferRow(selection.start.row)
23353 }
23354}
23355
23356fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23357 if next_selection.end.column > 0 || next_selection.is_empty() {
23358 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23359 } else {
23360 MultiBufferRow(next_selection.end.row)
23361 }
23362}
23363
23364impl EditorSnapshot {
23365 pub fn remote_selections_in_range<'a>(
23366 &'a self,
23367 range: &'a Range<Anchor>,
23368 collaboration_hub: &dyn CollaborationHub,
23369 cx: &'a App,
23370 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23371 let participant_names = collaboration_hub.user_names(cx);
23372 let participant_indices = collaboration_hub.user_participant_indices(cx);
23373 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23374 let collaborators_by_replica_id = collaborators_by_peer_id
23375 .values()
23376 .map(|collaborator| (collaborator.replica_id, collaborator))
23377 .collect::<HashMap<_, _>>();
23378 self.buffer_snapshot()
23379 .selections_in_range(range, false)
23380 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23381 if replica_id == ReplicaId::AGENT {
23382 Some(RemoteSelection {
23383 replica_id,
23384 selection,
23385 cursor_shape,
23386 line_mode,
23387 collaborator_id: CollaboratorId::Agent,
23388 user_name: Some("Agent".into()),
23389 color: cx.theme().players().agent(),
23390 })
23391 } else {
23392 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23393 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23394 let user_name = participant_names.get(&collaborator.user_id).cloned();
23395 Some(RemoteSelection {
23396 replica_id,
23397 selection,
23398 cursor_shape,
23399 line_mode,
23400 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23401 user_name,
23402 color: if let Some(index) = participant_index {
23403 cx.theme().players().color_for_participant(index.0)
23404 } else {
23405 cx.theme().players().absent()
23406 },
23407 })
23408 }
23409 })
23410 }
23411
23412 pub fn hunks_for_ranges(
23413 &self,
23414 ranges: impl IntoIterator<Item = Range<Point>>,
23415 ) -> Vec<MultiBufferDiffHunk> {
23416 let mut hunks = Vec::new();
23417 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23418 HashMap::default();
23419 for query_range in ranges {
23420 let query_rows =
23421 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23422 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23423 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23424 ) {
23425 // Include deleted hunks that are adjacent to the query range, because
23426 // otherwise they would be missed.
23427 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23428 if hunk.status().is_deleted() {
23429 intersects_range |= hunk.row_range.start == query_rows.end;
23430 intersects_range |= hunk.row_range.end == query_rows.start;
23431 }
23432 if intersects_range {
23433 if !processed_buffer_rows
23434 .entry(hunk.buffer_id)
23435 .or_default()
23436 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23437 {
23438 continue;
23439 }
23440 hunks.push(hunk);
23441 }
23442 }
23443 }
23444
23445 hunks
23446 }
23447
23448 fn display_diff_hunks_for_rows<'a>(
23449 &'a self,
23450 display_rows: Range<DisplayRow>,
23451 folded_buffers: &'a HashSet<BufferId>,
23452 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23453 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23454 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23455
23456 self.buffer_snapshot()
23457 .diff_hunks_in_range(buffer_start..buffer_end)
23458 .filter_map(|hunk| {
23459 if folded_buffers.contains(&hunk.buffer_id) {
23460 return None;
23461 }
23462
23463 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23464 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23465
23466 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23467 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23468
23469 let display_hunk = if hunk_display_start.column() != 0 {
23470 DisplayDiffHunk::Folded {
23471 display_row: hunk_display_start.row(),
23472 }
23473 } else {
23474 let mut end_row = hunk_display_end.row();
23475 if hunk_display_end.column() > 0 {
23476 end_row.0 += 1;
23477 }
23478 let is_created_file = hunk.is_created_file();
23479 DisplayDiffHunk::Unfolded {
23480 status: hunk.status(),
23481 diff_base_byte_range: hunk.diff_base_byte_range,
23482 display_row_range: hunk_display_start.row()..end_row,
23483 multi_buffer_range: Anchor::range_in_buffer(
23484 hunk.excerpt_id,
23485 hunk.buffer_id,
23486 hunk.buffer_range,
23487 ),
23488 is_created_file,
23489 }
23490 };
23491
23492 Some(display_hunk)
23493 })
23494 }
23495
23496 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23497 self.display_snapshot
23498 .buffer_snapshot()
23499 .language_at(position)
23500 }
23501
23502 pub fn is_focused(&self) -> bool {
23503 self.is_focused
23504 }
23505
23506 pub fn placeholder_text(&self) -> Option<String> {
23507 self.placeholder_display_snapshot
23508 .as_ref()
23509 .map(|display_map| display_map.text())
23510 }
23511
23512 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23513 self.scroll_anchor.scroll_position(&self.display_snapshot)
23514 }
23515
23516 fn gutter_dimensions(
23517 &self,
23518 font_id: FontId,
23519 font_size: Pixels,
23520 max_line_number_width: Pixels,
23521 cx: &App,
23522 ) -> Option<GutterDimensions> {
23523 if !self.show_gutter {
23524 return None;
23525 }
23526
23527 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23528 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23529
23530 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23531 matches!(
23532 ProjectSettings::get_global(cx).git.git_gutter,
23533 GitGutterSetting::TrackedFiles
23534 )
23535 });
23536 let gutter_settings = EditorSettings::get_global(cx).gutter;
23537 let show_line_numbers = self
23538 .show_line_numbers
23539 .unwrap_or(gutter_settings.line_numbers);
23540 let line_gutter_width = if show_line_numbers {
23541 // Avoid flicker-like gutter resizes when the line number gains another digit by
23542 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23543 let min_width_for_number_on_gutter =
23544 ch_advance * gutter_settings.min_line_number_digits as f32;
23545 max_line_number_width.max(min_width_for_number_on_gutter)
23546 } else {
23547 0.0.into()
23548 };
23549
23550 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23551 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23552
23553 let git_blame_entries_width =
23554 self.git_blame_gutter_max_author_length
23555 .map(|max_author_length| {
23556 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23557 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23558
23559 /// The number of characters to dedicate to gaps and margins.
23560 const SPACING_WIDTH: usize = 4;
23561
23562 let max_char_count = max_author_length.min(renderer.max_author_length())
23563 + ::git::SHORT_SHA_LENGTH
23564 + MAX_RELATIVE_TIMESTAMP.len()
23565 + SPACING_WIDTH;
23566
23567 ch_advance * max_char_count
23568 });
23569
23570 let is_singleton = self.buffer_snapshot().is_singleton();
23571
23572 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23573 left_padding += if !is_singleton {
23574 ch_width * 4.0
23575 } else if show_runnables || show_breakpoints {
23576 ch_width * 3.0
23577 } else if show_git_gutter && show_line_numbers {
23578 ch_width * 2.0
23579 } else if show_git_gutter || show_line_numbers {
23580 ch_width
23581 } else {
23582 px(0.)
23583 };
23584
23585 let shows_folds = is_singleton && gutter_settings.folds;
23586
23587 let right_padding = if shows_folds && show_line_numbers {
23588 ch_width * 4.0
23589 } else if shows_folds || (!is_singleton && show_line_numbers) {
23590 ch_width * 3.0
23591 } else if show_line_numbers {
23592 ch_width
23593 } else {
23594 px(0.)
23595 };
23596
23597 Some(GutterDimensions {
23598 left_padding,
23599 right_padding,
23600 width: line_gutter_width + left_padding + right_padding,
23601 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23602 git_blame_entries_width,
23603 })
23604 }
23605
23606 pub fn render_crease_toggle(
23607 &self,
23608 buffer_row: MultiBufferRow,
23609 row_contains_cursor: bool,
23610 editor: Entity<Editor>,
23611 window: &mut Window,
23612 cx: &mut App,
23613 ) -> Option<AnyElement> {
23614 let folded = self.is_line_folded(buffer_row);
23615 let mut is_foldable = false;
23616
23617 if let Some(crease) = self
23618 .crease_snapshot
23619 .query_row(buffer_row, self.buffer_snapshot())
23620 {
23621 is_foldable = true;
23622 match crease {
23623 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23624 if let Some(render_toggle) = render_toggle {
23625 let toggle_callback =
23626 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23627 if folded {
23628 editor.update(cx, |editor, cx| {
23629 editor.fold_at(buffer_row, window, cx)
23630 });
23631 } else {
23632 editor.update(cx, |editor, cx| {
23633 editor.unfold_at(buffer_row, window, cx)
23634 });
23635 }
23636 });
23637 return Some((render_toggle)(
23638 buffer_row,
23639 folded,
23640 toggle_callback,
23641 window,
23642 cx,
23643 ));
23644 }
23645 }
23646 }
23647 }
23648
23649 is_foldable |= self.starts_indent(buffer_row);
23650
23651 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23652 Some(
23653 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23654 .toggle_state(folded)
23655 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23656 if folded {
23657 this.unfold_at(buffer_row, window, cx);
23658 } else {
23659 this.fold_at(buffer_row, window, cx);
23660 }
23661 }))
23662 .into_any_element(),
23663 )
23664 } else {
23665 None
23666 }
23667 }
23668
23669 pub fn render_crease_trailer(
23670 &self,
23671 buffer_row: MultiBufferRow,
23672 window: &mut Window,
23673 cx: &mut App,
23674 ) -> Option<AnyElement> {
23675 let folded = self.is_line_folded(buffer_row);
23676 if let Crease::Inline { render_trailer, .. } = self
23677 .crease_snapshot
23678 .query_row(buffer_row, self.buffer_snapshot())?
23679 {
23680 let render_trailer = render_trailer.as_ref()?;
23681 Some(render_trailer(buffer_row, folded, window, cx))
23682 } else {
23683 None
23684 }
23685 }
23686}
23687
23688impl Deref for EditorSnapshot {
23689 type Target = DisplaySnapshot;
23690
23691 fn deref(&self) -> &Self::Target {
23692 &self.display_snapshot
23693 }
23694}
23695
23696#[derive(Clone, Debug, PartialEq, Eq)]
23697pub enum EditorEvent {
23698 InputIgnored {
23699 text: Arc<str>,
23700 },
23701 InputHandled {
23702 utf16_range_to_replace: Option<Range<isize>>,
23703 text: Arc<str>,
23704 },
23705 ExcerptsAdded {
23706 buffer: Entity<Buffer>,
23707 predecessor: ExcerptId,
23708 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23709 },
23710 ExcerptsRemoved {
23711 ids: Vec<ExcerptId>,
23712 removed_buffer_ids: Vec<BufferId>,
23713 },
23714 BufferFoldToggled {
23715 ids: Vec<ExcerptId>,
23716 folded: bool,
23717 },
23718 ExcerptsEdited {
23719 ids: Vec<ExcerptId>,
23720 },
23721 ExcerptsExpanded {
23722 ids: Vec<ExcerptId>,
23723 },
23724 BufferEdited,
23725 Edited {
23726 transaction_id: clock::Lamport,
23727 },
23728 Reparsed(BufferId),
23729 Focused,
23730 FocusedIn,
23731 Blurred,
23732 DirtyChanged,
23733 Saved,
23734 TitleChanged,
23735 SelectionsChanged {
23736 local: bool,
23737 },
23738 ScrollPositionChanged {
23739 local: bool,
23740 autoscroll: bool,
23741 },
23742 TransactionUndone {
23743 transaction_id: clock::Lamport,
23744 },
23745 TransactionBegun {
23746 transaction_id: clock::Lamport,
23747 },
23748 CursorShapeChanged,
23749 BreadcrumbsChanged,
23750 PushedToNavHistory {
23751 anchor: Anchor,
23752 is_deactivate: bool,
23753 },
23754}
23755
23756impl EventEmitter<EditorEvent> for Editor {}
23757
23758impl Focusable for Editor {
23759 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23760 self.focus_handle.clone()
23761 }
23762}
23763
23764impl Render for Editor {
23765 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23766 let settings = ThemeSettings::get_global(cx);
23767
23768 let mut text_style = match self.mode {
23769 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23770 color: cx.theme().colors().editor_foreground,
23771 font_family: settings.ui_font.family.clone(),
23772 font_features: settings.ui_font.features.clone(),
23773 font_fallbacks: settings.ui_font.fallbacks.clone(),
23774 font_size: rems(0.875).into(),
23775 font_weight: settings.ui_font.weight,
23776 line_height: relative(settings.buffer_line_height.value()),
23777 ..Default::default()
23778 },
23779 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23780 color: cx.theme().colors().editor_foreground,
23781 font_family: settings.buffer_font.family.clone(),
23782 font_features: settings.buffer_font.features.clone(),
23783 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23784 font_size: settings.buffer_font_size(cx).into(),
23785 font_weight: settings.buffer_font.weight,
23786 line_height: relative(settings.buffer_line_height.value()),
23787 ..Default::default()
23788 },
23789 };
23790 if let Some(text_style_refinement) = &self.text_style_refinement {
23791 text_style.refine(text_style_refinement)
23792 }
23793
23794 let background = match self.mode {
23795 EditorMode::SingleLine => cx.theme().system().transparent,
23796 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23797 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23798 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23799 };
23800
23801 EditorElement::new(
23802 &cx.entity(),
23803 EditorStyle {
23804 background,
23805 border: cx.theme().colors().border,
23806 local_player: cx.theme().players().local(),
23807 text: text_style,
23808 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23809 syntax: cx.theme().syntax().clone(),
23810 status: cx.theme().status().clone(),
23811 inlay_hints_style: make_inlay_hints_style(cx),
23812 edit_prediction_styles: make_suggestion_styles(cx),
23813 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23814 show_underlines: self.diagnostics_enabled(),
23815 },
23816 )
23817 }
23818}
23819
23820impl EntityInputHandler for Editor {
23821 fn text_for_range(
23822 &mut self,
23823 range_utf16: Range<usize>,
23824 adjusted_range: &mut Option<Range<usize>>,
23825 _: &mut Window,
23826 cx: &mut Context<Self>,
23827 ) -> Option<String> {
23828 let snapshot = self.buffer.read(cx).read(cx);
23829 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23830 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23831 if (start.0..end.0) != range_utf16 {
23832 adjusted_range.replace(start.0..end.0);
23833 }
23834 Some(snapshot.text_for_range(start..end).collect())
23835 }
23836
23837 fn selected_text_range(
23838 &mut self,
23839 ignore_disabled_input: bool,
23840 _: &mut Window,
23841 cx: &mut Context<Self>,
23842 ) -> Option<UTF16Selection> {
23843 // Prevent the IME menu from appearing when holding down an alphabetic key
23844 // while input is disabled.
23845 if !ignore_disabled_input && !self.input_enabled {
23846 return None;
23847 }
23848
23849 let selection = self
23850 .selections
23851 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23852 let range = selection.range();
23853
23854 Some(UTF16Selection {
23855 range: range.start.0..range.end.0,
23856 reversed: selection.reversed,
23857 })
23858 }
23859
23860 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23861 let snapshot = self.buffer.read(cx).read(cx);
23862 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23863 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23864 }
23865
23866 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23867 self.clear_highlights::<InputComposition>(cx);
23868 self.ime_transaction.take();
23869 }
23870
23871 fn replace_text_in_range(
23872 &mut self,
23873 range_utf16: Option<Range<usize>>,
23874 text: &str,
23875 window: &mut Window,
23876 cx: &mut Context<Self>,
23877 ) {
23878 if !self.input_enabled {
23879 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23880 return;
23881 }
23882
23883 self.transact(window, cx, |this, window, cx| {
23884 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23885 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23886 Some(this.selection_replacement_ranges(range_utf16, cx))
23887 } else {
23888 this.marked_text_ranges(cx)
23889 };
23890
23891 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23892 let newest_selection_id = this.selections.newest_anchor().id;
23893 this.selections
23894 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23895 .iter()
23896 .zip(ranges_to_replace.iter())
23897 .find_map(|(selection, range)| {
23898 if selection.id == newest_selection_id {
23899 Some(
23900 (range.start.0 as isize - selection.head().0 as isize)
23901 ..(range.end.0 as isize - selection.head().0 as isize),
23902 )
23903 } else {
23904 None
23905 }
23906 })
23907 });
23908
23909 cx.emit(EditorEvent::InputHandled {
23910 utf16_range_to_replace: range_to_replace,
23911 text: text.into(),
23912 });
23913
23914 if let Some(new_selected_ranges) = new_selected_ranges {
23915 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23916 selections.select_ranges(new_selected_ranges)
23917 });
23918 this.backspace(&Default::default(), window, cx);
23919 }
23920
23921 this.handle_input(text, window, cx);
23922 });
23923
23924 if let Some(transaction) = self.ime_transaction {
23925 self.buffer.update(cx, |buffer, cx| {
23926 buffer.group_until_transaction(transaction, cx);
23927 });
23928 }
23929
23930 self.unmark_text(window, cx);
23931 }
23932
23933 fn replace_and_mark_text_in_range(
23934 &mut self,
23935 range_utf16: Option<Range<usize>>,
23936 text: &str,
23937 new_selected_range_utf16: Option<Range<usize>>,
23938 window: &mut Window,
23939 cx: &mut Context<Self>,
23940 ) {
23941 if !self.input_enabled {
23942 return;
23943 }
23944
23945 let transaction = self.transact(window, cx, |this, window, cx| {
23946 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23947 let snapshot = this.buffer.read(cx).read(cx);
23948 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23949 for marked_range in &mut marked_ranges {
23950 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23951 marked_range.start.0 += relative_range_utf16.start;
23952 marked_range.start =
23953 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23954 marked_range.end =
23955 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23956 }
23957 }
23958 Some(marked_ranges)
23959 } else if let Some(range_utf16) = range_utf16 {
23960 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23961 Some(this.selection_replacement_ranges(range_utf16, cx))
23962 } else {
23963 None
23964 };
23965
23966 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23967 let newest_selection_id = this.selections.newest_anchor().id;
23968 this.selections
23969 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23970 .iter()
23971 .zip(ranges_to_replace.iter())
23972 .find_map(|(selection, range)| {
23973 if selection.id == newest_selection_id {
23974 Some(
23975 (range.start.0 as isize - selection.head().0 as isize)
23976 ..(range.end.0 as isize - selection.head().0 as isize),
23977 )
23978 } else {
23979 None
23980 }
23981 })
23982 });
23983
23984 cx.emit(EditorEvent::InputHandled {
23985 utf16_range_to_replace: range_to_replace,
23986 text: text.into(),
23987 });
23988
23989 if let Some(ranges) = ranges_to_replace {
23990 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23991 s.select_ranges(ranges)
23992 });
23993 }
23994
23995 let marked_ranges = {
23996 let snapshot = this.buffer.read(cx).read(cx);
23997 this.selections
23998 .disjoint_anchors_arc()
23999 .iter()
24000 .map(|selection| {
24001 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24002 })
24003 .collect::<Vec<_>>()
24004 };
24005
24006 if text.is_empty() {
24007 this.unmark_text(window, cx);
24008 } else {
24009 this.highlight_text::<InputComposition>(
24010 marked_ranges.clone(),
24011 HighlightStyle {
24012 underline: Some(UnderlineStyle {
24013 thickness: px(1.),
24014 color: None,
24015 wavy: false,
24016 }),
24017 ..Default::default()
24018 },
24019 cx,
24020 );
24021 }
24022
24023 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24024 let use_autoclose = this.use_autoclose;
24025 let use_auto_surround = this.use_auto_surround;
24026 this.set_use_autoclose(false);
24027 this.set_use_auto_surround(false);
24028 this.handle_input(text, window, cx);
24029 this.set_use_autoclose(use_autoclose);
24030 this.set_use_auto_surround(use_auto_surround);
24031
24032 if let Some(new_selected_range) = new_selected_range_utf16 {
24033 let snapshot = this.buffer.read(cx).read(cx);
24034 let new_selected_ranges = marked_ranges
24035 .into_iter()
24036 .map(|marked_range| {
24037 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24038 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24039 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24040 snapshot.clip_offset_utf16(new_start, Bias::Left)
24041 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24042 })
24043 .collect::<Vec<_>>();
24044
24045 drop(snapshot);
24046 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24047 selections.select_ranges(new_selected_ranges)
24048 });
24049 }
24050 });
24051
24052 self.ime_transaction = self.ime_transaction.or(transaction);
24053 if let Some(transaction) = self.ime_transaction {
24054 self.buffer.update(cx, |buffer, cx| {
24055 buffer.group_until_transaction(transaction, cx);
24056 });
24057 }
24058
24059 if self.text_highlights::<InputComposition>(cx).is_none() {
24060 self.ime_transaction.take();
24061 }
24062 }
24063
24064 fn bounds_for_range(
24065 &mut self,
24066 range_utf16: Range<usize>,
24067 element_bounds: gpui::Bounds<Pixels>,
24068 window: &mut Window,
24069 cx: &mut Context<Self>,
24070 ) -> Option<gpui::Bounds<Pixels>> {
24071 let text_layout_details = self.text_layout_details(window);
24072 let CharacterDimensions {
24073 em_width,
24074 em_advance,
24075 line_height,
24076 } = self.character_dimensions(window);
24077
24078 let snapshot = self.snapshot(window, cx);
24079 let scroll_position = snapshot.scroll_position();
24080 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24081
24082 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24083 let x = Pixels::from(
24084 ScrollOffset::from(
24085 snapshot.x_for_display_point(start, &text_layout_details)
24086 + self.gutter_dimensions.full_width(),
24087 ) - scroll_left,
24088 );
24089 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24090
24091 Some(Bounds {
24092 origin: element_bounds.origin + point(x, y),
24093 size: size(em_width, line_height),
24094 })
24095 }
24096
24097 fn character_index_for_point(
24098 &mut self,
24099 point: gpui::Point<Pixels>,
24100 _window: &mut Window,
24101 _cx: &mut Context<Self>,
24102 ) -> Option<usize> {
24103 let position_map = self.last_position_map.as_ref()?;
24104 if !position_map.text_hitbox.contains(&point) {
24105 return None;
24106 }
24107 let display_point = position_map.point_for_position(point).previous_valid;
24108 let anchor = position_map
24109 .snapshot
24110 .display_point_to_anchor(display_point, Bias::Left);
24111 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24112 Some(utf16_offset.0)
24113 }
24114}
24115
24116trait SelectionExt {
24117 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24118 fn spanned_rows(
24119 &self,
24120 include_end_if_at_line_start: bool,
24121 map: &DisplaySnapshot,
24122 ) -> Range<MultiBufferRow>;
24123}
24124
24125impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24126 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24127 let start = self
24128 .start
24129 .to_point(map.buffer_snapshot())
24130 .to_display_point(map);
24131 let end = self
24132 .end
24133 .to_point(map.buffer_snapshot())
24134 .to_display_point(map);
24135 if self.reversed {
24136 end..start
24137 } else {
24138 start..end
24139 }
24140 }
24141
24142 fn spanned_rows(
24143 &self,
24144 include_end_if_at_line_start: bool,
24145 map: &DisplaySnapshot,
24146 ) -> Range<MultiBufferRow> {
24147 let start = self.start.to_point(map.buffer_snapshot());
24148 let mut end = self.end.to_point(map.buffer_snapshot());
24149 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24150 end.row -= 1;
24151 }
24152
24153 let buffer_start = map.prev_line_boundary(start).0;
24154 let buffer_end = map.next_line_boundary(end).0;
24155 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24156 }
24157}
24158
24159impl<T: InvalidationRegion> InvalidationStack<T> {
24160 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24161 where
24162 S: Clone + ToOffset,
24163 {
24164 while let Some(region) = self.last() {
24165 let all_selections_inside_invalidation_ranges =
24166 if selections.len() == region.ranges().len() {
24167 selections
24168 .iter()
24169 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24170 .all(|(selection, invalidation_range)| {
24171 let head = selection.head().to_offset(buffer);
24172 invalidation_range.start <= head && invalidation_range.end >= head
24173 })
24174 } else {
24175 false
24176 };
24177
24178 if all_selections_inside_invalidation_ranges {
24179 break;
24180 } else {
24181 self.pop();
24182 }
24183 }
24184 }
24185}
24186
24187impl<T> Default for InvalidationStack<T> {
24188 fn default() -> Self {
24189 Self(Default::default())
24190 }
24191}
24192
24193impl<T> Deref for InvalidationStack<T> {
24194 type Target = Vec<T>;
24195
24196 fn deref(&self) -> &Self::Target {
24197 &self.0
24198 }
24199}
24200
24201impl<T> DerefMut for InvalidationStack<T> {
24202 fn deref_mut(&mut self) -> &mut Self::Target {
24203 &mut self.0
24204 }
24205}
24206
24207impl InvalidationRegion for SnippetState {
24208 fn ranges(&self) -> &[Range<Anchor>] {
24209 &self.ranges[self.active_index]
24210 }
24211}
24212
24213fn edit_prediction_edit_text(
24214 current_snapshot: &BufferSnapshot,
24215 edits: &[(Range<Anchor>, String)],
24216 edit_preview: &EditPreview,
24217 include_deletions: bool,
24218 cx: &App,
24219) -> HighlightedText {
24220 let edits = edits
24221 .iter()
24222 .map(|(anchor, text)| {
24223 (
24224 anchor.start.text_anchor..anchor.end.text_anchor,
24225 text.clone(),
24226 )
24227 })
24228 .collect::<Vec<_>>();
24229
24230 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24231}
24232
24233fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24234 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24235 // Just show the raw edit text with basic styling
24236 let mut text = String::new();
24237 let mut highlights = Vec::new();
24238
24239 let insertion_highlight_style = HighlightStyle {
24240 color: Some(cx.theme().colors().text),
24241 ..Default::default()
24242 };
24243
24244 for (_, edit_text) in edits {
24245 let start_offset = text.len();
24246 text.push_str(edit_text);
24247 let end_offset = text.len();
24248
24249 if start_offset < end_offset {
24250 highlights.push((start_offset..end_offset, insertion_highlight_style));
24251 }
24252 }
24253
24254 HighlightedText {
24255 text: text.into(),
24256 highlights,
24257 }
24258}
24259
24260pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24261 match severity {
24262 lsp::DiagnosticSeverity::ERROR => colors.error,
24263 lsp::DiagnosticSeverity::WARNING => colors.warning,
24264 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24265 lsp::DiagnosticSeverity::HINT => colors.info,
24266 _ => colors.ignored,
24267 }
24268}
24269
24270pub fn styled_runs_for_code_label<'a>(
24271 label: &'a CodeLabel,
24272 syntax_theme: &'a theme::SyntaxTheme,
24273) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24274 let fade_out = HighlightStyle {
24275 fade_out: Some(0.35),
24276 ..Default::default()
24277 };
24278
24279 let mut prev_end = label.filter_range.end;
24280 label
24281 .runs
24282 .iter()
24283 .enumerate()
24284 .flat_map(move |(ix, (range, highlight_id))| {
24285 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24286 style
24287 } else {
24288 return Default::default();
24289 };
24290 let muted_style = style.highlight(fade_out);
24291
24292 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24293 if range.start >= label.filter_range.end {
24294 if range.start > prev_end {
24295 runs.push((prev_end..range.start, fade_out));
24296 }
24297 runs.push((range.clone(), muted_style));
24298 } else if range.end <= label.filter_range.end {
24299 runs.push((range.clone(), style));
24300 } else {
24301 runs.push((range.start..label.filter_range.end, style));
24302 runs.push((label.filter_range.end..range.end, muted_style));
24303 }
24304 prev_end = cmp::max(prev_end, range.end);
24305
24306 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24307 runs.push((prev_end..label.text.len(), fade_out));
24308 }
24309
24310 runs
24311 })
24312}
24313
24314pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24315 let mut prev_index = 0;
24316 let mut prev_codepoint: Option<char> = None;
24317 text.char_indices()
24318 .chain([(text.len(), '\0')])
24319 .filter_map(move |(index, codepoint)| {
24320 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24321 let is_boundary = index == text.len()
24322 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24323 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24324 if is_boundary {
24325 let chunk = &text[prev_index..index];
24326 prev_index = index;
24327 Some(chunk)
24328 } else {
24329 None
24330 }
24331 })
24332}
24333
24334pub trait RangeToAnchorExt: Sized {
24335 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24336
24337 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24338 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24339 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24340 }
24341}
24342
24343impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24344 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24345 let start_offset = self.start.to_offset(snapshot);
24346 let end_offset = self.end.to_offset(snapshot);
24347 if start_offset == end_offset {
24348 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24349 } else {
24350 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24351 }
24352 }
24353}
24354
24355pub trait RowExt {
24356 fn as_f64(&self) -> f64;
24357
24358 fn next_row(&self) -> Self;
24359
24360 fn previous_row(&self) -> Self;
24361
24362 fn minus(&self, other: Self) -> u32;
24363}
24364
24365impl RowExt for DisplayRow {
24366 fn as_f64(&self) -> f64 {
24367 self.0 as _
24368 }
24369
24370 fn next_row(&self) -> Self {
24371 Self(self.0 + 1)
24372 }
24373
24374 fn previous_row(&self) -> Self {
24375 Self(self.0.saturating_sub(1))
24376 }
24377
24378 fn minus(&self, other: Self) -> u32 {
24379 self.0 - other.0
24380 }
24381}
24382
24383impl RowExt for MultiBufferRow {
24384 fn as_f64(&self) -> f64 {
24385 self.0 as _
24386 }
24387
24388 fn next_row(&self) -> Self {
24389 Self(self.0 + 1)
24390 }
24391
24392 fn previous_row(&self) -> Self {
24393 Self(self.0.saturating_sub(1))
24394 }
24395
24396 fn minus(&self, other: Self) -> u32 {
24397 self.0 - other.0
24398 }
24399}
24400
24401trait RowRangeExt {
24402 type Row;
24403
24404 fn len(&self) -> usize;
24405
24406 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24407}
24408
24409impl RowRangeExt for Range<MultiBufferRow> {
24410 type Row = MultiBufferRow;
24411
24412 fn len(&self) -> usize {
24413 (self.end.0 - self.start.0) as usize
24414 }
24415
24416 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24417 (self.start.0..self.end.0).map(MultiBufferRow)
24418 }
24419}
24420
24421impl RowRangeExt for Range<DisplayRow> {
24422 type Row = DisplayRow;
24423
24424 fn len(&self) -> usize {
24425 (self.end.0 - self.start.0) as usize
24426 }
24427
24428 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24429 (self.start.0..self.end.0).map(DisplayRow)
24430 }
24431}
24432
24433/// If select range has more than one line, we
24434/// just point the cursor to range.start.
24435fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24436 if range.start.row == range.end.row {
24437 range
24438 } else {
24439 range.start..range.start
24440 }
24441}
24442pub struct KillRing(ClipboardItem);
24443impl Global for KillRing {}
24444
24445const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24446
24447enum BreakpointPromptEditAction {
24448 Log,
24449 Condition,
24450 HitCondition,
24451}
24452
24453struct BreakpointPromptEditor {
24454 pub(crate) prompt: Entity<Editor>,
24455 editor: WeakEntity<Editor>,
24456 breakpoint_anchor: Anchor,
24457 breakpoint: Breakpoint,
24458 edit_action: BreakpointPromptEditAction,
24459 block_ids: HashSet<CustomBlockId>,
24460 editor_margins: Arc<Mutex<EditorMargins>>,
24461 _subscriptions: Vec<Subscription>,
24462}
24463
24464impl BreakpointPromptEditor {
24465 const MAX_LINES: u8 = 4;
24466
24467 fn new(
24468 editor: WeakEntity<Editor>,
24469 breakpoint_anchor: Anchor,
24470 breakpoint: Breakpoint,
24471 edit_action: BreakpointPromptEditAction,
24472 window: &mut Window,
24473 cx: &mut Context<Self>,
24474 ) -> Self {
24475 let base_text = match edit_action {
24476 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24477 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24478 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24479 }
24480 .map(|msg| msg.to_string())
24481 .unwrap_or_default();
24482
24483 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24484 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24485
24486 let prompt = cx.new(|cx| {
24487 let mut prompt = Editor::new(
24488 EditorMode::AutoHeight {
24489 min_lines: 1,
24490 max_lines: Some(Self::MAX_LINES as usize),
24491 },
24492 buffer,
24493 None,
24494 window,
24495 cx,
24496 );
24497 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24498 prompt.set_show_cursor_when_unfocused(false, cx);
24499 prompt.set_placeholder_text(
24500 match edit_action {
24501 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24502 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24503 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24504 },
24505 window,
24506 cx,
24507 );
24508
24509 prompt
24510 });
24511
24512 Self {
24513 prompt,
24514 editor,
24515 breakpoint_anchor,
24516 breakpoint,
24517 edit_action,
24518 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24519 block_ids: Default::default(),
24520 _subscriptions: vec![],
24521 }
24522 }
24523
24524 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24525 self.block_ids.extend(block_ids)
24526 }
24527
24528 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24529 if let Some(editor) = self.editor.upgrade() {
24530 let message = self
24531 .prompt
24532 .read(cx)
24533 .buffer
24534 .read(cx)
24535 .as_singleton()
24536 .expect("A multi buffer in breakpoint prompt isn't possible")
24537 .read(cx)
24538 .as_rope()
24539 .to_string();
24540
24541 editor.update(cx, |editor, cx| {
24542 editor.edit_breakpoint_at_anchor(
24543 self.breakpoint_anchor,
24544 self.breakpoint.clone(),
24545 match self.edit_action {
24546 BreakpointPromptEditAction::Log => {
24547 BreakpointEditAction::EditLogMessage(message.into())
24548 }
24549 BreakpointPromptEditAction::Condition => {
24550 BreakpointEditAction::EditCondition(message.into())
24551 }
24552 BreakpointPromptEditAction::HitCondition => {
24553 BreakpointEditAction::EditHitCondition(message.into())
24554 }
24555 },
24556 cx,
24557 );
24558
24559 editor.remove_blocks(self.block_ids.clone(), None, cx);
24560 cx.focus_self(window);
24561 });
24562 }
24563 }
24564
24565 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24566 self.editor
24567 .update(cx, |editor, cx| {
24568 editor.remove_blocks(self.block_ids.clone(), None, cx);
24569 window.focus(&editor.focus_handle);
24570 })
24571 .log_err();
24572 }
24573
24574 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24575 let settings = ThemeSettings::get_global(cx);
24576 let text_style = TextStyle {
24577 color: if self.prompt.read(cx).read_only(cx) {
24578 cx.theme().colors().text_disabled
24579 } else {
24580 cx.theme().colors().text
24581 },
24582 font_family: settings.buffer_font.family.clone(),
24583 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24584 font_size: settings.buffer_font_size(cx).into(),
24585 font_weight: settings.buffer_font.weight,
24586 line_height: relative(settings.buffer_line_height.value()),
24587 ..Default::default()
24588 };
24589 EditorElement::new(
24590 &self.prompt,
24591 EditorStyle {
24592 background: cx.theme().colors().editor_background,
24593 local_player: cx.theme().players().local(),
24594 text: text_style,
24595 ..Default::default()
24596 },
24597 )
24598 }
24599}
24600
24601impl Render for BreakpointPromptEditor {
24602 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24603 let editor_margins = *self.editor_margins.lock();
24604 let gutter_dimensions = editor_margins.gutter;
24605 h_flex()
24606 .key_context("Editor")
24607 .bg(cx.theme().colors().editor_background)
24608 .border_y_1()
24609 .border_color(cx.theme().status().info_border)
24610 .size_full()
24611 .py(window.line_height() / 2.5)
24612 .on_action(cx.listener(Self::confirm))
24613 .on_action(cx.listener(Self::cancel))
24614 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24615 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24616 }
24617}
24618
24619impl Focusable for BreakpointPromptEditor {
24620 fn focus_handle(&self, cx: &App) -> FocusHandle {
24621 self.prompt.focus_handle(cx)
24622 }
24623}
24624
24625fn all_edits_insertions_or_deletions(
24626 edits: &Vec<(Range<Anchor>, String)>,
24627 snapshot: &MultiBufferSnapshot,
24628) -> bool {
24629 let mut all_insertions = true;
24630 let mut all_deletions = true;
24631
24632 for (range, new_text) in edits.iter() {
24633 let range_is_empty = range.to_offset(snapshot).is_empty();
24634 let text_is_empty = new_text.is_empty();
24635
24636 if range_is_empty != text_is_empty {
24637 if range_is_empty {
24638 all_deletions = false;
24639 } else {
24640 all_insertions = false;
24641 }
24642 } else {
24643 return false;
24644 }
24645
24646 if !all_insertions && !all_deletions {
24647 return false;
24648 }
24649 }
24650 all_insertions || all_deletions
24651}
24652
24653struct MissingEditPredictionKeybindingTooltip;
24654
24655impl Render for MissingEditPredictionKeybindingTooltip {
24656 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24657 ui::tooltip_container(cx, |container, cx| {
24658 container
24659 .flex_shrink_0()
24660 .max_w_80()
24661 .min_h(rems_from_px(124.))
24662 .justify_between()
24663 .child(
24664 v_flex()
24665 .flex_1()
24666 .text_ui_sm(cx)
24667 .child(Label::new("Conflict with Accept Keybinding"))
24668 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24669 )
24670 .child(
24671 h_flex()
24672 .pb_1()
24673 .gap_1()
24674 .items_end()
24675 .w_full()
24676 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24677 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24678 }))
24679 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24680 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24681 })),
24682 )
24683 })
24684 }
24685}
24686
24687#[derive(Debug, Clone, Copy, PartialEq)]
24688pub struct LineHighlight {
24689 pub background: Background,
24690 pub border: Option<gpui::Hsla>,
24691 pub include_gutter: bool,
24692 pub type_id: Option<TypeId>,
24693}
24694
24695struct LineManipulationResult {
24696 pub new_text: String,
24697 pub line_count_before: usize,
24698 pub line_count_after: usize,
24699}
24700
24701fn render_diff_hunk_controls(
24702 row: u32,
24703 status: &DiffHunkStatus,
24704 hunk_range: Range<Anchor>,
24705 is_created_file: bool,
24706 line_height: Pixels,
24707 editor: &Entity<Editor>,
24708 _window: &mut Window,
24709 cx: &mut App,
24710) -> AnyElement {
24711 h_flex()
24712 .h(line_height)
24713 .mr_1()
24714 .gap_1()
24715 .px_0p5()
24716 .pb_1()
24717 .border_x_1()
24718 .border_b_1()
24719 .border_color(cx.theme().colors().border_variant)
24720 .rounded_b_lg()
24721 .bg(cx.theme().colors().editor_background)
24722 .gap_1()
24723 .block_mouse_except_scroll()
24724 .shadow_md()
24725 .child(if status.has_secondary_hunk() {
24726 Button::new(("stage", row as u64), "Stage")
24727 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24728 .tooltip({
24729 let focus_handle = editor.focus_handle(cx);
24730 move |_window, cx| {
24731 Tooltip::for_action_in(
24732 "Stage Hunk",
24733 &::git::ToggleStaged,
24734 &focus_handle,
24735 cx,
24736 )
24737 }
24738 })
24739 .on_click({
24740 let editor = editor.clone();
24741 move |_event, _window, cx| {
24742 editor.update(cx, |editor, cx| {
24743 editor.stage_or_unstage_diff_hunks(
24744 true,
24745 vec![hunk_range.start..hunk_range.start],
24746 cx,
24747 );
24748 });
24749 }
24750 })
24751 } else {
24752 Button::new(("unstage", row as u64), "Unstage")
24753 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24754 .tooltip({
24755 let focus_handle = editor.focus_handle(cx);
24756 move |_window, cx| {
24757 Tooltip::for_action_in(
24758 "Unstage Hunk",
24759 &::git::ToggleStaged,
24760 &focus_handle,
24761 cx,
24762 )
24763 }
24764 })
24765 .on_click({
24766 let editor = editor.clone();
24767 move |_event, _window, cx| {
24768 editor.update(cx, |editor, cx| {
24769 editor.stage_or_unstage_diff_hunks(
24770 false,
24771 vec![hunk_range.start..hunk_range.start],
24772 cx,
24773 );
24774 });
24775 }
24776 })
24777 })
24778 .child(
24779 Button::new(("restore", row as u64), "Restore")
24780 .tooltip({
24781 let focus_handle = editor.focus_handle(cx);
24782 move |_window, cx| {
24783 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24784 }
24785 })
24786 .on_click({
24787 let editor = editor.clone();
24788 move |_event, window, cx| {
24789 editor.update(cx, |editor, cx| {
24790 let snapshot = editor.snapshot(window, cx);
24791 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24792 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24793 });
24794 }
24795 })
24796 .disabled(is_created_file),
24797 )
24798 .when(
24799 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24800 |el| {
24801 el.child(
24802 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24803 .shape(IconButtonShape::Square)
24804 .icon_size(IconSize::Small)
24805 // .disabled(!has_multiple_hunks)
24806 .tooltip({
24807 let focus_handle = editor.focus_handle(cx);
24808 move |_window, cx| {
24809 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24810 }
24811 })
24812 .on_click({
24813 let editor = editor.clone();
24814 move |_event, window, cx| {
24815 editor.update(cx, |editor, cx| {
24816 let snapshot = editor.snapshot(window, cx);
24817 let position =
24818 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24819 editor.go_to_hunk_before_or_after_position(
24820 &snapshot,
24821 position,
24822 Direction::Next,
24823 window,
24824 cx,
24825 );
24826 editor.expand_selected_diff_hunks(cx);
24827 });
24828 }
24829 }),
24830 )
24831 .child(
24832 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24833 .shape(IconButtonShape::Square)
24834 .icon_size(IconSize::Small)
24835 // .disabled(!has_multiple_hunks)
24836 .tooltip({
24837 let focus_handle = editor.focus_handle(cx);
24838 move |_window, cx| {
24839 Tooltip::for_action_in(
24840 "Previous Hunk",
24841 &GoToPreviousHunk,
24842 &focus_handle,
24843 cx,
24844 )
24845 }
24846 })
24847 .on_click({
24848 let editor = editor.clone();
24849 move |_event, window, cx| {
24850 editor.update(cx, |editor, cx| {
24851 let snapshot = editor.snapshot(window, cx);
24852 let point =
24853 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24854 editor.go_to_hunk_before_or_after_position(
24855 &snapshot,
24856 point,
24857 Direction::Prev,
24858 window,
24859 cx,
24860 );
24861 editor.expand_selected_diff_hunks(cx);
24862 });
24863 }
24864 }),
24865 )
24866 },
24867 )
24868 .into_any_element()
24869}
24870
24871pub fn multibuffer_context_lines(cx: &App) -> u32 {
24872 EditorSettings::try_get(cx)
24873 .map(|settings| settings.excerpt_context_lines)
24874 .unwrap_or(2)
24875 .min(32)
24876}