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//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{
164 DiagnosticSeverity, GitGutterSetting, GoToDiagnosticSeverityFilter, ProjectSettings,
165 },
166};
167use rand::{seq::SliceRandom, thread_rng};
168use rpc::{ErrorCode, ErrorExt, proto::PeerId};
169use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
170use selections_collection::{
171 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
172};
173use serde::{Deserialize, Serialize};
174use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
175use smallvec::{SmallVec, smallvec};
176use snippet::Snippet;
177use std::{
178 any::TypeId,
179 borrow::Cow,
180 cell::OnceCell,
181 cell::RefCell,
182 cmp::{self, Ordering, Reverse},
183 iter::Peekable,
184 mem,
185 num::NonZeroU32,
186 ops::Not,
187 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
188 path::{Path, PathBuf},
189 rc::Rc,
190 sync::Arc,
191 time::{Duration, Instant},
192};
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
223const MAX_LINE_LEN: usize = 1024;
224const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
225const MAX_SELECTION_HISTORY_LEN: usize = 1024;
226pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
227#[doc(hidden)]
228pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
229pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
230
231pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
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
268struct InlineValueCache {
269 enabled: bool,
270 inlays: Vec<InlayId>,
271 refresh_task: Task<Option<()>>,
272}
273
274impl InlineValueCache {
275 fn new(enabled: bool) -> Self {
276 Self {
277 enabled,
278 inlays: Vec::new(),
279 refresh_task: Task::ready(None),
280 }
281 }
282}
283
284#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
285pub enum InlayId {
286 EditPrediction(usize),
287 DebuggerValue(usize),
288 // LSP
289 Hint(usize),
290 Color(usize),
291}
292
293impl InlayId {
294 fn id(&self) -> usize {
295 match self {
296 Self::EditPrediction(id) => *id,
297 Self::DebuggerValue(id) => *id,
298 Self::Hint(id) => *id,
299 Self::Color(id) => *id,
300 }
301 }
302}
303
304pub enum ActiveDebugLine {}
305pub enum DebugStackFrameLine {}
306enum DocumentHighlightRead {}
307enum DocumentHighlightWrite {}
308enum InputComposition {}
309pub enum PendingInput {}
310enum SelectedTextHighlight {}
311
312pub enum ConflictsOuter {}
313pub enum ConflictsOurs {}
314pub enum ConflictsTheirs {}
315pub enum ConflictsOursMarker {}
316pub enum ConflictsTheirsMarker {}
317
318#[derive(Debug, Copy, Clone, PartialEq, Eq)]
319pub enum Navigated {
320 Yes,
321 No,
322}
323
324impl Navigated {
325 pub fn from_bool(yes: bool) -> Navigated {
326 if yes { Navigated::Yes } else { Navigated::No }
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331enum DisplayDiffHunk {
332 Folded {
333 display_row: DisplayRow,
334 },
335 Unfolded {
336 is_created_file: bool,
337 diff_base_byte_range: Range<usize>,
338 display_row_range: Range<DisplayRow>,
339 multi_buffer_range: Range<Anchor>,
340 status: DiffHunkStatus,
341 },
342}
343
344pub enum HideMouseCursorOrigin {
345 TypingAction,
346 MovementAction,
347}
348
349pub fn init_settings(cx: &mut App) {
350 EditorSettings::register(cx);
351}
352
353pub fn init(cx: &mut App) {
354 init_settings(cx);
355
356 cx.set_global(GlobalBlameRenderer(Arc::new(())));
357
358 workspace::register_project_item::<Editor>(cx);
359 workspace::FollowableViewRegistry::register::<Editor>(cx);
360 workspace::register_serializable_item::<Editor>(cx);
361
362 cx.observe_new(
363 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
364 workspace.register_action(Editor::new_file);
365 workspace.register_action(Editor::new_file_vertical);
366 workspace.register_action(Editor::new_file_horizontal);
367 workspace.register_action(Editor::cancel_language_server_work);
368 workspace.register_action(Editor::toggle_focus);
369 },
370 )
371 .detach();
372
373 cx.on_action(move |_: &workspace::NewFile, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387 cx.on_action(move |_: &workspace::NewWindow, cx| {
388 let app_state = workspace::AppState::global(cx);
389 if let Some(app_state) = app_state.upgrade() {
390 workspace::open_new(
391 Default::default(),
392 app_state,
393 cx,
394 |workspace, window, cx| {
395 cx.activate(true);
396 Editor::new_file(workspace, &Default::default(), window, cx)
397 },
398 )
399 .detach();
400 }
401 });
402}
403
404pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
405 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
406}
407
408pub trait DiagnosticRenderer {
409 fn render_group(
410 &self,
411 diagnostic_group: Vec<DiagnosticEntry<Point>>,
412 buffer_id: BufferId,
413 snapshot: EditorSnapshot,
414 editor: WeakEntity<Editor>,
415 cx: &mut App,
416 ) -> Vec<BlockProperties<Anchor>>;
417
418 fn render_hover(
419 &self,
420 diagnostic_group: Vec<DiagnosticEntry<Point>>,
421 range: Range<Point>,
422 buffer_id: BufferId,
423 cx: &mut App,
424 ) -> Option<Entity<markdown::Markdown>>;
425
426 fn open_link(
427 &self,
428 editor: &mut Editor,
429 link: SharedString,
430 window: &mut Window,
431 cx: &mut Context<Editor>,
432 );
433}
434
435pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
436
437impl GlobalDiagnosticRenderer {
438 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
439 cx.try_global::<Self>().map(|g| g.0.clone())
440 }
441}
442
443impl gpui::Global for GlobalDiagnosticRenderer {}
444pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
445 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
446}
447
448pub struct SearchWithinRange;
449
450trait InvalidationRegion {
451 fn ranges(&self) -> &[Range<Anchor>];
452}
453
454#[derive(Clone, Debug, PartialEq)]
455pub enum SelectPhase {
456 Begin {
457 position: DisplayPoint,
458 add: bool,
459 click_count: usize,
460 },
461 BeginColumnar {
462 position: DisplayPoint,
463 reset: bool,
464 mode: ColumnarMode,
465 goal_column: u32,
466 },
467 Extend {
468 position: DisplayPoint,
469 click_count: usize,
470 },
471 Update {
472 position: DisplayPoint,
473 goal_column: u32,
474 scroll_delta: gpui::Point<f32>,
475 },
476 End,
477}
478
479#[derive(Clone, Debug, PartialEq)]
480pub enum ColumnarMode {
481 FromMouse,
482 FromSelection,
483}
484
485#[derive(Clone, Debug)]
486pub enum SelectMode {
487 Character,
488 Word(Range<Anchor>),
489 Line(Range<Anchor>),
490 All,
491}
492
493#[derive(Clone, PartialEq, Eq, Debug)]
494pub enum EditorMode {
495 SingleLine,
496 AutoHeight {
497 min_lines: usize,
498 max_lines: Option<usize>,
499 },
500 Full {
501 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
502 scale_ui_elements_with_buffer_font_size: bool,
503 /// When set to `true`, the editor will render a background for the active line.
504 show_active_line_background: bool,
505 /// When set to `true`, the editor's height will be determined by its content.
506 sized_by_content: bool,
507 },
508 Minimap {
509 parent: WeakEntity<Editor>,
510 },
511}
512
513impl EditorMode {
514 pub fn full() -> Self {
515 Self::Full {
516 scale_ui_elements_with_buffer_font_size: true,
517 show_active_line_background: true,
518 sized_by_content: false,
519 }
520 }
521
522 #[inline]
523 pub fn is_full(&self) -> bool {
524 matches!(self, Self::Full { .. })
525 }
526
527 #[inline]
528 pub fn is_single_line(&self) -> bool {
529 matches!(self, Self::SingleLine { .. })
530 }
531
532 #[inline]
533 fn is_minimap(&self) -> bool {
534 matches!(self, Self::Minimap { .. })
535 }
536}
537
538#[derive(Copy, Clone, Debug)]
539pub enum SoftWrap {
540 /// Prefer not to wrap at all.
541 ///
542 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
543 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
544 GitDiff,
545 /// Prefer a single line generally, unless an overly long line is encountered.
546 None,
547 /// Soft wrap lines that exceed the editor width.
548 EditorWidth,
549 /// Soft wrap lines at the preferred line length.
550 Column(u32),
551 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
552 Bounded(u32),
553}
554
555#[derive(Clone)]
556pub struct EditorStyle {
557 pub background: Hsla,
558 pub border: Hsla,
559 pub local_player: PlayerColor,
560 pub text: TextStyle,
561 pub scrollbar_width: Pixels,
562 pub syntax: Arc<SyntaxTheme>,
563 pub status: StatusColors,
564 pub inlay_hints_style: HighlightStyle,
565 pub edit_prediction_styles: EditPredictionStyles,
566 pub unnecessary_code_fade: f32,
567 pub show_underlines: bool,
568}
569
570impl Default for EditorStyle {
571 fn default() -> Self {
572 Self {
573 background: Hsla::default(),
574 border: Hsla::default(),
575 local_player: PlayerColor::default(),
576 text: TextStyle::default(),
577 scrollbar_width: Pixels::default(),
578 syntax: Default::default(),
579 // HACK: Status colors don't have a real default.
580 // We should look into removing the status colors from the editor
581 // style and retrieve them directly from the theme.
582 status: StatusColors::dark(),
583 inlay_hints_style: HighlightStyle::default(),
584 edit_prediction_styles: EditPredictionStyles {
585 insertion: HighlightStyle::default(),
586 whitespace: HighlightStyle::default(),
587 },
588 unnecessary_code_fade: Default::default(),
589 show_underlines: true,
590 }
591 }
592}
593
594pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
595 let show_background = language_settings::language_settings(None, None, cx)
596 .inlay_hints
597 .show_background;
598
599 HighlightStyle {
600 color: Some(cx.theme().status().hint),
601 background_color: show_background.then(|| cx.theme().status().hint_background),
602 ..HighlightStyle::default()
603 }
604}
605
606pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
607 EditPredictionStyles {
608 insertion: HighlightStyle {
609 color: Some(cx.theme().status().predictive),
610 ..HighlightStyle::default()
611 },
612 whitespace: HighlightStyle {
613 background_color: Some(cx.theme().status().created_background),
614 ..HighlightStyle::default()
615 },
616 }
617}
618
619type CompletionId = usize;
620
621pub(crate) enum EditDisplayMode {
622 TabAccept,
623 DiffPopover,
624 Inline,
625}
626
627enum EditPrediction {
628 Edit {
629 edits: Vec<(Range<Anchor>, String)>,
630 edit_preview: Option<EditPreview>,
631 display_mode: EditDisplayMode,
632 snapshot: BufferSnapshot,
633 },
634 Move {
635 target: Anchor,
636 snapshot: BufferSnapshot,
637 },
638}
639
640struct EditPredictionState {
641 inlay_ids: Vec<InlayId>,
642 completion: EditPrediction,
643 completion_id: Option<SharedString>,
644 invalidation_range: Range<Anchor>,
645}
646
647enum EditPredictionSettings {
648 Disabled,
649 Enabled {
650 show_in_menu: bool,
651 preview_requires_modifier: bool,
652 },
653}
654
655enum EditPredictionHighlight {}
656
657#[derive(Debug, Clone)]
658struct InlineDiagnostic {
659 message: SharedString,
660 group_id: usize,
661 is_primary: bool,
662 start: Point,
663 severity: lsp::DiagnosticSeverity,
664}
665
666pub enum MenuEditPredictionsPolicy {
667 Never,
668 ByProvider,
669}
670
671pub enum EditPredictionPreview {
672 /// Modifier is not pressed
673 Inactive { released_too_fast: bool },
674 /// Modifier pressed
675 Active {
676 since: Instant,
677 previous_scroll_position: Option<ScrollAnchor>,
678 },
679}
680
681impl EditPredictionPreview {
682 pub fn released_too_fast(&self) -> bool {
683 match self {
684 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
685 EditPredictionPreview::Active { .. } => false,
686 }
687 }
688
689 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
690 if let EditPredictionPreview::Active {
691 previous_scroll_position,
692 ..
693 } = self
694 {
695 *previous_scroll_position = scroll_position;
696 }
697 }
698}
699
700pub struct ContextMenuOptions {
701 pub min_entries_visible: usize,
702 pub max_entries_visible: usize,
703 pub placement: Option<ContextMenuPlacement>,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq)]
707pub enum ContextMenuPlacement {
708 Above,
709 Below,
710}
711
712#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
713struct EditorActionId(usize);
714
715impl EditorActionId {
716 pub fn post_inc(&mut self) -> Self {
717 let answer = self.0;
718
719 *self = Self(answer + 1);
720
721 Self(answer)
722 }
723}
724
725// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
726// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
727
728type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
729type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
730
731#[derive(Default)]
732struct ScrollbarMarkerState {
733 scrollbar_size: Size<Pixels>,
734 dirty: bool,
735 markers: Arc<[PaintQuad]>,
736 pending_refresh: Option<Task<Result<()>>>,
737}
738
739impl ScrollbarMarkerState {
740 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
741 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
742 }
743}
744
745#[derive(Clone, Copy, PartialEq, Eq)]
746pub enum MinimapVisibility {
747 Disabled,
748 Enabled {
749 /// The configuration currently present in the users settings.
750 setting_configuration: bool,
751 /// Whether to override the currently set visibility from the users setting.
752 toggle_override: bool,
753 },
754}
755
756impl MinimapVisibility {
757 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
758 if mode.is_full() {
759 Self::Enabled {
760 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
761 toggle_override: false,
762 }
763 } else {
764 Self::Disabled
765 }
766 }
767
768 fn hidden(&self) -> Self {
769 match *self {
770 Self::Enabled {
771 setting_configuration,
772 ..
773 } => Self::Enabled {
774 setting_configuration,
775 toggle_override: setting_configuration,
776 },
777 Self::Disabled => Self::Disabled,
778 }
779 }
780
781 fn disabled(&self) -> bool {
782 matches!(*self, Self::Disabled)
783 }
784
785 fn settings_visibility(&self) -> bool {
786 match *self {
787 Self::Enabled {
788 setting_configuration,
789 ..
790 } => setting_configuration,
791 _ => false,
792 }
793 }
794
795 fn visible(&self) -> bool {
796 match *self {
797 Self::Enabled {
798 setting_configuration,
799 toggle_override,
800 } => setting_configuration ^ toggle_override,
801 _ => false,
802 }
803 }
804
805 fn toggle_visibility(&self) -> Self {
806 match *self {
807 Self::Enabled {
808 toggle_override,
809 setting_configuration,
810 } => Self::Enabled {
811 setting_configuration,
812 toggle_override: !toggle_override,
813 },
814 Self::Disabled => Self::Disabled,
815 }
816 }
817}
818
819#[derive(Clone, Debug)]
820struct RunnableTasks {
821 templates: Vec<(TaskSourceKind, TaskTemplate)>,
822 offset: multi_buffer::Anchor,
823 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
824 column: u32,
825 // Values of all named captures, including those starting with '_'
826 extra_variables: HashMap<String, String>,
827 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
828 context_range: Range<BufferOffset>,
829}
830
831impl RunnableTasks {
832 fn resolve<'a>(
833 &'a self,
834 cx: &'a task::TaskContext,
835 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
836 self.templates.iter().filter_map(|(kind, template)| {
837 template
838 .resolve_task(&kind.to_id_base(), cx)
839 .map(|task| (kind.clone(), task))
840 })
841 }
842}
843
844#[derive(Clone)]
845pub struct ResolvedTasks {
846 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
847 position: Anchor,
848}
849
850#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
851struct BufferOffset(usize);
852
853// Addons allow storing per-editor state in other crates (e.g. Vim)
854pub trait Addon: 'static {
855 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
856
857 fn render_buffer_header_controls(
858 &self,
859 _: &ExcerptInfo,
860 _: &Window,
861 _: &App,
862 ) -> Option<AnyElement> {
863 None
864 }
865
866 fn to_any(&self) -> &dyn std::any::Any;
867
868 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
869 None
870 }
871}
872
873struct ChangeLocation {
874 current: Option<Vec<Anchor>>,
875 original: Vec<Anchor>,
876}
877impl ChangeLocation {
878 fn locations(&self) -> &[Anchor] {
879 self.current.as_ref().unwrap_or(&self.original)
880 }
881}
882
883/// A set of caret positions, registered when the editor was edited.
884pub struct ChangeList {
885 changes: Vec<ChangeLocation>,
886 /// Currently "selected" change.
887 position: Option<usize>,
888}
889
890impl ChangeList {
891 pub fn new() -> Self {
892 Self {
893 changes: Vec::new(),
894 position: None,
895 }
896 }
897
898 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
899 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
900 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
901 if self.changes.is_empty() {
902 return None;
903 }
904
905 let prev = self.position.unwrap_or(self.changes.len());
906 let next = if direction == Direction::Prev {
907 prev.saturating_sub(count)
908 } else {
909 (prev + count).min(self.changes.len() - 1)
910 };
911 self.position = Some(next);
912 self.changes.get(next).map(|change| change.locations())
913 }
914
915 /// Adds a new change to the list, resetting the change list position.
916 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
917 self.position.take();
918 if let Some(last) = self.changes.last_mut()
919 && group
920 {
921 last.current = Some(new_positions)
922 } else {
923 self.changes.push(ChangeLocation {
924 original: new_positions,
925 current: None,
926 });
927 }
928 }
929
930 pub fn last(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.locations())
932 }
933
934 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
935 self.changes.last().map(|change| change.original.as_slice())
936 }
937
938 pub fn invert_last_group(&mut self) {
939 if let Some(last) = self.changes.last_mut()
940 && let Some(current) = last.current.as_mut()
941 {
942 mem::swap(&mut last.original, current);
943 }
944 }
945}
946
947#[derive(Clone)]
948struct InlineBlamePopoverState {
949 scroll_handle: ScrollHandle,
950 commit_message: Option<ParsedCommitMessage>,
951 markdown: Entity<Markdown>,
952}
953
954struct InlineBlamePopover {
955 position: gpui::Point<Pixels>,
956 hide_task: Option<Task<()>>,
957 popover_bounds: Option<Bounds<Pixels>>,
958 popover_state: InlineBlamePopoverState,
959 keyboard_grace: bool,
960}
961
962enum SelectionDragState {
963 /// State when no drag related activity is detected.
964 None,
965 /// State when the mouse is down on a selection that is about to be dragged.
966 ReadyToDrag {
967 selection: Selection<Anchor>,
968 click_position: gpui::Point<Pixels>,
969 mouse_down_time: Instant,
970 },
971 /// State when the mouse is dragging the selection in the editor.
972 Dragging {
973 selection: Selection<Anchor>,
974 drop_cursor: Selection<Anchor>,
975 hide_drop_cursor: bool,
976 },
977}
978
979enum ColumnarSelectionState {
980 FromMouse {
981 selection_tail: Anchor,
982 display_point: Option<DisplayPoint>,
983 },
984 FromSelection {
985 selection_tail: Anchor,
986 },
987}
988
989/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
990/// a breakpoint on them.
991#[derive(Clone, Copy, Debug, PartialEq, Eq)]
992struct PhantomBreakpointIndicator {
993 display_row: DisplayRow,
994 /// There's a small debounce between hovering over the line and showing the indicator.
995 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
996 is_active: bool,
997 collides_with_existing_breakpoint: bool,
998}
999
1000/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1001///
1002/// See the [module level documentation](self) for more information.
1003pub struct Editor {
1004 focus_handle: FocusHandle,
1005 last_focused_descendant: Option<WeakFocusHandle>,
1006 /// The text buffer being edited
1007 buffer: Entity<MultiBuffer>,
1008 /// Map of how text in the buffer should be displayed.
1009 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1010 pub display_map: Entity<DisplayMap>,
1011 pub selections: SelectionsCollection,
1012 pub scroll_manager: ScrollManager,
1013 /// When inline assist editors are linked, they all render cursors because
1014 /// typing enters text into each of them, even the ones that aren't focused.
1015 pub(crate) show_cursor_when_unfocused: bool,
1016 columnar_selection_state: Option<ColumnarSelectionState>,
1017 add_selections_state: Option<AddSelectionsState>,
1018 select_next_state: Option<SelectNextState>,
1019 select_prev_state: Option<SelectNextState>,
1020 selection_history: SelectionHistory,
1021 defer_selection_effects: bool,
1022 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1023 autoclose_regions: Vec<AutocloseRegion>,
1024 snippet_stack: InvalidationStack<SnippetState>,
1025 select_syntax_node_history: SelectSyntaxNodeHistory,
1026 ime_transaction: Option<TransactionId>,
1027 pub diagnostics_max_severity: DiagnosticSeverity,
1028 active_diagnostics: ActiveDiagnostic,
1029 show_inline_diagnostics: bool,
1030 inline_diagnostics_update: Task<()>,
1031 inline_diagnostics_enabled: bool,
1032 diagnostics_enabled: bool,
1033 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1034 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1035 hard_wrap: Option<usize>,
1036 project: Option<Entity<Project>>,
1037 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1038 completion_provider: Option<Rc<dyn CompletionProvider>>,
1039 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1040 blink_manager: Entity<BlinkManager>,
1041 show_cursor_names: bool,
1042 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1043 pub show_local_selections: bool,
1044 mode: EditorMode,
1045 show_breadcrumbs: bool,
1046 show_gutter: bool,
1047 show_scrollbars: ScrollbarAxes,
1048 minimap_visibility: MinimapVisibility,
1049 offset_content: bool,
1050 disable_expand_excerpt_buttons: bool,
1051 show_line_numbers: Option<bool>,
1052 use_relative_line_numbers: Option<bool>,
1053 show_git_diff_gutter: Option<bool>,
1054 show_code_actions: Option<bool>,
1055 show_runnables: Option<bool>,
1056 show_breakpoints: Option<bool>,
1057 show_wrap_guides: Option<bool>,
1058 show_indent_guides: Option<bool>,
1059 placeholder_text: Option<Arc<str>>,
1060 highlight_order: usize,
1061 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1062 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1063 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1064 scrollbar_marker_state: ScrollbarMarkerState,
1065 active_indent_guides_state: ActiveIndentGuidesState,
1066 nav_history: Option<ItemNavHistory>,
1067 context_menu: RefCell<Option<CodeContextMenu>>,
1068 context_menu_options: Option<ContextMenuOptions>,
1069 mouse_context_menu: Option<MouseContextMenu>,
1070 completion_tasks: Vec<(CompletionId, Task<()>)>,
1071 inline_blame_popover: Option<InlineBlamePopover>,
1072 inline_blame_popover_show_task: Option<Task<()>>,
1073 signature_help_state: SignatureHelpState,
1074 auto_signature_help: Option<bool>,
1075 find_all_references_task_sources: Vec<Anchor>,
1076 next_completion_id: CompletionId,
1077 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1078 code_actions_task: Option<Task<Result<()>>>,
1079 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1080 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 document_highlights_task: Option<Task<()>>,
1082 linked_editing_range_task: Option<Task<Option<()>>>,
1083 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1084 pending_rename: Option<RenameState>,
1085 searchable: bool,
1086 cursor_shape: CursorShape,
1087 current_line_highlight: Option<CurrentLineHighlight>,
1088 collapse_matches: bool,
1089 autoindent_mode: Option<AutoindentMode>,
1090 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1091 input_enabled: bool,
1092 use_modal_editing: bool,
1093 read_only: bool,
1094 leader_id: Option<CollaboratorId>,
1095 remote_id: Option<ViewId>,
1096 pub hover_state: HoverState,
1097 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1098 gutter_hovered: bool,
1099 hovered_link_state: Option<HoveredLinkState>,
1100 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1101 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1102 active_edit_prediction: Option<EditPredictionState>,
1103 /// Used to prevent flickering as the user types while the menu is open
1104 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1105 edit_prediction_settings: EditPredictionSettings,
1106 edit_predictions_hidden_for_vim_mode: bool,
1107 show_edit_predictions_override: Option<bool>,
1108 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1109 edit_prediction_preview: EditPredictionPreview,
1110 edit_prediction_indent_conflict: bool,
1111 edit_prediction_requires_modifier_in_indent_conflict: bool,
1112 inlay_hint_cache: InlayHintCache,
1113 next_inlay_id: usize,
1114 _subscriptions: Vec<Subscription>,
1115 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1116 gutter_dimensions: GutterDimensions,
1117 style: Option<EditorStyle>,
1118 text_style_refinement: Option<TextStyleRefinement>,
1119 next_editor_action_id: EditorActionId,
1120 editor_actions: Rc<
1121 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1122 >,
1123 use_autoclose: bool,
1124 use_auto_surround: bool,
1125 auto_replace_emoji_shortcode: bool,
1126 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1127 show_git_blame_gutter: bool,
1128 show_git_blame_inline: bool,
1129 show_git_blame_inline_delay_task: Option<Task<()>>,
1130 git_blame_inline_enabled: bool,
1131 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1132 serialize_dirty_buffers: bool,
1133 show_selection_menu: Option<bool>,
1134 blame: Option<Entity<GitBlame>>,
1135 blame_subscription: Option<Subscription>,
1136 custom_context_menu: Option<
1137 Box<
1138 dyn 'static
1139 + Fn(
1140 &mut Self,
1141 DisplayPoint,
1142 &mut Window,
1143 &mut Context<Self>,
1144 ) -> Option<Entity<ui::ContextMenu>>,
1145 >,
1146 >,
1147 last_bounds: Option<Bounds<Pixels>>,
1148 last_position_map: Option<Rc<PositionMap>>,
1149 expect_bounds_change: Option<Bounds<Pixels>>,
1150 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1151 tasks_update_task: Option<Task<()>>,
1152 breakpoint_store: Option<Entity<BreakpointStore>>,
1153 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1154 hovered_diff_hunk_row: Option<DisplayRow>,
1155 pull_diagnostics_task: Task<()>,
1156 in_project_search: bool,
1157 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1158 breadcrumb_header: Option<String>,
1159 focused_block: Option<FocusedBlock>,
1160 next_scroll_position: NextScrollCursorCenterTopBottom,
1161 addons: HashMap<TypeId, Box<dyn Addon>>,
1162 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1163 load_diff_task: Option<Shared<Task<()>>>,
1164 /// Whether we are temporarily displaying a diff other than git's
1165 temporary_diff_override: bool,
1166 selection_mark_mode: bool,
1167 toggle_fold_multiple_buffers: Task<()>,
1168 _scroll_cursor_center_top_bottom_task: Task<()>,
1169 serialize_selections: Task<()>,
1170 serialize_folds: Task<()>,
1171 mouse_cursor_hidden: bool,
1172 minimap: Option<Entity<Self>>,
1173 hide_mouse_mode: HideMouseMode,
1174 pub change_list: ChangeList,
1175 inline_value_cache: InlineValueCache,
1176 selection_drag_state: SelectionDragState,
1177 next_color_inlay_id: usize,
1178 colors: Option<LspColorData>,
1179 folding_newlines: Task<()>,
1180}
1181
1182#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1183enum NextScrollCursorCenterTopBottom {
1184 #[default]
1185 Center,
1186 Top,
1187 Bottom,
1188}
1189
1190impl NextScrollCursorCenterTopBottom {
1191 fn next(&self) -> Self {
1192 match self {
1193 Self::Center => Self::Top,
1194 Self::Top => Self::Bottom,
1195 Self::Bottom => Self::Center,
1196 }
1197 }
1198}
1199
1200#[derive(Clone)]
1201pub struct EditorSnapshot {
1202 pub mode: EditorMode,
1203 show_gutter: bool,
1204 show_line_numbers: Option<bool>,
1205 show_git_diff_gutter: Option<bool>,
1206 show_code_actions: Option<bool>,
1207 show_runnables: Option<bool>,
1208 show_breakpoints: Option<bool>,
1209 git_blame_gutter_max_author_length: Option<usize>,
1210 pub display_snapshot: DisplaySnapshot,
1211 pub placeholder_text: Option<Arc<str>>,
1212 is_focused: bool,
1213 scroll_anchor: ScrollAnchor,
1214 ongoing_scroll: OngoingScroll,
1215 current_line_highlight: CurrentLineHighlight,
1216 gutter_hovered: bool,
1217}
1218
1219#[derive(Default, Debug, Clone, Copy)]
1220pub struct GutterDimensions {
1221 pub left_padding: Pixels,
1222 pub right_padding: Pixels,
1223 pub width: Pixels,
1224 pub margin: Pixels,
1225 pub git_blame_entries_width: Option<Pixels>,
1226}
1227
1228impl GutterDimensions {
1229 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1230 Self {
1231 margin: Self::default_gutter_margin(font_id, font_size, cx),
1232 ..Default::default()
1233 }
1234 }
1235
1236 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1237 -cx.text_system().descent(font_id, font_size)
1238 }
1239 /// The full width of the space taken up by the gutter.
1240 pub fn full_width(&self) -> Pixels {
1241 self.margin + self.width
1242 }
1243
1244 /// The width of the space reserved for the fold indicators,
1245 /// use alongside 'justify_end' and `gutter_width` to
1246 /// right align content with the line numbers
1247 pub fn fold_area_width(&self) -> Pixels {
1248 self.margin + self.right_padding
1249 }
1250}
1251
1252struct CharacterDimensions {
1253 em_width: Pixels,
1254 em_advance: Pixels,
1255 line_height: Pixels,
1256}
1257
1258#[derive(Debug)]
1259pub struct RemoteSelection {
1260 pub replica_id: ReplicaId,
1261 pub selection: Selection<Anchor>,
1262 pub cursor_shape: CursorShape,
1263 pub collaborator_id: CollaboratorId,
1264 pub line_mode: bool,
1265 pub user_name: Option<SharedString>,
1266 pub color: PlayerColor,
1267}
1268
1269#[derive(Clone, Debug)]
1270struct SelectionHistoryEntry {
1271 selections: Arc<[Selection<Anchor>]>,
1272 select_next_state: Option<SelectNextState>,
1273 select_prev_state: Option<SelectNextState>,
1274 add_selections_state: Option<AddSelectionsState>,
1275}
1276
1277#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1278enum SelectionHistoryMode {
1279 Normal,
1280 Undoing,
1281 Redoing,
1282 Skipping,
1283}
1284
1285#[derive(Clone, PartialEq, Eq, Hash)]
1286struct HoveredCursor {
1287 replica_id: u16,
1288 selection_id: usize,
1289}
1290
1291impl Default for SelectionHistoryMode {
1292 fn default() -> Self {
1293 Self::Normal
1294 }
1295}
1296
1297#[derive(Debug)]
1298/// SelectionEffects controls the side-effects of updating the selection.
1299///
1300/// The default behaviour does "what you mostly want":
1301/// - it pushes to the nav history if the cursor moved by >10 lines
1302/// - it re-triggers completion requests
1303/// - it scrolls to fit
1304///
1305/// You might want to modify these behaviours. For example when doing a "jump"
1306/// like go to definition, we always want to add to nav history; but when scrolling
1307/// in vim mode we never do.
1308///
1309/// Similarly, you might want to disable scrolling if you don't want the viewport to
1310/// move.
1311#[derive(Clone)]
1312pub struct SelectionEffects {
1313 nav_history: Option<bool>,
1314 completions: bool,
1315 scroll: Option<Autoscroll>,
1316}
1317
1318impl Default for SelectionEffects {
1319 fn default() -> Self {
1320 Self {
1321 nav_history: None,
1322 completions: true,
1323 scroll: Some(Autoscroll::fit()),
1324 }
1325 }
1326}
1327impl SelectionEffects {
1328 pub fn scroll(scroll: Autoscroll) -> Self {
1329 Self {
1330 scroll: Some(scroll),
1331 ..Default::default()
1332 }
1333 }
1334
1335 pub fn no_scroll() -> Self {
1336 Self {
1337 scroll: None,
1338 ..Default::default()
1339 }
1340 }
1341
1342 pub fn completions(self, completions: bool) -> Self {
1343 Self {
1344 completions,
1345 ..self
1346 }
1347 }
1348
1349 pub fn nav_history(self, nav_history: bool) -> Self {
1350 Self {
1351 nav_history: Some(nav_history),
1352 ..self
1353 }
1354 }
1355}
1356
1357struct DeferredSelectionEffectsState {
1358 changed: bool,
1359 effects: SelectionEffects,
1360 old_cursor_position: Anchor,
1361 history_entry: SelectionHistoryEntry,
1362}
1363
1364#[derive(Default)]
1365struct SelectionHistory {
1366 #[allow(clippy::type_complexity)]
1367 selections_by_transaction:
1368 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1369 mode: SelectionHistoryMode,
1370 undo_stack: VecDeque<SelectionHistoryEntry>,
1371 redo_stack: VecDeque<SelectionHistoryEntry>,
1372}
1373
1374impl SelectionHistory {
1375 #[track_caller]
1376 fn insert_transaction(
1377 &mut self,
1378 transaction_id: TransactionId,
1379 selections: Arc<[Selection<Anchor>]>,
1380 ) {
1381 if selections.is_empty() {
1382 log::error!(
1383 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1384 std::panic::Location::caller()
1385 );
1386 return;
1387 }
1388 self.selections_by_transaction
1389 .insert(transaction_id, (selections, None));
1390 }
1391
1392 #[allow(clippy::type_complexity)]
1393 fn transaction(
1394 &self,
1395 transaction_id: TransactionId,
1396 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1397 self.selections_by_transaction.get(&transaction_id)
1398 }
1399
1400 #[allow(clippy::type_complexity)]
1401 fn transaction_mut(
1402 &mut self,
1403 transaction_id: TransactionId,
1404 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1405 self.selections_by_transaction.get_mut(&transaction_id)
1406 }
1407
1408 fn push(&mut self, entry: SelectionHistoryEntry) {
1409 if !entry.selections.is_empty() {
1410 match self.mode {
1411 SelectionHistoryMode::Normal => {
1412 self.push_undo(entry);
1413 self.redo_stack.clear();
1414 }
1415 SelectionHistoryMode::Undoing => self.push_redo(entry),
1416 SelectionHistoryMode::Redoing => self.push_undo(entry),
1417 SelectionHistoryMode::Skipping => {}
1418 }
1419 }
1420 }
1421
1422 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1423 if self
1424 .undo_stack
1425 .back()
1426 .is_none_or(|e| e.selections != entry.selections)
1427 {
1428 self.undo_stack.push_back(entry);
1429 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1430 self.undo_stack.pop_front();
1431 }
1432 }
1433 }
1434
1435 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1436 if self
1437 .redo_stack
1438 .back()
1439 .is_none_or(|e| e.selections != entry.selections)
1440 {
1441 self.redo_stack.push_back(entry);
1442 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1443 self.redo_stack.pop_front();
1444 }
1445 }
1446 }
1447}
1448
1449#[derive(Clone, Copy)]
1450pub struct RowHighlightOptions {
1451 pub autoscroll: bool,
1452 pub include_gutter: bool,
1453}
1454
1455impl Default for RowHighlightOptions {
1456 fn default() -> Self {
1457 Self {
1458 autoscroll: Default::default(),
1459 include_gutter: true,
1460 }
1461 }
1462}
1463
1464struct RowHighlight {
1465 index: usize,
1466 range: Range<Anchor>,
1467 color: Hsla,
1468 options: RowHighlightOptions,
1469 type_id: TypeId,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsState {
1474 groups: Vec<AddSelectionsGroup>,
1475}
1476
1477#[derive(Clone, Debug)]
1478struct AddSelectionsGroup {
1479 above: bool,
1480 stack: Vec<usize>,
1481}
1482
1483#[derive(Clone)]
1484struct SelectNextState {
1485 query: AhoCorasick,
1486 wordwise: bool,
1487 done: bool,
1488}
1489
1490impl std::fmt::Debug for SelectNextState {
1491 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1492 f.debug_struct(std::any::type_name::<Self>())
1493 .field("wordwise", &self.wordwise)
1494 .field("done", &self.done)
1495 .finish()
1496 }
1497}
1498
1499#[derive(Debug)]
1500struct AutocloseRegion {
1501 selection_id: usize,
1502 range: Range<Anchor>,
1503 pair: BracketPair,
1504}
1505
1506#[derive(Debug)]
1507struct SnippetState {
1508 ranges: Vec<Vec<Range<Anchor>>>,
1509 active_index: usize,
1510 choices: Vec<Option<Vec<String>>>,
1511}
1512
1513#[doc(hidden)]
1514pub struct RenameState {
1515 pub range: Range<Anchor>,
1516 pub old_name: Arc<str>,
1517 pub editor: Entity<Editor>,
1518 block_id: CustomBlockId,
1519}
1520
1521struct InvalidationStack<T>(Vec<T>);
1522
1523struct RegisteredEditPredictionProvider {
1524 provider: Arc<dyn EditPredictionProviderHandle>,
1525 _subscription: Subscription,
1526}
1527
1528#[derive(Debug, PartialEq, Eq)]
1529pub struct ActiveDiagnosticGroup {
1530 pub active_range: Range<Anchor>,
1531 pub active_message: String,
1532 pub group_id: usize,
1533 pub blocks: HashSet<CustomBlockId>,
1534}
1535
1536#[derive(Debug, PartialEq, Eq)]
1537
1538pub(crate) enum ActiveDiagnostic {
1539 None,
1540 All,
1541 Group(ActiveDiagnosticGroup),
1542}
1543
1544#[derive(Serialize, Deserialize, Clone, Debug)]
1545pub struct ClipboardSelection {
1546 /// The number of bytes in this selection.
1547 pub len: usize,
1548 /// Whether this was a full-line selection.
1549 pub is_entire_line: bool,
1550 /// The indentation of the first line when this content was originally copied.
1551 pub first_line_indent: u32,
1552}
1553
1554// selections, scroll behavior, was newest selection reversed
1555type SelectSyntaxNodeHistoryState = (
1556 Box<[Selection<usize>]>,
1557 SelectSyntaxNodeScrollBehavior,
1558 bool,
1559);
1560
1561#[derive(Default)]
1562struct SelectSyntaxNodeHistory {
1563 stack: Vec<SelectSyntaxNodeHistoryState>,
1564 // disable temporarily to allow changing selections without losing the stack
1565 pub disable_clearing: bool,
1566}
1567
1568impl SelectSyntaxNodeHistory {
1569 pub fn try_clear(&mut self) {
1570 if !self.disable_clearing {
1571 self.stack.clear();
1572 }
1573 }
1574
1575 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1576 self.stack.push(selection);
1577 }
1578
1579 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1580 self.stack.pop()
1581 }
1582}
1583
1584enum SelectSyntaxNodeScrollBehavior {
1585 CursorTop,
1586 FitSelection,
1587 CursorBottom,
1588}
1589
1590#[derive(Debug)]
1591pub(crate) struct NavigationData {
1592 cursor_anchor: Anchor,
1593 cursor_position: Point,
1594 scroll_anchor: ScrollAnchor,
1595 scroll_top_row: u32,
1596}
1597
1598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1599pub enum GotoDefinitionKind {
1600 Symbol,
1601 Declaration,
1602 Type,
1603 Implementation,
1604}
1605
1606#[derive(Debug, Clone)]
1607enum InlayHintRefreshReason {
1608 ModifiersChanged(bool),
1609 Toggle(bool),
1610 SettingsChange(InlayHintSettings),
1611 NewLinesShown,
1612 BufferEdited(HashSet<Arc<Language>>),
1613 RefreshRequested,
1614 ExcerptsRemoved(Vec<ExcerptId>),
1615}
1616
1617impl InlayHintRefreshReason {
1618 fn description(&self) -> &'static str {
1619 match self {
1620 Self::ModifiersChanged(_) => "modifiers changed",
1621 Self::Toggle(_) => "toggle",
1622 Self::SettingsChange(_) => "settings change",
1623 Self::NewLinesShown => "new lines shown",
1624 Self::BufferEdited(_) => "buffer edited",
1625 Self::RefreshRequested => "refresh requested",
1626 Self::ExcerptsRemoved(_) => "excerpts removed",
1627 }
1628 }
1629}
1630
1631pub enum FormatTarget {
1632 Buffers(HashSet<Entity<Buffer>>),
1633 Ranges(Vec<Range<MultiBufferPoint>>),
1634}
1635
1636pub(crate) struct FocusedBlock {
1637 id: BlockId,
1638 focus_handle: WeakFocusHandle,
1639}
1640
1641#[derive(Clone)]
1642enum JumpData {
1643 MultiBufferRow {
1644 row: MultiBufferRow,
1645 line_offset_from_top: u32,
1646 },
1647 MultiBufferPoint {
1648 excerpt_id: ExcerptId,
1649 position: Point,
1650 anchor: text::Anchor,
1651 line_offset_from_top: u32,
1652 },
1653}
1654
1655pub enum MultibufferSelectionMode {
1656 First,
1657 All,
1658}
1659
1660#[derive(Clone, Copy, Debug, Default)]
1661pub struct RewrapOptions {
1662 pub override_language_settings: bool,
1663 pub preserve_existing_whitespace: bool,
1664}
1665
1666impl Editor {
1667 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1671 }
1672
1673 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::full(), buffer, None, window, cx)
1677 }
1678
1679 pub fn auto_height(
1680 min_lines: usize,
1681 max_lines: usize,
1682 window: &mut Window,
1683 cx: &mut Context<Self>,
1684 ) -> Self {
1685 let buffer = cx.new(|cx| Buffer::local("", cx));
1686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1687 Self::new(
1688 EditorMode::AutoHeight {
1689 min_lines,
1690 max_lines: Some(max_lines),
1691 },
1692 buffer,
1693 None,
1694 window,
1695 cx,
1696 )
1697 }
1698
1699 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1700 /// The editor grows as tall as needed to fit its content.
1701 pub fn auto_height_unbounded(
1702 min_lines: usize,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 let buffer = cx.new(|cx| Buffer::local("", cx));
1707 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1708 Self::new(
1709 EditorMode::AutoHeight {
1710 min_lines,
1711 max_lines: None,
1712 },
1713 buffer,
1714 None,
1715 window,
1716 cx,
1717 )
1718 }
1719
1720 pub fn for_buffer(
1721 buffer: Entity<Buffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1727 Self::new(EditorMode::full(), buffer, project, window, cx)
1728 }
1729
1730 pub fn for_multibuffer(
1731 buffer: Entity<MultiBuffer>,
1732 project: Option<Entity<Project>>,
1733 window: &mut Window,
1734 cx: &mut Context<Self>,
1735 ) -> Self {
1736 Self::new(EditorMode::full(), buffer, project, window, cx)
1737 }
1738
1739 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1740 let mut clone = Self::new(
1741 self.mode.clone(),
1742 self.buffer.clone(),
1743 self.project.clone(),
1744 window,
1745 cx,
1746 );
1747 self.display_map.update(cx, |display_map, cx| {
1748 let snapshot = display_map.snapshot(cx);
1749 clone.display_map.update(cx, |display_map, cx| {
1750 display_map.set_state(&snapshot, cx);
1751 });
1752 });
1753 clone.folds_did_change(cx);
1754 clone.selections.clone_state(&self.selections);
1755 clone.scroll_manager.clone_state(&self.scroll_manager);
1756 clone.searchable = self.searchable;
1757 clone.read_only = self.read_only;
1758 clone
1759 }
1760
1761 pub fn new(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 window: &mut Window,
1766 cx: &mut Context<Self>,
1767 ) -> Self {
1768 Editor::new_internal(mode, buffer, project, None, window, cx)
1769 }
1770
1771 fn new_internal(
1772 mode: EditorMode,
1773 buffer: Entity<MultiBuffer>,
1774 project: Option<Entity<Project>>,
1775 display_map: Option<Entity<DisplayMap>>,
1776 window: &mut Window,
1777 cx: &mut Context<Self>,
1778 ) -> Self {
1779 debug_assert!(
1780 display_map.is_none() || mode.is_minimap(),
1781 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1782 );
1783
1784 let full_mode = mode.is_full();
1785 let is_minimap = mode.is_minimap();
1786 let diagnostics_max_severity = if full_mode {
1787 EditorSettings::get_global(cx)
1788 .diagnostics_max_severity
1789 .unwrap_or(DiagnosticSeverity::Hint)
1790 } else {
1791 DiagnosticSeverity::Off
1792 };
1793 let style = window.text_style();
1794 let font_size = style.font_size.to_pixels(window.rem_size());
1795 let editor = cx.entity().downgrade();
1796 let fold_placeholder = FoldPlaceholder {
1797 constrain_width: true,
1798 render: Arc::new(move |fold_id, fold_range, cx| {
1799 let editor = editor.clone();
1800 div()
1801 .id(fold_id)
1802 .bg(cx.theme().colors().ghost_element_background)
1803 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1804 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1805 .rounded_xs()
1806 .size_full()
1807 .cursor_pointer()
1808 .child("⋯")
1809 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1810 .on_click(move |_, _window, cx| {
1811 editor
1812 .update(cx, |editor, cx| {
1813 editor.unfold_ranges(
1814 &[fold_range.start..fold_range.end],
1815 true,
1816 false,
1817 cx,
1818 );
1819 cx.stop_propagation();
1820 })
1821 .ok();
1822 })
1823 .into_any()
1824 }),
1825 merge_adjacent: true,
1826 ..FoldPlaceholder::default()
1827 };
1828 let display_map = display_map.unwrap_or_else(|| {
1829 cx.new(|cx| {
1830 DisplayMap::new(
1831 buffer.clone(),
1832 style.font(),
1833 font_size,
1834 None,
1835 FILE_HEADER_HEIGHT,
1836 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1837 fold_placeholder,
1838 diagnostics_max_severity,
1839 cx,
1840 )
1841 })
1842 });
1843
1844 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1845
1846 let blink_manager = cx.new(|cx| {
1847 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1848 if is_minimap {
1849 blink_manager.disable(cx);
1850 }
1851 blink_manager
1852 });
1853
1854 let soft_wrap_mode_override =
1855 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1856
1857 let mut project_subscriptions = Vec::new();
1858 if full_mode && let Some(project) = project.as_ref() {
1859 project_subscriptions.push(cx.subscribe_in(
1860 project,
1861 window,
1862 |editor, _, event, window, cx| match event {
1863 project::Event::RefreshCodeLens => {
1864 // we always query lens with actions, without storing them, always refreshing them
1865 }
1866 project::Event::RefreshInlayHints => {
1867 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1868 }
1869 project::Event::LanguageServerAdded(..)
1870 | project::Event::LanguageServerRemoved(..) => {
1871 if editor.tasks_update_task.is_none() {
1872 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1873 }
1874 }
1875 project::Event::SnippetEdit(id, snippet_edits) => {
1876 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1877 let focus_handle = editor.focus_handle(cx);
1878 if focus_handle.is_focused(window) {
1879 let snapshot = buffer.read(cx).snapshot();
1880 for (range, snippet) in snippet_edits {
1881 let editor_range =
1882 language::range_from_lsp(*range).to_offset(&snapshot);
1883 editor
1884 .insert_snippet(
1885 &[editor_range],
1886 snippet.clone(),
1887 window,
1888 cx,
1889 )
1890 .ok();
1891 }
1892 }
1893 }
1894 }
1895 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1896 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1897 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1898 }
1899 }
1900
1901 project::Event::EntryRenamed(transaction) => {
1902 let Some(workspace) = editor.workspace() else {
1903 return;
1904 };
1905 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1906 else {
1907 return;
1908 };
1909 if active_editor.entity_id() == cx.entity_id() {
1910 let edited_buffers_already_open = {
1911 let other_editors: Vec<Entity<Editor>> = workspace
1912 .read(cx)
1913 .panes()
1914 .iter()
1915 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1916 .filter(|editor| editor.entity_id() != cx.entity_id())
1917 .collect();
1918
1919 transaction.0.keys().all(|buffer| {
1920 other_editors.iter().any(|editor| {
1921 let multi_buffer = editor.read(cx).buffer();
1922 multi_buffer.read(cx).is_singleton()
1923 && multi_buffer.read(cx).as_singleton().map_or(
1924 false,
1925 |singleton| {
1926 singleton.entity_id() == buffer.entity_id()
1927 },
1928 )
1929 })
1930 })
1931 };
1932
1933 if !edited_buffers_already_open {
1934 let workspace = workspace.downgrade();
1935 let transaction = transaction.clone();
1936 cx.defer_in(window, move |_, window, cx| {
1937 cx.spawn_in(window, async move |editor, cx| {
1938 Self::open_project_transaction(
1939 &editor,
1940 workspace,
1941 transaction,
1942 "Rename".to_string(),
1943 cx,
1944 )
1945 .await
1946 .ok()
1947 })
1948 .detach();
1949 });
1950 }
1951 }
1952 }
1953
1954 _ => {}
1955 },
1956 ));
1957 if let Some(task_inventory) = project
1958 .read(cx)
1959 .task_store()
1960 .read(cx)
1961 .task_inventory()
1962 .cloned()
1963 {
1964 project_subscriptions.push(cx.observe_in(
1965 &task_inventory,
1966 window,
1967 |editor, _, window, cx| {
1968 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1969 },
1970 ));
1971 };
1972
1973 project_subscriptions.push(cx.subscribe_in(
1974 &project.read(cx).breakpoint_store(),
1975 window,
1976 |editor, _, event, window, cx| match event {
1977 BreakpointStoreEvent::ClearDebugLines => {
1978 editor.clear_row_highlights::<ActiveDebugLine>();
1979 editor.refresh_inline_values(cx);
1980 }
1981 BreakpointStoreEvent::SetDebugLine => {
1982 if editor.go_to_active_debug_line(window, cx) {
1983 cx.stop_propagation();
1984 }
1985
1986 editor.refresh_inline_values(cx);
1987 }
1988 _ => {}
1989 },
1990 ));
1991 let git_store = project.read(cx).git_store().clone();
1992 let project = project.clone();
1993 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1994 if let GitStoreEvent::RepositoryUpdated(
1995 _,
1996 RepositoryEvent::Updated {
1997 new_instance: true, ..
1998 },
1999 _,
2000 ) = event
2001 {
2002 this.load_diff_task = Some(
2003 update_uncommitted_diff_for_buffer(
2004 cx.entity(),
2005 &project,
2006 this.buffer.read(cx).all_buffers(),
2007 this.buffer.clone(),
2008 cx,
2009 )
2010 .shared(),
2011 );
2012 }
2013 }));
2014 }
2015
2016 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2017
2018 let inlay_hint_settings =
2019 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2020 let focus_handle = cx.focus_handle();
2021 if !is_minimap {
2022 cx.on_focus(&focus_handle, window, Self::handle_focus)
2023 .detach();
2024 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2025 .detach();
2026 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2027 .detach();
2028 cx.on_blur(&focus_handle, window, Self::handle_blur)
2029 .detach();
2030 cx.observe_pending_input(window, Self::observe_pending_input)
2031 .detach();
2032 }
2033
2034 let show_indent_guides =
2035 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2036 Some(false)
2037 } else {
2038 None
2039 };
2040
2041 let breakpoint_store = match (&mode, project.as_ref()) {
2042 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2043 _ => None,
2044 };
2045
2046 let mut code_action_providers = Vec::new();
2047 let mut load_uncommitted_diff = None;
2048 if let Some(project) = project.clone() {
2049 load_uncommitted_diff = Some(
2050 update_uncommitted_diff_for_buffer(
2051 cx.entity(),
2052 &project,
2053 buffer.read(cx).all_buffers(),
2054 buffer.clone(),
2055 cx,
2056 )
2057 .shared(),
2058 );
2059 code_action_providers.push(Rc::new(project) as Rc<_>);
2060 }
2061
2062 let mut editor = Self {
2063 focus_handle,
2064 show_cursor_when_unfocused: false,
2065 last_focused_descendant: None,
2066 buffer: buffer.clone(),
2067 display_map: display_map.clone(),
2068 selections,
2069 scroll_manager: ScrollManager::new(cx),
2070 columnar_selection_state: None,
2071 add_selections_state: None,
2072 select_next_state: None,
2073 select_prev_state: None,
2074 selection_history: SelectionHistory::default(),
2075 defer_selection_effects: false,
2076 deferred_selection_effects_state: None,
2077 autoclose_regions: Vec::new(),
2078 snippet_stack: InvalidationStack::default(),
2079 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2080 ime_transaction: None,
2081 active_diagnostics: ActiveDiagnostic::None,
2082 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2083 inline_diagnostics_update: Task::ready(()),
2084 inline_diagnostics: Vec::new(),
2085 soft_wrap_mode_override,
2086 diagnostics_max_severity,
2087 hard_wrap: None,
2088 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2089 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2090 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2091 project,
2092 blink_manager: blink_manager.clone(),
2093 show_local_selections: true,
2094 show_scrollbars: ScrollbarAxes {
2095 horizontal: full_mode,
2096 vertical: full_mode,
2097 },
2098 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2099 offset_content: !matches!(mode, EditorMode::SingleLine),
2100 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2101 show_gutter: full_mode,
2102 show_line_numbers: (!full_mode).then_some(false),
2103 use_relative_line_numbers: None,
2104 disable_expand_excerpt_buttons: !full_mode,
2105 show_git_diff_gutter: None,
2106 show_code_actions: None,
2107 show_runnables: None,
2108 show_breakpoints: None,
2109 show_wrap_guides: None,
2110 show_indent_guides,
2111 placeholder_text: None,
2112 highlight_order: 0,
2113 highlighted_rows: HashMap::default(),
2114 background_highlights: HashMap::default(),
2115 gutter_highlights: HashMap::default(),
2116 scrollbar_marker_state: ScrollbarMarkerState::default(),
2117 active_indent_guides_state: ActiveIndentGuidesState::default(),
2118 nav_history: None,
2119 context_menu: RefCell::new(None),
2120 context_menu_options: None,
2121 mouse_context_menu: None,
2122 completion_tasks: Vec::new(),
2123 inline_blame_popover: None,
2124 inline_blame_popover_show_task: None,
2125 signature_help_state: SignatureHelpState::default(),
2126 auto_signature_help: None,
2127 find_all_references_task_sources: Vec::new(),
2128 next_completion_id: 0,
2129 next_inlay_id: 0,
2130 code_action_providers,
2131 available_code_actions: None,
2132 code_actions_task: None,
2133 quick_selection_highlight_task: None,
2134 debounced_selection_highlight_task: None,
2135 document_highlights_task: None,
2136 linked_editing_range_task: None,
2137 pending_rename: None,
2138 searchable: !is_minimap,
2139 cursor_shape: EditorSettings::get_global(cx)
2140 .cursor_shape
2141 .unwrap_or_default(),
2142 current_line_highlight: None,
2143 autoindent_mode: Some(AutoindentMode::EachLine),
2144 collapse_matches: false,
2145 workspace: None,
2146 input_enabled: !is_minimap,
2147 use_modal_editing: full_mode,
2148 read_only: is_minimap,
2149 use_autoclose: true,
2150 use_auto_surround: true,
2151 auto_replace_emoji_shortcode: false,
2152 jsx_tag_auto_close_enabled_in_any_buffer: false,
2153 leader_id: None,
2154 remote_id: None,
2155 hover_state: HoverState::default(),
2156 pending_mouse_down: None,
2157 hovered_link_state: None,
2158 edit_prediction_provider: None,
2159 active_edit_prediction: None,
2160 stale_edit_prediction_in_menu: None,
2161 edit_prediction_preview: EditPredictionPreview::Inactive {
2162 released_too_fast: false,
2163 },
2164 inline_diagnostics_enabled: full_mode,
2165 diagnostics_enabled: full_mode,
2166 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2167 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2168 gutter_hovered: false,
2169 pixel_position_of_newest_cursor: None,
2170 last_bounds: None,
2171 last_position_map: None,
2172 expect_bounds_change: None,
2173 gutter_dimensions: GutterDimensions::default(),
2174 style: None,
2175 show_cursor_names: false,
2176 hovered_cursors: HashMap::default(),
2177 next_editor_action_id: EditorActionId::default(),
2178 editor_actions: Rc::default(),
2179 edit_predictions_hidden_for_vim_mode: false,
2180 show_edit_predictions_override: None,
2181 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2182 edit_prediction_settings: EditPredictionSettings::Disabled,
2183 edit_prediction_indent_conflict: false,
2184 edit_prediction_requires_modifier_in_indent_conflict: true,
2185 custom_context_menu: None,
2186 show_git_blame_gutter: false,
2187 show_git_blame_inline: false,
2188 show_selection_menu: None,
2189 show_git_blame_inline_delay_task: None,
2190 git_blame_inline_enabled: full_mode
2191 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2192 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2193 serialize_dirty_buffers: !is_minimap
2194 && ProjectSettings::get_global(cx)
2195 .session
2196 .restore_unsaved_buffers,
2197 blame: None,
2198 blame_subscription: None,
2199 tasks: BTreeMap::default(),
2200
2201 breakpoint_store,
2202 gutter_breakpoint_indicator: (None, None),
2203 hovered_diff_hunk_row: None,
2204 _subscriptions: (!is_minimap)
2205 .then(|| {
2206 vec![
2207 cx.observe(&buffer, Self::on_buffer_changed),
2208 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2209 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2210 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2211 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2212 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2213 cx.observe_window_activation(window, |editor, window, cx| {
2214 let active = window.is_window_active();
2215 editor.blink_manager.update(cx, |blink_manager, cx| {
2216 if active {
2217 blink_manager.enable(cx);
2218 } else {
2219 blink_manager.disable(cx);
2220 }
2221 });
2222 if active {
2223 editor.show_mouse_cursor(cx);
2224 }
2225 }),
2226 ]
2227 })
2228 .unwrap_or_default(),
2229 tasks_update_task: None,
2230 pull_diagnostics_task: Task::ready(()),
2231 colors: None,
2232 next_color_inlay_id: 0,
2233 linked_edit_ranges: Default::default(),
2234 in_project_search: false,
2235 previous_search_ranges: None,
2236 breadcrumb_header: None,
2237 focused_block: None,
2238 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2239 addons: HashMap::default(),
2240 registered_buffers: HashMap::default(),
2241 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2242 selection_mark_mode: false,
2243 toggle_fold_multiple_buffers: Task::ready(()),
2244 serialize_selections: Task::ready(()),
2245 serialize_folds: Task::ready(()),
2246 text_style_refinement: None,
2247 load_diff_task: load_uncommitted_diff,
2248 temporary_diff_override: false,
2249 mouse_cursor_hidden: false,
2250 minimap: None,
2251 hide_mouse_mode: EditorSettings::get_global(cx)
2252 .hide_mouse
2253 .unwrap_or_default(),
2254 change_list: ChangeList::new(),
2255 mode,
2256 selection_drag_state: SelectionDragState::None,
2257 folding_newlines: Task::ready(()),
2258 };
2259
2260 if is_minimap {
2261 return editor;
2262 }
2263
2264 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2265 editor
2266 ._subscriptions
2267 .push(cx.observe(breakpoints, |_, _, cx| {
2268 cx.notify();
2269 }));
2270 }
2271 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2272 editor._subscriptions.extend(project_subscriptions);
2273
2274 editor._subscriptions.push(cx.subscribe_in(
2275 &cx.entity(),
2276 window,
2277 |editor, _, e: &EditorEvent, window, cx| match e {
2278 EditorEvent::ScrollPositionChanged { local, .. } => {
2279 if *local {
2280 let new_anchor = editor.scroll_manager.anchor();
2281 let snapshot = editor.snapshot(window, cx);
2282 editor.update_restoration_data(cx, move |data| {
2283 data.scroll_position = (
2284 new_anchor.top_row(&snapshot.buffer_snapshot),
2285 new_anchor.offset,
2286 );
2287 });
2288 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2289 editor.inline_blame_popover.take();
2290 }
2291 }
2292 EditorEvent::Edited { .. } => {
2293 if !vim_enabled(cx) {
2294 let (map, selections) = editor.selections.all_adjusted_display(cx);
2295 let pop_state = editor
2296 .change_list
2297 .last()
2298 .map(|previous| {
2299 previous.len() == selections.len()
2300 && previous.iter().enumerate().all(|(ix, p)| {
2301 p.to_display_point(&map).row()
2302 == selections[ix].head().row()
2303 })
2304 })
2305 .unwrap_or(false);
2306 let new_positions = selections
2307 .into_iter()
2308 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2309 .collect();
2310 editor
2311 .change_list
2312 .push_to_change_list(pop_state, new_positions);
2313 }
2314 }
2315 _ => (),
2316 },
2317 ));
2318
2319 if let Some(dap_store) = editor
2320 .project
2321 .as_ref()
2322 .map(|project| project.read(cx).dap_store())
2323 {
2324 let weak_editor = cx.weak_entity();
2325
2326 editor
2327 ._subscriptions
2328 .push(
2329 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2330 let session_entity = cx.entity();
2331 weak_editor
2332 .update(cx, |editor, cx| {
2333 editor._subscriptions.push(
2334 cx.subscribe(&session_entity, Self::on_debug_session_event),
2335 );
2336 })
2337 .ok();
2338 }),
2339 );
2340
2341 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2342 editor
2343 ._subscriptions
2344 .push(cx.subscribe(&session, Self::on_debug_session_event));
2345 }
2346 }
2347
2348 // skip adding the initial selection to selection history
2349 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2350 editor.end_selection(window, cx);
2351 editor.selection_history.mode = SelectionHistoryMode::Normal;
2352
2353 editor.scroll_manager.show_scrollbars(window, cx);
2354 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2355
2356 if full_mode {
2357 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2358 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2359
2360 if editor.git_blame_inline_enabled {
2361 editor.start_git_blame_inline(false, window, cx);
2362 }
2363
2364 editor.go_to_active_debug_line(window, cx);
2365
2366 if let Some(buffer) = buffer.read(cx).as_singleton()
2367 && let Some(project) = editor.project()
2368 {
2369 let handle = project.update(cx, |project, cx| {
2370 project.register_buffer_with_language_servers(&buffer, cx)
2371 });
2372 editor
2373 .registered_buffers
2374 .insert(buffer.read(cx).remote_id(), handle);
2375 }
2376
2377 editor.minimap =
2378 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2379 editor.colors = Some(LspColorData::new(cx));
2380 editor.update_lsp_data(false, None, window, cx);
2381 }
2382
2383 if editor.mode.is_full() {
2384 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2385 }
2386
2387 editor
2388 }
2389
2390 pub fn deploy_mouse_context_menu(
2391 &mut self,
2392 position: gpui::Point<Pixels>,
2393 context_menu: Entity<ContextMenu>,
2394 window: &mut Window,
2395 cx: &mut Context<Self>,
2396 ) {
2397 self.mouse_context_menu = Some(MouseContextMenu::new(
2398 self,
2399 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2400 context_menu,
2401 window,
2402 cx,
2403 ));
2404 }
2405
2406 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2407 self.mouse_context_menu
2408 .as_ref()
2409 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2410 }
2411
2412 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2413 if self
2414 .selections
2415 .pending
2416 .as_ref()
2417 .is_some_and(|pending_selection| {
2418 let snapshot = self.buffer().read(cx).snapshot(cx);
2419 pending_selection
2420 .selection
2421 .range()
2422 .includes(range, &snapshot)
2423 })
2424 {
2425 return true;
2426 }
2427
2428 self.selections
2429 .disjoint_in_range::<usize>(range.clone(), cx)
2430 .into_iter()
2431 .any(|selection| {
2432 // This is needed to cover a corner case, if we just check for an existing
2433 // selection in the fold range, having a cursor at the start of the fold
2434 // marks it as selected. Non-empty selections don't cause this.
2435 let length = selection.end - selection.start;
2436 length > 0
2437 })
2438 }
2439
2440 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2441 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2442 }
2443
2444 fn key_context_internal(
2445 &self,
2446 has_active_edit_prediction: bool,
2447 window: &Window,
2448 cx: &App,
2449 ) -> KeyContext {
2450 let mut key_context = KeyContext::new_with_defaults();
2451 key_context.add("Editor");
2452 let mode = match self.mode {
2453 EditorMode::SingleLine => "single_line",
2454 EditorMode::AutoHeight { .. } => "auto_height",
2455 EditorMode::Minimap { .. } => "minimap",
2456 EditorMode::Full { .. } => "full",
2457 };
2458
2459 if EditorSettings::jupyter_enabled(cx) {
2460 key_context.add("jupyter");
2461 }
2462
2463 key_context.set("mode", mode);
2464 if self.pending_rename.is_some() {
2465 key_context.add("renaming");
2466 }
2467
2468 match self.context_menu.borrow().as_ref() {
2469 Some(CodeContextMenu::Completions(menu)) => {
2470 if menu.visible() {
2471 key_context.add("menu");
2472 key_context.add("showing_completions");
2473 }
2474 }
2475 Some(CodeContextMenu::CodeActions(menu)) => {
2476 if menu.visible() {
2477 key_context.add("menu");
2478 key_context.add("showing_code_actions")
2479 }
2480 }
2481 None => {}
2482 }
2483
2484 if self.signature_help_state.has_multiple_signatures() {
2485 key_context.add("showing_signature_help");
2486 }
2487
2488 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2489 if !self.focus_handle(cx).contains_focused(window, cx)
2490 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2491 {
2492 for addon in self.addons.values() {
2493 addon.extend_key_context(&mut key_context, cx)
2494 }
2495 }
2496
2497 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2498 if let Some(extension) = singleton_buffer
2499 .read(cx)
2500 .file()
2501 .and_then(|file| file.path().extension()?.to_str())
2502 {
2503 key_context.set("extension", extension.to_string());
2504 }
2505 } else {
2506 key_context.add("multibuffer");
2507 }
2508
2509 if has_active_edit_prediction {
2510 if self.edit_prediction_in_conflict() {
2511 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2512 } else {
2513 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2514 key_context.add("copilot_suggestion");
2515 }
2516 }
2517
2518 if self.selection_mark_mode {
2519 key_context.add("selection_mode");
2520 }
2521
2522 key_context
2523 }
2524
2525 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2526 if self.mouse_cursor_hidden {
2527 self.mouse_cursor_hidden = false;
2528 cx.notify();
2529 }
2530 }
2531
2532 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2533 let hide_mouse_cursor = match origin {
2534 HideMouseCursorOrigin::TypingAction => {
2535 matches!(
2536 self.hide_mouse_mode,
2537 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2538 )
2539 }
2540 HideMouseCursorOrigin::MovementAction => {
2541 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2542 }
2543 };
2544 if self.mouse_cursor_hidden != hide_mouse_cursor {
2545 self.mouse_cursor_hidden = hide_mouse_cursor;
2546 cx.notify();
2547 }
2548 }
2549
2550 pub fn edit_prediction_in_conflict(&self) -> bool {
2551 if !self.show_edit_predictions_in_menu() {
2552 return false;
2553 }
2554
2555 let showing_completions = self
2556 .context_menu
2557 .borrow()
2558 .as_ref()
2559 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2560
2561 showing_completions
2562 || self.edit_prediction_requires_modifier()
2563 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2564 // bindings to insert tab characters.
2565 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2566 }
2567
2568 pub fn accept_edit_prediction_keybind(
2569 &self,
2570 accept_partial: bool,
2571 window: &Window,
2572 cx: &App,
2573 ) -> AcceptEditPredictionBinding {
2574 let key_context = self.key_context_internal(true, window, cx);
2575 let in_conflict = self.edit_prediction_in_conflict();
2576
2577 let bindings = if accept_partial {
2578 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2579 } else {
2580 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2581 };
2582
2583 // TODO: if the binding contains multiple keystrokes, display all of them, not
2584 // just the first one.
2585 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2586 !in_conflict
2587 || binding
2588 .keystrokes()
2589 .first()
2590 .is_some_and(|keystroke| keystroke.modifiers().modified())
2591 }))
2592 }
2593
2594 pub fn new_file(
2595 workspace: &mut Workspace,
2596 _: &workspace::NewFile,
2597 window: &mut Window,
2598 cx: &mut Context<Workspace>,
2599 ) {
2600 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2601 "Failed to create buffer",
2602 window,
2603 cx,
2604 |e, _, _| match e.error_code() {
2605 ErrorCode::RemoteUpgradeRequired => Some(format!(
2606 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2607 e.error_tag("required").unwrap_or("the latest version")
2608 )),
2609 _ => None,
2610 },
2611 );
2612 }
2613
2614 pub fn new_in_workspace(
2615 workspace: &mut Workspace,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) -> Task<Result<Entity<Editor>>> {
2619 let project = workspace.project().clone();
2620 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2621
2622 cx.spawn_in(window, async move |workspace, cx| {
2623 let buffer = create.await?;
2624 workspace.update_in(cx, |workspace, window, cx| {
2625 let editor =
2626 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2627 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2628 editor
2629 })
2630 })
2631 }
2632
2633 fn new_file_vertical(
2634 workspace: &mut Workspace,
2635 _: &workspace::NewFileSplitVertical,
2636 window: &mut Window,
2637 cx: &mut Context<Workspace>,
2638 ) {
2639 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2640 }
2641
2642 fn new_file_horizontal(
2643 workspace: &mut Workspace,
2644 _: &workspace::NewFileSplitHorizontal,
2645 window: &mut Window,
2646 cx: &mut Context<Workspace>,
2647 ) {
2648 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2649 }
2650
2651 fn new_file_in_direction(
2652 workspace: &mut Workspace,
2653 direction: SplitDirection,
2654 window: &mut Window,
2655 cx: &mut Context<Workspace>,
2656 ) {
2657 let project = workspace.project().clone();
2658 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2659
2660 cx.spawn_in(window, async move |workspace, cx| {
2661 let buffer = create.await?;
2662 workspace.update_in(cx, move |workspace, window, cx| {
2663 workspace.split_item(
2664 direction,
2665 Box::new(
2666 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2667 ),
2668 window,
2669 cx,
2670 )
2671 })?;
2672 anyhow::Ok(())
2673 })
2674 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2675 match e.error_code() {
2676 ErrorCode::RemoteUpgradeRequired => Some(format!(
2677 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2678 e.error_tag("required").unwrap_or("the latest version")
2679 )),
2680 _ => None,
2681 }
2682 });
2683 }
2684
2685 pub fn leader_id(&self) -> Option<CollaboratorId> {
2686 self.leader_id
2687 }
2688
2689 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2690 &self.buffer
2691 }
2692
2693 pub fn project(&self) -> Option<&Entity<Project>> {
2694 self.project.as_ref()
2695 }
2696
2697 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2698 self.workspace.as_ref()?.0.upgrade()
2699 }
2700
2701 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2702 self.buffer().read(cx).title(cx)
2703 }
2704
2705 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2706 let git_blame_gutter_max_author_length = self
2707 .render_git_blame_gutter(cx)
2708 .then(|| {
2709 if let Some(blame) = self.blame.as_ref() {
2710 let max_author_length =
2711 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2712 Some(max_author_length)
2713 } else {
2714 None
2715 }
2716 })
2717 .flatten();
2718
2719 EditorSnapshot {
2720 mode: self.mode.clone(),
2721 show_gutter: self.show_gutter,
2722 show_line_numbers: self.show_line_numbers,
2723 show_git_diff_gutter: self.show_git_diff_gutter,
2724 show_code_actions: self.show_code_actions,
2725 show_runnables: self.show_runnables,
2726 show_breakpoints: self.show_breakpoints,
2727 git_blame_gutter_max_author_length,
2728 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2729 scroll_anchor: self.scroll_manager.anchor(),
2730 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2731 placeholder_text: self.placeholder_text.clone(),
2732 is_focused: self.focus_handle.is_focused(window),
2733 current_line_highlight: self
2734 .current_line_highlight
2735 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2736 gutter_hovered: self.gutter_hovered,
2737 }
2738 }
2739
2740 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2741 self.buffer.read(cx).language_at(point, cx)
2742 }
2743
2744 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2745 self.buffer.read(cx).read(cx).file_at(point).cloned()
2746 }
2747
2748 pub fn active_excerpt(
2749 &self,
2750 cx: &App,
2751 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2752 self.buffer
2753 .read(cx)
2754 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2755 }
2756
2757 pub fn mode(&self) -> &EditorMode {
2758 &self.mode
2759 }
2760
2761 pub fn set_mode(&mut self, mode: EditorMode) {
2762 self.mode = mode;
2763 }
2764
2765 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2766 self.collaboration_hub.as_deref()
2767 }
2768
2769 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2770 self.collaboration_hub = Some(hub);
2771 }
2772
2773 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2774 self.in_project_search = in_project_search;
2775 }
2776
2777 pub fn set_custom_context_menu(
2778 &mut self,
2779 f: impl 'static
2780 + Fn(
2781 &mut Self,
2782 DisplayPoint,
2783 &mut Window,
2784 &mut Context<Self>,
2785 ) -> Option<Entity<ui::ContextMenu>>,
2786 ) {
2787 self.custom_context_menu = Some(Box::new(f))
2788 }
2789
2790 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2791 self.completion_provider = provider;
2792 }
2793
2794 #[cfg(any(test, feature = "test-support"))]
2795 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2796 self.completion_provider.clone()
2797 }
2798
2799 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2800 self.semantics_provider.clone()
2801 }
2802
2803 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2804 self.semantics_provider = provider;
2805 }
2806
2807 pub fn set_edit_prediction_provider<T>(
2808 &mut self,
2809 provider: Option<Entity<T>>,
2810 window: &mut Window,
2811 cx: &mut Context<Self>,
2812 ) where
2813 T: EditPredictionProvider,
2814 {
2815 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2816 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2817 if this.focus_handle.is_focused(window) {
2818 this.update_visible_edit_prediction(window, cx);
2819 }
2820 }),
2821 provider: Arc::new(provider),
2822 });
2823 self.update_edit_prediction_settings(cx);
2824 self.refresh_edit_prediction(false, false, window, cx);
2825 }
2826
2827 pub fn placeholder_text(&self) -> Option<&str> {
2828 self.placeholder_text.as_deref()
2829 }
2830
2831 pub fn set_placeholder_text(
2832 &mut self,
2833 placeholder_text: impl Into<Arc<str>>,
2834 cx: &mut Context<Self>,
2835 ) {
2836 let placeholder_text = Some(placeholder_text.into());
2837 if self.placeholder_text != placeholder_text {
2838 self.placeholder_text = placeholder_text;
2839 cx.notify();
2840 }
2841 }
2842
2843 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2844 self.cursor_shape = cursor_shape;
2845
2846 // Disrupt blink for immediate user feedback that the cursor shape has changed
2847 self.blink_manager.update(cx, BlinkManager::show_cursor);
2848
2849 cx.notify();
2850 }
2851
2852 pub fn set_current_line_highlight(
2853 &mut self,
2854 current_line_highlight: Option<CurrentLineHighlight>,
2855 ) {
2856 self.current_line_highlight = current_line_highlight;
2857 }
2858
2859 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2860 self.collapse_matches = collapse_matches;
2861 }
2862
2863 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2864 let buffers = self.buffer.read(cx).all_buffers();
2865 let Some(project) = self.project.as_ref() else {
2866 return;
2867 };
2868 project.update(cx, |project, cx| {
2869 for buffer in buffers {
2870 self.registered_buffers
2871 .entry(buffer.read(cx).remote_id())
2872 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2873 }
2874 })
2875 }
2876
2877 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2878 if self.collapse_matches {
2879 return range.start..range.start;
2880 }
2881 range.clone()
2882 }
2883
2884 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2885 if self.display_map.read(cx).clip_at_line_ends != clip {
2886 self.display_map
2887 .update(cx, |map, _| map.clip_at_line_ends = clip);
2888 }
2889 }
2890
2891 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2892 self.input_enabled = input_enabled;
2893 }
2894
2895 pub fn set_edit_predictions_hidden_for_vim_mode(
2896 &mut self,
2897 hidden: bool,
2898 window: &mut Window,
2899 cx: &mut Context<Self>,
2900 ) {
2901 if hidden != self.edit_predictions_hidden_for_vim_mode {
2902 self.edit_predictions_hidden_for_vim_mode = hidden;
2903 if hidden {
2904 self.update_visible_edit_prediction(window, cx);
2905 } else {
2906 self.refresh_edit_prediction(true, false, window, cx);
2907 }
2908 }
2909 }
2910
2911 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2912 self.menu_edit_predictions_policy = value;
2913 }
2914
2915 pub fn set_autoindent(&mut self, autoindent: bool) {
2916 if autoindent {
2917 self.autoindent_mode = Some(AutoindentMode::EachLine);
2918 } else {
2919 self.autoindent_mode = None;
2920 }
2921 }
2922
2923 pub fn read_only(&self, cx: &App) -> bool {
2924 self.read_only || self.buffer.read(cx).read_only()
2925 }
2926
2927 pub fn set_read_only(&mut self, read_only: bool) {
2928 self.read_only = read_only;
2929 }
2930
2931 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2932 self.use_autoclose = autoclose;
2933 }
2934
2935 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2936 self.use_auto_surround = auto_surround;
2937 }
2938
2939 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2940 self.auto_replace_emoji_shortcode = auto_replace;
2941 }
2942
2943 pub fn toggle_edit_predictions(
2944 &mut self,
2945 _: &ToggleEditPrediction,
2946 window: &mut Window,
2947 cx: &mut Context<Self>,
2948 ) {
2949 if self.show_edit_predictions_override.is_some() {
2950 self.set_show_edit_predictions(None, window, cx);
2951 } else {
2952 let show_edit_predictions = !self.edit_predictions_enabled();
2953 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2954 }
2955 }
2956
2957 pub fn set_show_edit_predictions(
2958 &mut self,
2959 show_edit_predictions: Option<bool>,
2960 window: &mut Window,
2961 cx: &mut Context<Self>,
2962 ) {
2963 self.show_edit_predictions_override = show_edit_predictions;
2964 self.update_edit_prediction_settings(cx);
2965
2966 if let Some(false) = show_edit_predictions {
2967 self.discard_edit_prediction(false, cx);
2968 } else {
2969 self.refresh_edit_prediction(false, true, window, cx);
2970 }
2971 }
2972
2973 fn edit_predictions_disabled_in_scope(
2974 &self,
2975 buffer: &Entity<Buffer>,
2976 buffer_position: language::Anchor,
2977 cx: &App,
2978 ) -> bool {
2979 let snapshot = buffer.read(cx).snapshot();
2980 let settings = snapshot.settings_at(buffer_position, cx);
2981
2982 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2983 return false;
2984 };
2985
2986 scope.override_name().is_some_and(|scope_name| {
2987 settings
2988 .edit_predictions_disabled_in
2989 .iter()
2990 .any(|s| s == scope_name)
2991 })
2992 }
2993
2994 pub fn set_use_modal_editing(&mut self, to: bool) {
2995 self.use_modal_editing = to;
2996 }
2997
2998 pub fn use_modal_editing(&self) -> bool {
2999 self.use_modal_editing
3000 }
3001
3002 fn selections_did_change(
3003 &mut self,
3004 local: bool,
3005 old_cursor_position: &Anchor,
3006 effects: SelectionEffects,
3007 window: &mut Window,
3008 cx: &mut Context<Self>,
3009 ) {
3010 window.invalidate_character_coordinates();
3011
3012 // Copy selections to primary selection buffer
3013 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3014 if local {
3015 let selections = self.selections.all::<usize>(cx);
3016 let buffer_handle = self.buffer.read(cx).read(cx);
3017
3018 let mut text = String::new();
3019 for (index, selection) in selections.iter().enumerate() {
3020 let text_for_selection = buffer_handle
3021 .text_for_range(selection.start..selection.end)
3022 .collect::<String>();
3023
3024 text.push_str(&text_for_selection);
3025 if index != selections.len() - 1 {
3026 text.push('\n');
3027 }
3028 }
3029
3030 if !text.is_empty() {
3031 cx.write_to_primary(ClipboardItem::new_string(text));
3032 }
3033 }
3034
3035 let selection_anchors = self.selections.disjoint_anchors();
3036
3037 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3038 self.buffer.update(cx, |buffer, cx| {
3039 buffer.set_active_selections(
3040 &selection_anchors,
3041 self.selections.line_mode,
3042 self.cursor_shape,
3043 cx,
3044 )
3045 });
3046 }
3047 let display_map = self
3048 .display_map
3049 .update(cx, |display_map, cx| display_map.snapshot(cx));
3050 let buffer = &display_map.buffer_snapshot;
3051 if self.selections.count() == 1 {
3052 self.add_selections_state = None;
3053 }
3054 self.select_next_state = None;
3055 self.select_prev_state = None;
3056 self.select_syntax_node_history.try_clear();
3057 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3058 self.snippet_stack.invalidate(&selection_anchors, buffer);
3059 self.take_rename(false, window, cx);
3060
3061 let newest_selection = self.selections.newest_anchor();
3062 let new_cursor_position = newest_selection.head();
3063 let selection_start = newest_selection.start;
3064
3065 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3066 self.push_to_nav_history(
3067 *old_cursor_position,
3068 Some(new_cursor_position.to_point(buffer)),
3069 false,
3070 effects.nav_history == Some(true),
3071 cx,
3072 );
3073 }
3074
3075 if local {
3076 if let Some(buffer_id) = new_cursor_position.buffer_id
3077 && !self.registered_buffers.contains_key(&buffer_id)
3078 && let Some(project) = self.project.as_ref()
3079 {
3080 project.update(cx, |project, cx| {
3081 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3082 return;
3083 };
3084 self.registered_buffers.insert(
3085 buffer_id,
3086 project.register_buffer_with_language_servers(&buffer, cx),
3087 );
3088 })
3089 }
3090
3091 let mut context_menu = self.context_menu.borrow_mut();
3092 let completion_menu = match context_menu.as_ref() {
3093 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3094 Some(CodeContextMenu::CodeActions(_)) => {
3095 *context_menu = None;
3096 None
3097 }
3098 None => None,
3099 };
3100 let completion_position = completion_menu.map(|menu| menu.initial_position);
3101 drop(context_menu);
3102
3103 if effects.completions
3104 && let Some(completion_position) = completion_position
3105 {
3106 let start_offset = selection_start.to_offset(buffer);
3107 let position_matches = start_offset == completion_position.to_offset(buffer);
3108 let continue_showing = if position_matches {
3109 if self.snippet_stack.is_empty() {
3110 buffer.char_kind_before(start_offset, true) == 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 self.refresh_selected_text_highlights(false, window, cx);
3138 refresh_matching_bracket_highlights(self, window, cx);
3139 self.update_visible_edit_prediction(window, cx);
3140 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3141 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3142 self.inline_blame_popover.take();
3143 if self.git_blame_inline_enabled {
3144 self.start_inline_blame_timer(window, cx);
3145 }
3146 }
3147
3148 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3149 cx.emit(EditorEvent::SelectionsChanged { local });
3150
3151 let selections = &self.selections.disjoint;
3152 if selections.len() == 1 {
3153 cx.emit(SearchEvent::ActiveMatchChanged)
3154 }
3155 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3156 let inmemory_selections = selections
3157 .iter()
3158 .map(|s| {
3159 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3160 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3161 })
3162 .collect();
3163 self.update_restoration_data(cx, |data| {
3164 data.selections = inmemory_selections;
3165 });
3166
3167 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3168 && let Some(workspace_id) =
3169 self.workspace.as_ref().and_then(|workspace| workspace.1)
3170 {
3171 let snapshot = self.buffer().read(cx).snapshot(cx);
3172 let selections = selections.clone();
3173 let background_executor = cx.background_executor().clone();
3174 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3175 self.serialize_selections = cx.background_spawn(async move {
3176 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3177 let db_selections = selections
3178 .iter()
3179 .map(|selection| {
3180 (
3181 selection.start.to_offset(&snapshot),
3182 selection.end.to_offset(&snapshot),
3183 )
3184 })
3185 .collect();
3186
3187 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3188 .await
3189 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3190 .log_err();
3191 });
3192 }
3193 }
3194
3195 cx.notify();
3196 }
3197
3198 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3199 use text::ToOffset as _;
3200 use text::ToPoint as _;
3201
3202 if self.mode.is_minimap()
3203 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3204 {
3205 return;
3206 }
3207
3208 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3209 return;
3210 };
3211
3212 let snapshot = singleton.read(cx).snapshot();
3213 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3214 let display_snapshot = display_map.snapshot(cx);
3215
3216 display_snapshot
3217 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3218 .map(|fold| {
3219 fold.range.start.text_anchor.to_point(&snapshot)
3220 ..fold.range.end.text_anchor.to_point(&snapshot)
3221 })
3222 .collect()
3223 });
3224 self.update_restoration_data(cx, |data| {
3225 data.folds = inmemory_folds;
3226 });
3227
3228 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3229 return;
3230 };
3231 let background_executor = cx.background_executor().clone();
3232 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3233 let db_folds = self.display_map.update(cx, |display_map, cx| {
3234 display_map
3235 .snapshot(cx)
3236 .folds_in_range(0..snapshot.len())
3237 .map(|fold| {
3238 (
3239 fold.range.start.text_anchor.to_offset(&snapshot),
3240 fold.range.end.text_anchor.to_offset(&snapshot),
3241 )
3242 })
3243 .collect()
3244 });
3245 self.serialize_folds = cx.background_spawn(async move {
3246 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3247 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3248 .await
3249 .with_context(|| {
3250 format!(
3251 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3252 )
3253 })
3254 .log_err();
3255 });
3256 }
3257
3258 pub fn sync_selections(
3259 &mut self,
3260 other: Entity<Editor>,
3261 cx: &mut Context<Self>,
3262 ) -> gpui::Subscription {
3263 let other_selections = other.read(cx).selections.disjoint.to_vec();
3264 self.selections.change_with(cx, |selections| {
3265 selections.select_anchors(other_selections);
3266 });
3267
3268 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3269 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3270 let other_selections = other.read(cx).selections.disjoint.to_vec();
3271 if other_selections.is_empty() {
3272 return;
3273 }
3274 this.selections.change_with(cx, |selections| {
3275 selections.select_anchors(other_selections);
3276 });
3277 }
3278 });
3279
3280 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3281 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3282 let these_selections = this.selections.disjoint.to_vec();
3283 if these_selections.is_empty() {
3284 return;
3285 }
3286 other.update(cx, |other_editor, cx| {
3287 other_editor.selections.change_with(cx, |selections| {
3288 selections.select_anchors(these_selections);
3289 })
3290 });
3291 }
3292 });
3293
3294 Subscription::join(other_subscription, this_subscription)
3295 }
3296
3297 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3298 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3299 /// effects of selection change occur at the end of the transaction.
3300 pub fn change_selections<R>(
3301 &mut self,
3302 effects: SelectionEffects,
3303 window: &mut Window,
3304 cx: &mut Context<Self>,
3305 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3306 ) -> R {
3307 if let Some(state) = &mut self.deferred_selection_effects_state {
3308 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3309 state.effects.completions = effects.completions;
3310 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3311 let (changed, result) = self.selections.change_with(cx, change);
3312 state.changed |= changed;
3313 return result;
3314 }
3315 let mut state = DeferredSelectionEffectsState {
3316 changed: false,
3317 effects,
3318 old_cursor_position: self.selections.newest_anchor().head(),
3319 history_entry: SelectionHistoryEntry {
3320 selections: self.selections.disjoint_anchors(),
3321 select_next_state: self.select_next_state.clone(),
3322 select_prev_state: self.select_prev_state.clone(),
3323 add_selections_state: self.add_selections_state.clone(),
3324 },
3325 };
3326 let (changed, result) = self.selections.change_with(cx, change);
3327 state.changed = state.changed || changed;
3328 if self.defer_selection_effects {
3329 self.deferred_selection_effects_state = Some(state);
3330 } else {
3331 self.apply_selection_effects(state, window, cx);
3332 }
3333 result
3334 }
3335
3336 /// Defers the effects of selection change, so that the effects of multiple calls to
3337 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3338 /// to selection history and the state of popovers based on selection position aren't
3339 /// erroneously updated.
3340 pub fn with_selection_effects_deferred<R>(
3341 &mut self,
3342 window: &mut Window,
3343 cx: &mut Context<Self>,
3344 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3345 ) -> R {
3346 let already_deferred = self.defer_selection_effects;
3347 self.defer_selection_effects = true;
3348 let result = update(self, window, cx);
3349 if !already_deferred {
3350 self.defer_selection_effects = false;
3351 if let Some(state) = self.deferred_selection_effects_state.take() {
3352 self.apply_selection_effects(state, window, cx);
3353 }
3354 }
3355 result
3356 }
3357
3358 fn apply_selection_effects(
3359 &mut self,
3360 state: DeferredSelectionEffectsState,
3361 window: &mut Window,
3362 cx: &mut Context<Self>,
3363 ) {
3364 if state.changed {
3365 self.selection_history.push(state.history_entry);
3366
3367 if let Some(autoscroll) = state.effects.scroll {
3368 self.request_autoscroll(autoscroll, cx);
3369 }
3370
3371 let old_cursor_position = &state.old_cursor_position;
3372
3373 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3374
3375 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3376 self.show_signature_help(&ShowSignatureHelp, window, cx);
3377 }
3378 }
3379 }
3380
3381 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3382 where
3383 I: IntoIterator<Item = (Range<S>, T)>,
3384 S: ToOffset,
3385 T: Into<Arc<str>>,
3386 {
3387 if self.read_only(cx) {
3388 return;
3389 }
3390
3391 self.buffer
3392 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3393 }
3394
3395 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3396 where
3397 I: IntoIterator<Item = (Range<S>, T)>,
3398 S: ToOffset,
3399 T: Into<Arc<str>>,
3400 {
3401 if self.read_only(cx) {
3402 return;
3403 }
3404
3405 self.buffer.update(cx, |buffer, cx| {
3406 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3407 });
3408 }
3409
3410 pub fn edit_with_block_indent<I, S, T>(
3411 &mut self,
3412 edits: I,
3413 original_indent_columns: Vec<Option<u32>>,
3414 cx: &mut Context<Self>,
3415 ) where
3416 I: IntoIterator<Item = (Range<S>, T)>,
3417 S: ToOffset,
3418 T: Into<Arc<str>>,
3419 {
3420 if self.read_only(cx) {
3421 return;
3422 }
3423
3424 self.buffer.update(cx, |buffer, cx| {
3425 buffer.edit(
3426 edits,
3427 Some(AutoindentMode::Block {
3428 original_indent_columns,
3429 }),
3430 cx,
3431 )
3432 });
3433 }
3434
3435 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3436 self.hide_context_menu(window, cx);
3437
3438 match phase {
3439 SelectPhase::Begin {
3440 position,
3441 add,
3442 click_count,
3443 } => self.begin_selection(position, add, click_count, window, cx),
3444 SelectPhase::BeginColumnar {
3445 position,
3446 goal_column,
3447 reset,
3448 mode,
3449 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3450 SelectPhase::Extend {
3451 position,
3452 click_count,
3453 } => self.extend_selection(position, click_count, window, cx),
3454 SelectPhase::Update {
3455 position,
3456 goal_column,
3457 scroll_delta,
3458 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3459 SelectPhase::End => self.end_selection(window, cx),
3460 }
3461 }
3462
3463 fn extend_selection(
3464 &mut self,
3465 position: DisplayPoint,
3466 click_count: usize,
3467 window: &mut Window,
3468 cx: &mut Context<Self>,
3469 ) {
3470 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3471 let tail = self.selections.newest::<usize>(cx).tail();
3472 self.begin_selection(position, false, click_count, window, cx);
3473
3474 let position = position.to_offset(&display_map, Bias::Left);
3475 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3476
3477 let mut pending_selection = self
3478 .selections
3479 .pending_anchor()
3480 .expect("extend_selection not called with pending selection");
3481 if position >= tail {
3482 pending_selection.start = tail_anchor;
3483 } else {
3484 pending_selection.end = tail_anchor;
3485 pending_selection.reversed = true;
3486 }
3487
3488 let mut pending_mode = self.selections.pending_mode().unwrap();
3489 match &mut pending_mode {
3490 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3491 _ => {}
3492 }
3493
3494 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3495 SelectionEffects::scroll(Autoscroll::fit())
3496 } else {
3497 SelectionEffects::no_scroll()
3498 };
3499
3500 self.change_selections(effects, window, cx, |s| {
3501 s.set_pending(pending_selection, pending_mode)
3502 });
3503 }
3504
3505 fn begin_selection(
3506 &mut self,
3507 position: DisplayPoint,
3508 add: bool,
3509 click_count: usize,
3510 window: &mut Window,
3511 cx: &mut Context<Self>,
3512 ) {
3513 if !self.focus_handle.is_focused(window) {
3514 self.last_focused_descendant = None;
3515 window.focus(&self.focus_handle);
3516 }
3517
3518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3519 let buffer = &display_map.buffer_snapshot;
3520 let position = display_map.clip_point(position, Bias::Left);
3521
3522 let start;
3523 let end;
3524 let mode;
3525 let mut auto_scroll;
3526 match click_count {
3527 1 => {
3528 start = buffer.anchor_before(position.to_point(&display_map));
3529 end = start;
3530 mode = SelectMode::Character;
3531 auto_scroll = true;
3532 }
3533 2 => {
3534 let position = display_map
3535 .clip_point(position, Bias::Left)
3536 .to_offset(&display_map, Bias::Left);
3537 let (range, _) = buffer.surrounding_word(position, false);
3538 start = buffer.anchor_before(range.start);
3539 end = buffer.anchor_before(range.end);
3540 mode = SelectMode::Word(start..end);
3541 auto_scroll = true;
3542 }
3543 3 => {
3544 let position = display_map
3545 .clip_point(position, Bias::Left)
3546 .to_point(&display_map);
3547 let line_start = display_map.prev_line_boundary(position).0;
3548 let next_line_start = buffer.clip_point(
3549 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3550 Bias::Left,
3551 );
3552 start = buffer.anchor_before(line_start);
3553 end = buffer.anchor_before(next_line_start);
3554 mode = SelectMode::Line(start..end);
3555 auto_scroll = true;
3556 }
3557 _ => {
3558 start = buffer.anchor_before(0);
3559 end = buffer.anchor_before(buffer.len());
3560 mode = SelectMode::All;
3561 auto_scroll = false;
3562 }
3563 }
3564 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3565
3566 let point_to_delete: Option<usize> = {
3567 let selected_points: Vec<Selection<Point>> =
3568 self.selections.disjoint_in_range(start..end, cx);
3569
3570 if !add || click_count > 1 {
3571 None
3572 } else if !selected_points.is_empty() {
3573 Some(selected_points[0].id)
3574 } else {
3575 let clicked_point_already_selected =
3576 self.selections.disjoint.iter().find(|selection| {
3577 selection.start.to_point(buffer) == start.to_point(buffer)
3578 || selection.end.to_point(buffer) == end.to_point(buffer)
3579 });
3580
3581 clicked_point_already_selected.map(|selection| selection.id)
3582 }
3583 };
3584
3585 let selections_count = self.selections.count();
3586 let effects = if auto_scroll {
3587 SelectionEffects::default()
3588 } else {
3589 SelectionEffects::no_scroll()
3590 };
3591
3592 self.change_selections(effects, window, cx, |s| {
3593 if let Some(point_to_delete) = point_to_delete {
3594 s.delete(point_to_delete);
3595
3596 if selections_count == 1 {
3597 s.set_pending_anchor_range(start..end, mode);
3598 }
3599 } else {
3600 if !add {
3601 s.clear_disjoint();
3602 }
3603
3604 s.set_pending_anchor_range(start..end, mode);
3605 }
3606 });
3607 }
3608
3609 fn begin_columnar_selection(
3610 &mut self,
3611 position: DisplayPoint,
3612 goal_column: u32,
3613 reset: bool,
3614 mode: ColumnarMode,
3615 window: &mut Window,
3616 cx: &mut Context<Self>,
3617 ) {
3618 if !self.focus_handle.is_focused(window) {
3619 self.last_focused_descendant = None;
3620 window.focus(&self.focus_handle);
3621 }
3622
3623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3624
3625 if reset {
3626 let pointer_position = display_map
3627 .buffer_snapshot
3628 .anchor_before(position.to_point(&display_map));
3629
3630 self.change_selections(
3631 SelectionEffects::scroll(Autoscroll::newest()),
3632 window,
3633 cx,
3634 |s| {
3635 s.clear_disjoint();
3636 s.set_pending_anchor_range(
3637 pointer_position..pointer_position,
3638 SelectMode::Character,
3639 );
3640 },
3641 );
3642 };
3643
3644 let tail = self.selections.newest::<Point>(cx).tail();
3645 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3646 self.columnar_selection_state = match mode {
3647 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3648 selection_tail: selection_anchor,
3649 display_point: if reset {
3650 if position.column() != goal_column {
3651 Some(DisplayPoint::new(position.row(), goal_column))
3652 } else {
3653 None
3654 }
3655 } else {
3656 None
3657 },
3658 }),
3659 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3660 selection_tail: selection_anchor,
3661 }),
3662 };
3663
3664 if !reset {
3665 self.select_columns(position, goal_column, &display_map, window, cx);
3666 }
3667 }
3668
3669 fn update_selection(
3670 &mut self,
3671 position: DisplayPoint,
3672 goal_column: u32,
3673 scroll_delta: gpui::Point<f32>,
3674 window: &mut Window,
3675 cx: &mut Context<Self>,
3676 ) {
3677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3678
3679 if self.columnar_selection_state.is_some() {
3680 self.select_columns(position, goal_column, &display_map, window, cx);
3681 } else if let Some(mut pending) = self.selections.pending_anchor() {
3682 let buffer = &display_map.buffer_snapshot;
3683 let head;
3684 let tail;
3685 let mode = self.selections.pending_mode().unwrap();
3686 match &mode {
3687 SelectMode::Character => {
3688 head = position.to_point(&display_map);
3689 tail = pending.tail().to_point(buffer);
3690 }
3691 SelectMode::Word(original_range) => {
3692 let offset = display_map
3693 .clip_point(position, Bias::Left)
3694 .to_offset(&display_map, Bias::Left);
3695 let original_range = original_range.to_offset(buffer);
3696
3697 let head_offset = if buffer.is_inside_word(offset, false)
3698 || original_range.contains(&offset)
3699 {
3700 let (word_range, _) = buffer.surrounding_word(offset, false);
3701 if word_range.start < original_range.start {
3702 word_range.start
3703 } else {
3704 word_range.end
3705 }
3706 } else {
3707 offset
3708 };
3709
3710 head = head_offset.to_point(buffer);
3711 if head_offset <= original_range.start {
3712 tail = original_range.end.to_point(buffer);
3713 } else {
3714 tail = original_range.start.to_point(buffer);
3715 }
3716 }
3717 SelectMode::Line(original_range) => {
3718 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3719
3720 let position = display_map
3721 .clip_point(position, Bias::Left)
3722 .to_point(&display_map);
3723 let line_start = display_map.prev_line_boundary(position).0;
3724 let next_line_start = buffer.clip_point(
3725 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3726 Bias::Left,
3727 );
3728
3729 if line_start < original_range.start {
3730 head = line_start
3731 } else {
3732 head = next_line_start
3733 }
3734
3735 if head <= original_range.start {
3736 tail = original_range.end;
3737 } else {
3738 tail = original_range.start;
3739 }
3740 }
3741 SelectMode::All => {
3742 return;
3743 }
3744 };
3745
3746 if head < tail {
3747 pending.start = buffer.anchor_before(head);
3748 pending.end = buffer.anchor_before(tail);
3749 pending.reversed = true;
3750 } else {
3751 pending.start = buffer.anchor_before(tail);
3752 pending.end = buffer.anchor_before(head);
3753 pending.reversed = false;
3754 }
3755
3756 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3757 s.set_pending(pending, mode);
3758 });
3759 } else {
3760 log::error!("update_selection dispatched with no pending selection");
3761 return;
3762 }
3763
3764 self.apply_scroll_delta(scroll_delta, window, cx);
3765 cx.notify();
3766 }
3767
3768 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3769 self.columnar_selection_state.take();
3770 if self.selections.pending_anchor().is_some() {
3771 let selections = self.selections.all::<usize>(cx);
3772 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3773 s.select(selections);
3774 s.clear_pending();
3775 });
3776 }
3777 }
3778
3779 fn select_columns(
3780 &mut self,
3781 head: DisplayPoint,
3782 goal_column: u32,
3783 display_map: &DisplaySnapshot,
3784 window: &mut Window,
3785 cx: &mut Context<Self>,
3786 ) {
3787 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3788 return;
3789 };
3790
3791 let tail = match columnar_state {
3792 ColumnarSelectionState::FromMouse {
3793 selection_tail,
3794 display_point,
3795 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3796 ColumnarSelectionState::FromSelection { selection_tail } => {
3797 selection_tail.to_display_point(display_map)
3798 }
3799 };
3800
3801 let start_row = cmp::min(tail.row(), head.row());
3802 let end_row = cmp::max(tail.row(), head.row());
3803 let start_column = cmp::min(tail.column(), goal_column);
3804 let end_column = cmp::max(tail.column(), goal_column);
3805 let reversed = start_column < tail.column();
3806
3807 let selection_ranges = (start_row.0..=end_row.0)
3808 .map(DisplayRow)
3809 .filter_map(|row| {
3810 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3811 || start_column <= display_map.line_len(row))
3812 && !display_map.is_block_line(row)
3813 {
3814 let start = display_map
3815 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3816 .to_point(display_map);
3817 let end = display_map
3818 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3819 .to_point(display_map);
3820 if reversed {
3821 Some(end..start)
3822 } else {
3823 Some(start..end)
3824 }
3825 } else {
3826 None
3827 }
3828 })
3829 .collect::<Vec<_>>();
3830
3831 let ranges = match columnar_state {
3832 ColumnarSelectionState::FromMouse { .. } => {
3833 let mut non_empty_ranges = selection_ranges
3834 .iter()
3835 .filter(|selection_range| selection_range.start != selection_range.end)
3836 .peekable();
3837 if non_empty_ranges.peek().is_some() {
3838 non_empty_ranges.cloned().collect()
3839 } else {
3840 selection_ranges
3841 }
3842 }
3843 _ => selection_ranges,
3844 };
3845
3846 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3847 s.select_ranges(ranges);
3848 });
3849 cx.notify();
3850 }
3851
3852 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3853 self.selections
3854 .all_adjusted(cx)
3855 .iter()
3856 .any(|selection| !selection.is_empty())
3857 }
3858
3859 pub fn has_pending_nonempty_selection(&self) -> bool {
3860 let pending_nonempty_selection = match self.selections.pending_anchor() {
3861 Some(Selection { start, end, .. }) => start != end,
3862 None => false,
3863 };
3864
3865 pending_nonempty_selection
3866 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3867 }
3868
3869 pub fn has_pending_selection(&self) -> bool {
3870 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3871 }
3872
3873 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3874 self.selection_mark_mode = false;
3875 self.selection_drag_state = SelectionDragState::None;
3876
3877 if self.clear_expanded_diff_hunks(cx) {
3878 cx.notify();
3879 return;
3880 }
3881 if self.dismiss_menus_and_popups(true, window, cx) {
3882 return;
3883 }
3884
3885 if self.mode.is_full()
3886 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3887 {
3888 return;
3889 }
3890
3891 cx.propagate();
3892 }
3893
3894 pub fn dismiss_menus_and_popups(
3895 &mut self,
3896 is_user_requested: bool,
3897 window: &mut Window,
3898 cx: &mut Context<Self>,
3899 ) -> bool {
3900 if self.take_rename(false, window, cx).is_some() {
3901 return true;
3902 }
3903
3904 if hide_hover(self, cx) {
3905 return true;
3906 }
3907
3908 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3909 return true;
3910 }
3911
3912 if self.hide_context_menu(window, cx).is_some() {
3913 return true;
3914 }
3915
3916 if self.mouse_context_menu.take().is_some() {
3917 return true;
3918 }
3919
3920 if is_user_requested && self.discard_edit_prediction(true, cx) {
3921 return true;
3922 }
3923
3924 if self.snippet_stack.pop().is_some() {
3925 return true;
3926 }
3927
3928 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3929 self.dismiss_diagnostics(cx);
3930 return true;
3931 }
3932
3933 false
3934 }
3935
3936 fn linked_editing_ranges_for(
3937 &self,
3938 selection: Range<text::Anchor>,
3939 cx: &App,
3940 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3941 if self.linked_edit_ranges.is_empty() {
3942 return None;
3943 }
3944 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3945 selection.end.buffer_id.and_then(|end_buffer_id| {
3946 if selection.start.buffer_id != Some(end_buffer_id) {
3947 return None;
3948 }
3949 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3950 let snapshot = buffer.read(cx).snapshot();
3951 self.linked_edit_ranges
3952 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3953 .map(|ranges| (ranges, snapshot, buffer))
3954 })?;
3955 use text::ToOffset as TO;
3956 // find offset from the start of current range to current cursor position
3957 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3958
3959 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3960 let start_difference = start_offset - start_byte_offset;
3961 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3962 let end_difference = end_offset - start_byte_offset;
3963 // Current range has associated linked ranges.
3964 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3965 for range in linked_ranges.iter() {
3966 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3967 let end_offset = start_offset + end_difference;
3968 let start_offset = start_offset + start_difference;
3969 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3970 continue;
3971 }
3972 if self.selections.disjoint_anchor_ranges().any(|s| {
3973 if s.start.buffer_id != selection.start.buffer_id
3974 || s.end.buffer_id != selection.end.buffer_id
3975 {
3976 return false;
3977 }
3978 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3979 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3980 }) {
3981 continue;
3982 }
3983 let start = buffer_snapshot.anchor_after(start_offset);
3984 let end = buffer_snapshot.anchor_after(end_offset);
3985 linked_edits
3986 .entry(buffer.clone())
3987 .or_default()
3988 .push(start..end);
3989 }
3990 Some(linked_edits)
3991 }
3992
3993 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3994 let text: Arc<str> = text.into();
3995
3996 if self.read_only(cx) {
3997 return;
3998 }
3999
4000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4001
4002 let selections = self.selections.all_adjusted(cx);
4003 let mut bracket_inserted = false;
4004 let mut edits = Vec::new();
4005 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4006 let mut new_selections = Vec::with_capacity(selections.len());
4007 let mut new_autoclose_regions = Vec::new();
4008 let snapshot = self.buffer.read(cx).read(cx);
4009 let mut clear_linked_edit_ranges = false;
4010
4011 for (selection, autoclose_region) in
4012 self.selections_with_autoclose_regions(selections, &snapshot)
4013 {
4014 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4015 // Determine if the inserted text matches the opening or closing
4016 // bracket of any of this language's bracket pairs.
4017 let mut bracket_pair = None;
4018 let mut is_bracket_pair_start = false;
4019 let mut is_bracket_pair_end = false;
4020 if !text.is_empty() {
4021 let mut bracket_pair_matching_end = None;
4022 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4023 // and they are removing the character that triggered IME popup.
4024 for (pair, enabled) in scope.brackets() {
4025 if !pair.close && !pair.surround {
4026 continue;
4027 }
4028
4029 if enabled && pair.start.ends_with(text.as_ref()) {
4030 let prefix_len = pair.start.len() - text.len();
4031 let preceding_text_matches_prefix = prefix_len == 0
4032 || (selection.start.column >= (prefix_len as u32)
4033 && snapshot.contains_str_at(
4034 Point::new(
4035 selection.start.row,
4036 selection.start.column - (prefix_len as u32),
4037 ),
4038 &pair.start[..prefix_len],
4039 ));
4040 if preceding_text_matches_prefix {
4041 bracket_pair = Some(pair.clone());
4042 is_bracket_pair_start = true;
4043 break;
4044 }
4045 }
4046 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4047 {
4048 // take first bracket pair matching end, but don't break in case a later bracket
4049 // pair matches start
4050 bracket_pair_matching_end = Some(pair.clone());
4051 }
4052 }
4053 if let Some(end) = bracket_pair_matching_end
4054 && bracket_pair.is_none()
4055 {
4056 bracket_pair = Some(end);
4057 is_bracket_pair_end = true;
4058 }
4059 }
4060
4061 if let Some(bracket_pair) = bracket_pair {
4062 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4063 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4064 let auto_surround =
4065 self.use_auto_surround && snapshot_settings.use_auto_surround;
4066 if selection.is_empty() {
4067 if is_bracket_pair_start {
4068 // If the inserted text is a suffix of an opening bracket and the
4069 // selection is preceded by the rest of the opening bracket, then
4070 // insert the closing bracket.
4071 let following_text_allows_autoclose = snapshot
4072 .chars_at(selection.start)
4073 .next()
4074 .is_none_or(|c| scope.should_autoclose_before(c));
4075
4076 let preceding_text_allows_autoclose = selection.start.column == 0
4077 || snapshot
4078 .reversed_chars_at(selection.start)
4079 .next()
4080 .is_none_or(|c| {
4081 bracket_pair.start != bracket_pair.end
4082 || !snapshot
4083 .char_classifier_at(selection.start)
4084 .is_word(c)
4085 });
4086
4087 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4088 && bracket_pair.start.len() == 1
4089 {
4090 let target = bracket_pair.start.chars().next().unwrap();
4091 let current_line_count = snapshot
4092 .reversed_chars_at(selection.start)
4093 .take_while(|&c| c != '\n')
4094 .filter(|&c| c == target)
4095 .count();
4096 current_line_count % 2 == 1
4097 } else {
4098 false
4099 };
4100
4101 if autoclose
4102 && bracket_pair.close
4103 && following_text_allows_autoclose
4104 && preceding_text_allows_autoclose
4105 && !is_closing_quote
4106 {
4107 let anchor = snapshot.anchor_before(selection.end);
4108 new_selections.push((selection.map(|_| anchor), text.len()));
4109 new_autoclose_regions.push((
4110 anchor,
4111 text.len(),
4112 selection.id,
4113 bracket_pair.clone(),
4114 ));
4115 edits.push((
4116 selection.range(),
4117 format!("{}{}", text, bracket_pair.end).into(),
4118 ));
4119 bracket_inserted = true;
4120 continue;
4121 }
4122 }
4123
4124 if let Some(region) = autoclose_region {
4125 // If the selection is followed by an auto-inserted closing bracket,
4126 // then don't insert that closing bracket again; just move the selection
4127 // past the closing bracket.
4128 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4129 && text.as_ref() == region.pair.end.as_str()
4130 && snapshot.contains_str_at(region.range.end, text.as_ref());
4131 if should_skip {
4132 let anchor = snapshot.anchor_after(selection.end);
4133 new_selections
4134 .push((selection.map(|_| anchor), region.pair.end.len()));
4135 continue;
4136 }
4137 }
4138
4139 let always_treat_brackets_as_autoclosed = snapshot
4140 .language_settings_at(selection.start, cx)
4141 .always_treat_brackets_as_autoclosed;
4142 if always_treat_brackets_as_autoclosed
4143 && is_bracket_pair_end
4144 && snapshot.contains_str_at(selection.end, text.as_ref())
4145 {
4146 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4147 // and the inserted text is a closing bracket and the selection is followed
4148 // by the closing bracket then move the selection past the closing bracket.
4149 let anchor = snapshot.anchor_after(selection.end);
4150 new_selections.push((selection.map(|_| anchor), text.len()));
4151 continue;
4152 }
4153 }
4154 // If an opening bracket is 1 character long and is typed while
4155 // text is selected, then surround that text with the bracket pair.
4156 else if auto_surround
4157 && bracket_pair.surround
4158 && is_bracket_pair_start
4159 && bracket_pair.start.chars().count() == 1
4160 {
4161 edits.push((selection.start..selection.start, text.clone()));
4162 edits.push((
4163 selection.end..selection.end,
4164 bracket_pair.end.as_str().into(),
4165 ));
4166 bracket_inserted = true;
4167 new_selections.push((
4168 Selection {
4169 id: selection.id,
4170 start: snapshot.anchor_after(selection.start),
4171 end: snapshot.anchor_before(selection.end),
4172 reversed: selection.reversed,
4173 goal: selection.goal,
4174 },
4175 0,
4176 ));
4177 continue;
4178 }
4179 }
4180 }
4181
4182 if self.auto_replace_emoji_shortcode
4183 && selection.is_empty()
4184 && text.as_ref().ends_with(':')
4185 && let Some(possible_emoji_short_code) =
4186 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4187 && !possible_emoji_short_code.is_empty()
4188 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4189 {
4190 let emoji_shortcode_start = Point::new(
4191 selection.start.row,
4192 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4193 );
4194
4195 // Remove shortcode from buffer
4196 edits.push((
4197 emoji_shortcode_start..selection.start,
4198 "".to_string().into(),
4199 ));
4200 new_selections.push((
4201 Selection {
4202 id: selection.id,
4203 start: snapshot.anchor_after(emoji_shortcode_start),
4204 end: snapshot.anchor_before(selection.start),
4205 reversed: selection.reversed,
4206 goal: selection.goal,
4207 },
4208 0,
4209 ));
4210
4211 // Insert emoji
4212 let selection_start_anchor = snapshot.anchor_after(selection.start);
4213 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4214 edits.push((selection.start..selection.end, emoji.to_string().into()));
4215
4216 continue;
4217 }
4218
4219 // If not handling any auto-close operation, then just replace the selected
4220 // text with the given input and move the selection to the end of the
4221 // newly inserted text.
4222 let anchor = snapshot.anchor_after(selection.end);
4223 if !self.linked_edit_ranges.is_empty() {
4224 let start_anchor = snapshot.anchor_before(selection.start);
4225
4226 let is_word_char = text.chars().next().is_none_or(|char| {
4227 let classifier = snapshot
4228 .char_classifier_at(start_anchor.to_offset(&snapshot))
4229 .ignore_punctuation(true);
4230 classifier.is_word(char)
4231 });
4232
4233 if is_word_char {
4234 if let Some(ranges) = self
4235 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4236 {
4237 for (buffer, edits) in ranges {
4238 linked_edits
4239 .entry(buffer.clone())
4240 .or_default()
4241 .extend(edits.into_iter().map(|range| (range, text.clone())));
4242 }
4243 }
4244 } else {
4245 clear_linked_edit_ranges = true;
4246 }
4247 }
4248
4249 new_selections.push((selection.map(|_| anchor), 0));
4250 edits.push((selection.start..selection.end, text.clone()));
4251 }
4252
4253 drop(snapshot);
4254
4255 self.transact(window, cx, |this, window, cx| {
4256 if clear_linked_edit_ranges {
4257 this.linked_edit_ranges.clear();
4258 }
4259 let initial_buffer_versions =
4260 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4261
4262 this.buffer.update(cx, |buffer, cx| {
4263 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4264 });
4265 for (buffer, edits) in linked_edits {
4266 buffer.update(cx, |buffer, cx| {
4267 let snapshot = buffer.snapshot();
4268 let edits = edits
4269 .into_iter()
4270 .map(|(range, text)| {
4271 use text::ToPoint as TP;
4272 let end_point = TP::to_point(&range.end, &snapshot);
4273 let start_point = TP::to_point(&range.start, &snapshot);
4274 (start_point..end_point, text)
4275 })
4276 .sorted_by_key(|(range, _)| range.start);
4277 buffer.edit(edits, None, cx);
4278 })
4279 }
4280 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4281 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4282 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4283 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4284 .zip(new_selection_deltas)
4285 .map(|(selection, delta)| Selection {
4286 id: selection.id,
4287 start: selection.start + delta,
4288 end: selection.end + delta,
4289 reversed: selection.reversed,
4290 goal: SelectionGoal::None,
4291 })
4292 .collect::<Vec<_>>();
4293
4294 let mut i = 0;
4295 for (position, delta, selection_id, pair) in new_autoclose_regions {
4296 let position = position.to_offset(&map.buffer_snapshot) + delta;
4297 let start = map.buffer_snapshot.anchor_before(position);
4298 let end = map.buffer_snapshot.anchor_after(position);
4299 while let Some(existing_state) = this.autoclose_regions.get(i) {
4300 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4301 Ordering::Less => i += 1,
4302 Ordering::Greater => break,
4303 Ordering::Equal => {
4304 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4305 Ordering::Less => i += 1,
4306 Ordering::Equal => break,
4307 Ordering::Greater => break,
4308 }
4309 }
4310 }
4311 }
4312 this.autoclose_regions.insert(
4313 i,
4314 AutocloseRegion {
4315 selection_id,
4316 range: start..end,
4317 pair,
4318 },
4319 );
4320 }
4321
4322 let had_active_edit_prediction = this.has_active_edit_prediction();
4323 this.change_selections(
4324 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4325 window,
4326 cx,
4327 |s| s.select(new_selections),
4328 );
4329
4330 if !bracket_inserted
4331 && let Some(on_type_format_task) =
4332 this.trigger_on_type_formatting(text.to_string(), window, cx)
4333 {
4334 on_type_format_task.detach_and_log_err(cx);
4335 }
4336
4337 let editor_settings = EditorSettings::get_global(cx);
4338 if bracket_inserted
4339 && (editor_settings.auto_signature_help
4340 || editor_settings.show_signature_help_after_edits)
4341 {
4342 this.show_signature_help(&ShowSignatureHelp, window, cx);
4343 }
4344
4345 let trigger_in_words =
4346 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4347 if this.hard_wrap.is_some() {
4348 let latest: Range<Point> = this.selections.newest(cx).range();
4349 if latest.is_empty()
4350 && this
4351 .buffer()
4352 .read(cx)
4353 .snapshot(cx)
4354 .line_len(MultiBufferRow(latest.start.row))
4355 == latest.start.column
4356 {
4357 this.rewrap_impl(
4358 RewrapOptions {
4359 override_language_settings: true,
4360 preserve_existing_whitespace: true,
4361 },
4362 cx,
4363 )
4364 }
4365 }
4366 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4367 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4368 this.refresh_edit_prediction(true, false, window, cx);
4369 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4370 });
4371 }
4372
4373 fn find_possible_emoji_shortcode_at_position(
4374 snapshot: &MultiBufferSnapshot,
4375 position: Point,
4376 ) -> Option<String> {
4377 let mut chars = Vec::new();
4378 let mut found_colon = false;
4379 for char in snapshot.reversed_chars_at(position).take(100) {
4380 // Found a possible emoji shortcode in the middle of the buffer
4381 if found_colon {
4382 if char.is_whitespace() {
4383 chars.reverse();
4384 return Some(chars.iter().collect());
4385 }
4386 // If the previous character is not a whitespace, we are in the middle of a word
4387 // and we only want to complete the shortcode if the word is made up of other emojis
4388 let mut containing_word = String::new();
4389 for ch in snapshot
4390 .reversed_chars_at(position)
4391 .skip(chars.len() + 1)
4392 .take(100)
4393 {
4394 if ch.is_whitespace() {
4395 break;
4396 }
4397 containing_word.push(ch);
4398 }
4399 let containing_word = containing_word.chars().rev().collect::<String>();
4400 if util::word_consists_of_emojis(containing_word.as_str()) {
4401 chars.reverse();
4402 return Some(chars.iter().collect());
4403 }
4404 }
4405
4406 if char.is_whitespace() || !char.is_ascii() {
4407 return None;
4408 }
4409 if char == ':' {
4410 found_colon = true;
4411 } else {
4412 chars.push(char);
4413 }
4414 }
4415 // Found a possible emoji shortcode at the beginning of the buffer
4416 chars.reverse();
4417 Some(chars.iter().collect())
4418 }
4419
4420 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4422 self.transact(window, cx, |this, window, cx| {
4423 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4424 let selections = this.selections.all::<usize>(cx);
4425 let multi_buffer = this.buffer.read(cx);
4426 let buffer = multi_buffer.snapshot(cx);
4427 selections
4428 .iter()
4429 .map(|selection| {
4430 let start_point = selection.start.to_point(&buffer);
4431 let mut existing_indent =
4432 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4433 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4434 let start = selection.start;
4435 let end = selection.end;
4436 let selection_is_empty = start == end;
4437 let language_scope = buffer.language_scope_at(start);
4438 let (
4439 comment_delimiter,
4440 doc_delimiter,
4441 insert_extra_newline,
4442 indent_on_newline,
4443 indent_on_extra_newline,
4444 ) = if let Some(language) = &language_scope {
4445 let mut insert_extra_newline =
4446 insert_extra_newline_brackets(&buffer, start..end, language)
4447 || insert_extra_newline_tree_sitter(&buffer, start..end);
4448
4449 // Comment extension on newline is allowed only for cursor selections
4450 let comment_delimiter = maybe!({
4451 if !selection_is_empty {
4452 return None;
4453 }
4454
4455 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4456 return None;
4457 }
4458
4459 let delimiters = language.line_comment_prefixes();
4460 let max_len_of_delimiter =
4461 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4462 let (snapshot, range) =
4463 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4464
4465 let num_of_whitespaces = snapshot
4466 .chars_for_range(range.clone())
4467 .take_while(|c| c.is_whitespace())
4468 .count();
4469 let comment_candidate = snapshot
4470 .chars_for_range(range.clone())
4471 .skip(num_of_whitespaces)
4472 .take(max_len_of_delimiter)
4473 .collect::<String>();
4474 let (delimiter, trimmed_len) = delimiters
4475 .iter()
4476 .filter_map(|delimiter| {
4477 let prefix = delimiter.trim_end();
4478 if comment_candidate.starts_with(prefix) {
4479 Some((delimiter, prefix.len()))
4480 } else {
4481 None
4482 }
4483 })
4484 .max_by_key(|(_, len)| *len)?;
4485
4486 if let Some(BlockCommentConfig {
4487 start: block_start, ..
4488 }) = language.block_comment()
4489 {
4490 let block_start_trimmed = block_start.trim_end();
4491 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4492 let line_content = snapshot
4493 .chars_for_range(range)
4494 .skip(num_of_whitespaces)
4495 .take(block_start_trimmed.len())
4496 .collect::<String>();
4497
4498 if line_content.starts_with(block_start_trimmed) {
4499 return None;
4500 }
4501 }
4502 }
4503
4504 let cursor_is_placed_after_comment_marker =
4505 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4506 if cursor_is_placed_after_comment_marker {
4507 Some(delimiter.clone())
4508 } else {
4509 None
4510 }
4511 });
4512
4513 let mut indent_on_newline = IndentSize::spaces(0);
4514 let mut indent_on_extra_newline = IndentSize::spaces(0);
4515
4516 let doc_delimiter = maybe!({
4517 if !selection_is_empty {
4518 return None;
4519 }
4520
4521 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4522 return None;
4523 }
4524
4525 let BlockCommentConfig {
4526 start: start_tag,
4527 end: end_tag,
4528 prefix: delimiter,
4529 tab_size: len,
4530 } = language.documentation_comment()?;
4531 let is_within_block_comment = buffer
4532 .language_scope_at(start_point)
4533 .is_some_and(|scope| scope.override_name() == Some("comment"));
4534 if !is_within_block_comment {
4535 return None;
4536 }
4537
4538 let (snapshot, range) =
4539 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4540
4541 let num_of_whitespaces = snapshot
4542 .chars_for_range(range.clone())
4543 .take_while(|c| c.is_whitespace())
4544 .count();
4545
4546 // 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.
4547 let column = start_point.column;
4548 let cursor_is_after_start_tag = {
4549 let start_tag_len = start_tag.len();
4550 let start_tag_line = snapshot
4551 .chars_for_range(range.clone())
4552 .skip(num_of_whitespaces)
4553 .take(start_tag_len)
4554 .collect::<String>();
4555 if start_tag_line.starts_with(start_tag.as_ref()) {
4556 num_of_whitespaces + start_tag_len <= column as usize
4557 } else {
4558 false
4559 }
4560 };
4561
4562 let cursor_is_after_delimiter = {
4563 let delimiter_trim = delimiter.trim_end();
4564 let delimiter_line = snapshot
4565 .chars_for_range(range.clone())
4566 .skip(num_of_whitespaces)
4567 .take(delimiter_trim.len())
4568 .collect::<String>();
4569 if delimiter_line.starts_with(delimiter_trim) {
4570 num_of_whitespaces + delimiter_trim.len() <= column as usize
4571 } else {
4572 false
4573 }
4574 };
4575
4576 let cursor_is_before_end_tag_if_exists = {
4577 let mut char_position = 0u32;
4578 let mut end_tag_offset = None;
4579
4580 'outer: for chunk in snapshot.text_for_range(range) {
4581 if let Some(byte_pos) = chunk.find(&**end_tag) {
4582 let chars_before_match =
4583 chunk[..byte_pos].chars().count() as u32;
4584 end_tag_offset =
4585 Some(char_position + chars_before_match);
4586 break 'outer;
4587 }
4588 char_position += chunk.chars().count() as u32;
4589 }
4590
4591 if let Some(end_tag_offset) = end_tag_offset {
4592 let cursor_is_before_end_tag = column <= end_tag_offset;
4593 if cursor_is_after_start_tag {
4594 if cursor_is_before_end_tag {
4595 insert_extra_newline = true;
4596 }
4597 let cursor_is_at_start_of_end_tag =
4598 column == end_tag_offset;
4599 if cursor_is_at_start_of_end_tag {
4600 indent_on_extra_newline.len = *len;
4601 }
4602 }
4603 cursor_is_before_end_tag
4604 } else {
4605 true
4606 }
4607 };
4608
4609 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4610 && cursor_is_before_end_tag_if_exists
4611 {
4612 if cursor_is_after_start_tag {
4613 indent_on_newline.len = *len;
4614 }
4615 Some(delimiter.clone())
4616 } else {
4617 None
4618 }
4619 });
4620
4621 (
4622 comment_delimiter,
4623 doc_delimiter,
4624 insert_extra_newline,
4625 indent_on_newline,
4626 indent_on_extra_newline,
4627 )
4628 } else {
4629 (
4630 None,
4631 None,
4632 false,
4633 IndentSize::default(),
4634 IndentSize::default(),
4635 )
4636 };
4637
4638 let prevent_auto_indent = doc_delimiter.is_some();
4639 let delimiter = comment_delimiter.or(doc_delimiter);
4640
4641 let capacity_for_delimiter =
4642 delimiter.as_deref().map(str::len).unwrap_or_default();
4643 let mut new_text = String::with_capacity(
4644 1 + capacity_for_delimiter
4645 + existing_indent.len as usize
4646 + indent_on_newline.len as usize
4647 + indent_on_extra_newline.len as usize,
4648 );
4649 new_text.push('\n');
4650 new_text.extend(existing_indent.chars());
4651 new_text.extend(indent_on_newline.chars());
4652
4653 if let Some(delimiter) = &delimiter {
4654 new_text.push_str(delimiter);
4655 }
4656
4657 if insert_extra_newline {
4658 new_text.push('\n');
4659 new_text.extend(existing_indent.chars());
4660 new_text.extend(indent_on_extra_newline.chars());
4661 }
4662
4663 let anchor = buffer.anchor_after(end);
4664 let new_selection = selection.map(|_| anchor);
4665 (
4666 ((start..end, new_text), prevent_auto_indent),
4667 (insert_extra_newline, new_selection),
4668 )
4669 })
4670 .unzip()
4671 };
4672
4673 let mut auto_indent_edits = Vec::new();
4674 let mut edits = Vec::new();
4675 for (edit, prevent_auto_indent) in edits_with_flags {
4676 if prevent_auto_indent {
4677 edits.push(edit);
4678 } else {
4679 auto_indent_edits.push(edit);
4680 }
4681 }
4682 if !edits.is_empty() {
4683 this.edit(edits, cx);
4684 }
4685 if !auto_indent_edits.is_empty() {
4686 this.edit_with_autoindent(auto_indent_edits, cx);
4687 }
4688
4689 let buffer = this.buffer.read(cx).snapshot(cx);
4690 let new_selections = selection_info
4691 .into_iter()
4692 .map(|(extra_newline_inserted, new_selection)| {
4693 let mut cursor = new_selection.end.to_point(&buffer);
4694 if extra_newline_inserted {
4695 cursor.row -= 1;
4696 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4697 }
4698 new_selection.map(|_| cursor)
4699 })
4700 .collect();
4701
4702 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4703 this.refresh_edit_prediction(true, false, window, cx);
4704 });
4705 }
4706
4707 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4709
4710 let buffer = self.buffer.read(cx);
4711 let snapshot = buffer.snapshot(cx);
4712
4713 let mut edits = Vec::new();
4714 let mut rows = Vec::new();
4715
4716 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4717 let cursor = selection.head();
4718 let row = cursor.row;
4719
4720 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4721
4722 let newline = "\n".to_string();
4723 edits.push((start_of_line..start_of_line, newline));
4724
4725 rows.push(row + rows_inserted as u32);
4726 }
4727
4728 self.transact(window, cx, |editor, window, cx| {
4729 editor.edit(edits, cx);
4730
4731 editor.change_selections(Default::default(), window, cx, |s| {
4732 let mut index = 0;
4733 s.move_cursors_with(|map, _, _| {
4734 let row = rows[index];
4735 index += 1;
4736
4737 let point = Point::new(row, 0);
4738 let boundary = map.next_line_boundary(point).1;
4739 let clipped = map.clip_point(boundary, Bias::Left);
4740
4741 (clipped, SelectionGoal::None)
4742 });
4743 });
4744
4745 let mut indent_edits = Vec::new();
4746 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4747 for row in rows {
4748 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4749 for (row, indent) in indents {
4750 if indent.len == 0 {
4751 continue;
4752 }
4753
4754 let text = match indent.kind {
4755 IndentKind::Space => " ".repeat(indent.len as usize),
4756 IndentKind::Tab => "\t".repeat(indent.len as usize),
4757 };
4758 let point = Point::new(row.0, 0);
4759 indent_edits.push((point..point, text));
4760 }
4761 }
4762 editor.edit(indent_edits, cx);
4763 });
4764 }
4765
4766 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4768
4769 let buffer = self.buffer.read(cx);
4770 let snapshot = buffer.snapshot(cx);
4771
4772 let mut edits = Vec::new();
4773 let mut rows = Vec::new();
4774 let mut rows_inserted = 0;
4775
4776 for selection in self.selections.all_adjusted(cx) {
4777 let cursor = selection.head();
4778 let row = cursor.row;
4779
4780 let point = Point::new(row + 1, 0);
4781 let start_of_line = snapshot.clip_point(point, Bias::Left);
4782
4783 let newline = "\n".to_string();
4784 edits.push((start_of_line..start_of_line, newline));
4785
4786 rows_inserted += 1;
4787 rows.push(row + rows_inserted);
4788 }
4789
4790 self.transact(window, cx, |editor, window, cx| {
4791 editor.edit(edits, cx);
4792
4793 editor.change_selections(Default::default(), window, cx, |s| {
4794 let mut index = 0;
4795 s.move_cursors_with(|map, _, _| {
4796 let row = rows[index];
4797 index += 1;
4798
4799 let point = Point::new(row, 0);
4800 let boundary = map.next_line_boundary(point).1;
4801 let clipped = map.clip_point(boundary, Bias::Left);
4802
4803 (clipped, SelectionGoal::None)
4804 });
4805 });
4806
4807 let mut indent_edits = Vec::new();
4808 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4809 for row in rows {
4810 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4811 for (row, indent) in indents {
4812 if indent.len == 0 {
4813 continue;
4814 }
4815
4816 let text = match indent.kind {
4817 IndentKind::Space => " ".repeat(indent.len as usize),
4818 IndentKind::Tab => "\t".repeat(indent.len as usize),
4819 };
4820 let point = Point::new(row.0, 0);
4821 indent_edits.push((point..point, text));
4822 }
4823 }
4824 editor.edit(indent_edits, cx);
4825 });
4826 }
4827
4828 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4829 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4830 original_indent_columns: Vec::new(),
4831 });
4832 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4833 }
4834
4835 fn insert_with_autoindent_mode(
4836 &mut self,
4837 text: &str,
4838 autoindent_mode: Option<AutoindentMode>,
4839 window: &mut Window,
4840 cx: &mut Context<Self>,
4841 ) {
4842 if self.read_only(cx) {
4843 return;
4844 }
4845
4846 let text: Arc<str> = text.into();
4847 self.transact(window, cx, |this, window, cx| {
4848 let old_selections = this.selections.all_adjusted(cx);
4849 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4850 let anchors = {
4851 let snapshot = buffer.read(cx);
4852 old_selections
4853 .iter()
4854 .map(|s| {
4855 let anchor = snapshot.anchor_after(s.head());
4856 s.map(|_| anchor)
4857 })
4858 .collect::<Vec<_>>()
4859 };
4860 buffer.edit(
4861 old_selections
4862 .iter()
4863 .map(|s| (s.start..s.end, text.clone())),
4864 autoindent_mode,
4865 cx,
4866 );
4867 anchors
4868 });
4869
4870 this.change_selections(Default::default(), window, cx, |s| {
4871 s.select_anchors(selection_anchors);
4872 });
4873
4874 cx.notify();
4875 });
4876 }
4877
4878 fn trigger_completion_on_input(
4879 &mut self,
4880 text: &str,
4881 trigger_in_words: bool,
4882 window: &mut Window,
4883 cx: &mut Context<Self>,
4884 ) {
4885 let completions_source = self
4886 .context_menu
4887 .borrow()
4888 .as_ref()
4889 .and_then(|menu| match menu {
4890 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4891 CodeContextMenu::CodeActions(_) => None,
4892 });
4893
4894 match completions_source {
4895 Some(CompletionsMenuSource::Words) => {
4896 self.show_word_completions(&ShowWordCompletions, window, cx)
4897 }
4898 Some(CompletionsMenuSource::Normal)
4899 | Some(CompletionsMenuSource::SnippetChoices)
4900 | None
4901 if self.is_completion_trigger(
4902 text,
4903 trigger_in_words,
4904 completions_source.is_some(),
4905 cx,
4906 ) =>
4907 {
4908 self.show_completions(
4909 &ShowCompletions {
4910 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4911 },
4912 window,
4913 cx,
4914 )
4915 }
4916 _ => {
4917 self.hide_context_menu(window, cx);
4918 }
4919 }
4920 }
4921
4922 fn is_completion_trigger(
4923 &self,
4924 text: &str,
4925 trigger_in_words: bool,
4926 menu_is_open: bool,
4927 cx: &mut Context<Self>,
4928 ) -> bool {
4929 let position = self.selections.newest_anchor().head();
4930 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4931 return false;
4932 };
4933
4934 if let Some(completion_provider) = &self.completion_provider {
4935 completion_provider.is_completion_trigger(
4936 &buffer,
4937 position.text_anchor,
4938 text,
4939 trigger_in_words,
4940 menu_is_open,
4941 cx,
4942 )
4943 } else {
4944 false
4945 }
4946 }
4947
4948 /// If any empty selections is touching the start of its innermost containing autoclose
4949 /// region, expand it to select the brackets.
4950 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4951 let selections = self.selections.all::<usize>(cx);
4952 let buffer = self.buffer.read(cx).read(cx);
4953 let new_selections = self
4954 .selections_with_autoclose_regions(selections, &buffer)
4955 .map(|(mut selection, region)| {
4956 if !selection.is_empty() {
4957 return selection;
4958 }
4959
4960 if let Some(region) = region {
4961 let mut range = region.range.to_offset(&buffer);
4962 if selection.start == range.start && range.start >= region.pair.start.len() {
4963 range.start -= region.pair.start.len();
4964 if buffer.contains_str_at(range.start, ®ion.pair.start)
4965 && buffer.contains_str_at(range.end, ®ion.pair.end)
4966 {
4967 range.end += region.pair.end.len();
4968 selection.start = range.start;
4969 selection.end = range.end;
4970
4971 return selection;
4972 }
4973 }
4974 }
4975
4976 let always_treat_brackets_as_autoclosed = buffer
4977 .language_settings_at(selection.start, cx)
4978 .always_treat_brackets_as_autoclosed;
4979
4980 if !always_treat_brackets_as_autoclosed {
4981 return selection;
4982 }
4983
4984 if let Some(scope) = buffer.language_scope_at(selection.start) {
4985 for (pair, enabled) in scope.brackets() {
4986 if !enabled || !pair.close {
4987 continue;
4988 }
4989
4990 if buffer.contains_str_at(selection.start, &pair.end) {
4991 let pair_start_len = pair.start.len();
4992 if buffer.contains_str_at(
4993 selection.start.saturating_sub(pair_start_len),
4994 &pair.start,
4995 ) {
4996 selection.start -= pair_start_len;
4997 selection.end += pair.end.len();
4998
4999 return selection;
5000 }
5001 }
5002 }
5003 }
5004
5005 selection
5006 })
5007 .collect();
5008
5009 drop(buffer);
5010 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5011 selections.select(new_selections)
5012 });
5013 }
5014
5015 /// Iterate the given selections, and for each one, find the smallest surrounding
5016 /// autoclose region. This uses the ordering of the selections and the autoclose
5017 /// regions to avoid repeated comparisons.
5018 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5019 &'a self,
5020 selections: impl IntoIterator<Item = Selection<D>>,
5021 buffer: &'a MultiBufferSnapshot,
5022 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5023 let mut i = 0;
5024 let mut regions = self.autoclose_regions.as_slice();
5025 selections.into_iter().map(move |selection| {
5026 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5027
5028 let mut enclosing = None;
5029 while let Some(pair_state) = regions.get(i) {
5030 if pair_state.range.end.to_offset(buffer) < range.start {
5031 regions = ®ions[i + 1..];
5032 i = 0;
5033 } else if pair_state.range.start.to_offset(buffer) > range.end {
5034 break;
5035 } else {
5036 if pair_state.selection_id == selection.id {
5037 enclosing = Some(pair_state);
5038 }
5039 i += 1;
5040 }
5041 }
5042
5043 (selection, enclosing)
5044 })
5045 }
5046
5047 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5048 fn invalidate_autoclose_regions(
5049 &mut self,
5050 mut selections: &[Selection<Anchor>],
5051 buffer: &MultiBufferSnapshot,
5052 ) {
5053 self.autoclose_regions.retain(|state| {
5054 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5055 return false;
5056 }
5057
5058 let mut i = 0;
5059 while let Some(selection) = selections.get(i) {
5060 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5061 selections = &selections[1..];
5062 continue;
5063 }
5064 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5065 break;
5066 }
5067 if selection.id == state.selection_id {
5068 return true;
5069 } else {
5070 i += 1;
5071 }
5072 }
5073 false
5074 });
5075 }
5076
5077 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5078 let offset = position.to_offset(buffer);
5079 let (word_range, kind) = buffer.surrounding_word(offset, true);
5080 if offset > word_range.start && kind == Some(CharKind::Word) {
5081 Some(
5082 buffer
5083 .text_for_range(word_range.start..offset)
5084 .collect::<String>(),
5085 )
5086 } else {
5087 None
5088 }
5089 }
5090
5091 pub fn toggle_inline_values(
5092 &mut self,
5093 _: &ToggleInlineValues,
5094 _: &mut Window,
5095 cx: &mut Context<Self>,
5096 ) {
5097 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5098
5099 self.refresh_inline_values(cx);
5100 }
5101
5102 pub fn toggle_inlay_hints(
5103 &mut self,
5104 _: &ToggleInlayHints,
5105 _: &mut Window,
5106 cx: &mut Context<Self>,
5107 ) {
5108 self.refresh_inlay_hints(
5109 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5110 cx,
5111 );
5112 }
5113
5114 pub fn inlay_hints_enabled(&self) -> bool {
5115 self.inlay_hint_cache.enabled
5116 }
5117
5118 pub fn inline_values_enabled(&self) -> bool {
5119 self.inline_value_cache.enabled
5120 }
5121
5122 #[cfg(any(test, feature = "test-support"))]
5123 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5124 self.display_map
5125 .read(cx)
5126 .current_inlays()
5127 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5128 .cloned()
5129 .collect()
5130 }
5131
5132 #[cfg(any(test, feature = "test-support"))]
5133 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5134 self.display_map
5135 .read(cx)
5136 .current_inlays()
5137 .cloned()
5138 .collect()
5139 }
5140
5141 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5142 if self.semantics_provider.is_none() || !self.mode.is_full() {
5143 return;
5144 }
5145
5146 let reason_description = reason.description();
5147 let ignore_debounce = matches!(
5148 reason,
5149 InlayHintRefreshReason::SettingsChange(_)
5150 | InlayHintRefreshReason::Toggle(_)
5151 | InlayHintRefreshReason::ExcerptsRemoved(_)
5152 | InlayHintRefreshReason::ModifiersChanged(_)
5153 );
5154 let (invalidate_cache, required_languages) = match reason {
5155 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5156 match self.inlay_hint_cache.modifiers_override(enabled) {
5157 Some(enabled) => {
5158 if enabled {
5159 (InvalidationStrategy::RefreshRequested, None)
5160 } else {
5161 self.splice_inlays(
5162 &self
5163 .visible_inlay_hints(cx)
5164 .iter()
5165 .map(|inlay| inlay.id)
5166 .collect::<Vec<InlayId>>(),
5167 Vec::new(),
5168 cx,
5169 );
5170 return;
5171 }
5172 }
5173 None => return,
5174 }
5175 }
5176 InlayHintRefreshReason::Toggle(enabled) => {
5177 if self.inlay_hint_cache.toggle(enabled) {
5178 if enabled {
5179 (InvalidationStrategy::RefreshRequested, None)
5180 } else {
5181 self.splice_inlays(
5182 &self
5183 .visible_inlay_hints(cx)
5184 .iter()
5185 .map(|inlay| inlay.id)
5186 .collect::<Vec<InlayId>>(),
5187 Vec::new(),
5188 cx,
5189 );
5190 return;
5191 }
5192 } else {
5193 return;
5194 }
5195 }
5196 InlayHintRefreshReason::SettingsChange(new_settings) => {
5197 match self.inlay_hint_cache.update_settings(
5198 &self.buffer,
5199 new_settings,
5200 self.visible_inlay_hints(cx),
5201 cx,
5202 ) {
5203 ControlFlow::Break(Some(InlaySplice {
5204 to_remove,
5205 to_insert,
5206 })) => {
5207 self.splice_inlays(&to_remove, to_insert, cx);
5208 return;
5209 }
5210 ControlFlow::Break(None) => return,
5211 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5212 }
5213 }
5214 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5215 if let Some(InlaySplice {
5216 to_remove,
5217 to_insert,
5218 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5219 {
5220 self.splice_inlays(&to_remove, to_insert, cx);
5221 }
5222 self.display_map.update(cx, |display_map, _| {
5223 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5224 });
5225 return;
5226 }
5227 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5228 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5229 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5230 }
5231 InlayHintRefreshReason::RefreshRequested => {
5232 (InvalidationStrategy::RefreshRequested, None)
5233 }
5234 };
5235
5236 if let Some(InlaySplice {
5237 to_remove,
5238 to_insert,
5239 }) = self.inlay_hint_cache.spawn_hint_refresh(
5240 reason_description,
5241 self.visible_excerpts(required_languages.as_ref(), cx),
5242 invalidate_cache,
5243 ignore_debounce,
5244 cx,
5245 ) {
5246 self.splice_inlays(&to_remove, to_insert, cx);
5247 }
5248 }
5249
5250 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5251 self.display_map
5252 .read(cx)
5253 .current_inlays()
5254 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5255 .cloned()
5256 .collect()
5257 }
5258
5259 pub fn visible_excerpts(
5260 &self,
5261 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5262 cx: &mut Context<Editor>,
5263 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5264 let Some(project) = self.project() else {
5265 return HashMap::default();
5266 };
5267 let project = project.read(cx);
5268 let multi_buffer = self.buffer().read(cx);
5269 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5270 let multi_buffer_visible_start = self
5271 .scroll_manager
5272 .anchor()
5273 .anchor
5274 .to_point(&multi_buffer_snapshot);
5275 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5276 multi_buffer_visible_start
5277 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5278 Bias::Left,
5279 );
5280 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5281 multi_buffer_snapshot
5282 .range_to_buffer_ranges(multi_buffer_visible_range)
5283 .into_iter()
5284 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5285 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5286 let buffer_file = project::File::from_dyn(buffer.file())?;
5287 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5288 let worktree_entry = buffer_worktree
5289 .read(cx)
5290 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5291 if worktree_entry.is_ignored {
5292 return None;
5293 }
5294
5295 let language = buffer.language()?;
5296 if let Some(restrict_to_languages) = restrict_to_languages
5297 && !restrict_to_languages.contains(language)
5298 {
5299 return None;
5300 }
5301 Some((
5302 excerpt_id,
5303 (
5304 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5305 buffer.version().clone(),
5306 excerpt_visible_range,
5307 ),
5308 ))
5309 })
5310 .collect()
5311 }
5312
5313 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5314 TextLayoutDetails {
5315 text_system: window.text_system().clone(),
5316 editor_style: self.style.clone().unwrap(),
5317 rem_size: window.rem_size(),
5318 scroll_anchor: self.scroll_manager.anchor(),
5319 visible_rows: self.visible_line_count(),
5320 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5321 }
5322 }
5323
5324 pub fn splice_inlays(
5325 &self,
5326 to_remove: &[InlayId],
5327 to_insert: Vec<Inlay>,
5328 cx: &mut Context<Self>,
5329 ) {
5330 self.display_map.update(cx, |display_map, cx| {
5331 display_map.splice_inlays(to_remove, to_insert, cx)
5332 });
5333 cx.notify();
5334 }
5335
5336 fn trigger_on_type_formatting(
5337 &self,
5338 input: String,
5339 window: &mut Window,
5340 cx: &mut Context<Self>,
5341 ) -> Option<Task<Result<()>>> {
5342 if input.len() != 1 {
5343 return None;
5344 }
5345
5346 let project = self.project()?;
5347 let position = self.selections.newest_anchor().head();
5348 let (buffer, buffer_position) = self
5349 .buffer
5350 .read(cx)
5351 .text_anchor_for_position(position, cx)?;
5352
5353 let settings = language_settings::language_settings(
5354 buffer
5355 .read(cx)
5356 .language_at(buffer_position)
5357 .map(|l| l.name()),
5358 buffer.read(cx).file(),
5359 cx,
5360 );
5361 if !settings.use_on_type_format {
5362 return None;
5363 }
5364
5365 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5366 // hence we do LSP request & edit on host side only — add formats to host's history.
5367 let push_to_lsp_host_history = true;
5368 // If this is not the host, append its history with new edits.
5369 let push_to_client_history = project.read(cx).is_via_collab();
5370
5371 let on_type_formatting = project.update(cx, |project, cx| {
5372 project.on_type_format(
5373 buffer.clone(),
5374 buffer_position,
5375 input,
5376 push_to_lsp_host_history,
5377 cx,
5378 )
5379 });
5380 Some(cx.spawn_in(window, async move |editor, cx| {
5381 if let Some(transaction) = on_type_formatting.await? {
5382 if push_to_client_history {
5383 buffer
5384 .update(cx, |buffer, _| {
5385 buffer.push_transaction(transaction, Instant::now());
5386 buffer.finalize_last_transaction();
5387 })
5388 .ok();
5389 }
5390 editor.update(cx, |editor, cx| {
5391 editor.refresh_document_highlights(cx);
5392 })?;
5393 }
5394 Ok(())
5395 }))
5396 }
5397
5398 pub fn show_word_completions(
5399 &mut self,
5400 _: &ShowWordCompletions,
5401 window: &mut Window,
5402 cx: &mut Context<Self>,
5403 ) {
5404 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5405 }
5406
5407 pub fn show_completions(
5408 &mut self,
5409 options: &ShowCompletions,
5410 window: &mut Window,
5411 cx: &mut Context<Self>,
5412 ) {
5413 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5414 }
5415
5416 fn open_or_update_completions_menu(
5417 &mut self,
5418 requested_source: Option<CompletionsMenuSource>,
5419 trigger: Option<&str>,
5420 window: &mut Window,
5421 cx: &mut Context<Self>,
5422 ) {
5423 if self.pending_rename.is_some() {
5424 return;
5425 }
5426
5427 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5428
5429 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5430 // inserted and selected. To handle that case, the start of the selection is used so that
5431 // the menu starts with all choices.
5432 let position = self
5433 .selections
5434 .newest_anchor()
5435 .start
5436 .bias_right(&multibuffer_snapshot);
5437 if position.diff_base_anchor.is_some() {
5438 return;
5439 }
5440 let (buffer, buffer_position) =
5441 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5442 output
5443 } else {
5444 return;
5445 };
5446 let buffer_snapshot = buffer.read(cx).snapshot();
5447
5448 let query: Option<Arc<String>> =
5449 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5450
5451 drop(multibuffer_snapshot);
5452
5453 let provider = match requested_source {
5454 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5455 Some(CompletionsMenuSource::Words) => None,
5456 Some(CompletionsMenuSource::SnippetChoices) => {
5457 log::error!("bug: SnippetChoices requested_source is not handled");
5458 None
5459 }
5460 };
5461
5462 let sort_completions = provider
5463 .as_ref()
5464 .is_some_and(|provider| provider.sort_completions());
5465
5466 let filter_completions = provider
5467 .as_ref()
5468 .is_none_or(|provider| provider.filter_completions());
5469
5470 let trigger_kind = match trigger {
5471 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5472 CompletionTriggerKind::TRIGGER_CHARACTER
5473 }
5474 _ => CompletionTriggerKind::INVOKED,
5475 };
5476 let completion_context = CompletionContext {
5477 trigger_character: trigger.and_then(|trigger| {
5478 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5479 Some(String::from(trigger))
5480 } else {
5481 None
5482 }
5483 }),
5484 trigger_kind,
5485 };
5486
5487 // Hide the current completions menu when a trigger char is typed. Without this, cached
5488 // completions from before the trigger char may be reused (#32774). Snippet choices could
5489 // involve trigger chars, so this is skipped in that case.
5490 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5491 {
5492 let menu_is_open = matches!(
5493 self.context_menu.borrow().as_ref(),
5494 Some(CodeContextMenu::Completions(_))
5495 );
5496 if menu_is_open {
5497 self.hide_context_menu(window, cx);
5498 }
5499 }
5500
5501 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5502 if filter_completions {
5503 menu.filter(query.clone(), provider.clone(), window, cx);
5504 }
5505 // When `is_incomplete` is false, no need to re-query completions when the current query
5506 // is a suffix of the initial query.
5507 if !menu.is_incomplete {
5508 // If the new query is a suffix of the old query (typing more characters) and
5509 // the previous result was complete, the existing completions can be filtered.
5510 //
5511 // Note that this is always true for snippet completions.
5512 let query_matches = match (&menu.initial_query, &query) {
5513 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5514 (None, _) => true,
5515 _ => false,
5516 };
5517 if query_matches {
5518 let position_matches = if menu.initial_position == position {
5519 true
5520 } else {
5521 let snapshot = self.buffer.read(cx).read(cx);
5522 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5523 };
5524 if position_matches {
5525 return;
5526 }
5527 }
5528 }
5529 };
5530
5531 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5532 buffer_snapshot.surrounding_word(buffer_position, false)
5533 {
5534 let word_to_exclude = buffer_snapshot
5535 .text_for_range(word_range.clone())
5536 .collect::<String>();
5537 (
5538 buffer_snapshot.anchor_before(word_range.start)
5539 ..buffer_snapshot.anchor_after(buffer_position),
5540 Some(word_to_exclude),
5541 )
5542 } else {
5543 (buffer_position..buffer_position, None)
5544 };
5545
5546 let language = buffer_snapshot
5547 .language_at(buffer_position)
5548 .map(|language| language.name());
5549
5550 let completion_settings =
5551 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5552
5553 let show_completion_documentation = buffer_snapshot
5554 .settings_at(buffer_position, cx)
5555 .show_completion_documentation;
5556
5557 // The document can be large, so stay in reasonable bounds when searching for words,
5558 // otherwise completion pop-up might be slow to appear.
5559 const WORD_LOOKUP_ROWS: u32 = 5_000;
5560 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5561 let min_word_search = buffer_snapshot.clip_point(
5562 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5563 Bias::Left,
5564 );
5565 let max_word_search = buffer_snapshot.clip_point(
5566 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5567 Bias::Right,
5568 );
5569 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5570 ..buffer_snapshot.point_to_offset(max_word_search);
5571
5572 let skip_digits = query
5573 .as_ref()
5574 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5575
5576 let omit_word_completions = match &query {
5577 Some(query) => query.chars().count() < completion_settings.words_min_length,
5578 None => completion_settings.words_min_length != 0,
5579 };
5580
5581 let (mut words, provider_responses) = match &provider {
5582 Some(provider) => {
5583 let provider_responses = provider.completions(
5584 position.excerpt_id,
5585 &buffer,
5586 buffer_position,
5587 completion_context,
5588 window,
5589 cx,
5590 );
5591
5592 let words = match (omit_word_completions, completion_settings.words) {
5593 (true, _) | (_, WordsCompletionMode::Disabled) => {
5594 Task::ready(BTreeMap::default())
5595 }
5596 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5597 .background_spawn(async move {
5598 buffer_snapshot.words_in_range(WordsQuery {
5599 fuzzy_contents: None,
5600 range: word_search_range,
5601 skip_digits,
5602 })
5603 }),
5604 };
5605
5606 (words, provider_responses)
5607 }
5608 None => {
5609 let words = if omit_word_completions {
5610 Task::ready(BTreeMap::default())
5611 } else {
5612 cx.background_spawn(async move {
5613 buffer_snapshot.words_in_range(WordsQuery {
5614 fuzzy_contents: None,
5615 range: word_search_range,
5616 skip_digits,
5617 })
5618 })
5619 };
5620 (words, Task::ready(Ok(Vec::new())))
5621 }
5622 };
5623
5624 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5625
5626 let id = post_inc(&mut self.next_completion_id);
5627 let task = cx.spawn_in(window, async move |editor, cx| {
5628 let Ok(()) = editor.update(cx, |this, _| {
5629 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5630 }) else {
5631 return;
5632 };
5633
5634 // TODO: Ideally completions from different sources would be selectively re-queried, so
5635 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5636 let mut completions = Vec::new();
5637 let mut is_incomplete = false;
5638 let mut display_options: Option<CompletionDisplayOptions> = None;
5639 if let Some(provider_responses) = provider_responses.await.log_err()
5640 && !provider_responses.is_empty()
5641 {
5642 for response in provider_responses {
5643 completions.extend(response.completions);
5644 is_incomplete = is_incomplete || response.is_incomplete;
5645 match display_options.as_mut() {
5646 None => {
5647 display_options = Some(response.display_options);
5648 }
5649 Some(options) => options.merge(&response.display_options),
5650 }
5651 }
5652 if completion_settings.words == WordsCompletionMode::Fallback {
5653 words = Task::ready(BTreeMap::default());
5654 }
5655 }
5656 let display_options = display_options.unwrap_or_default();
5657
5658 let mut words = words.await;
5659 if let Some(word_to_exclude) = &word_to_exclude {
5660 words.remove(word_to_exclude);
5661 }
5662 for lsp_completion in &completions {
5663 words.remove(&lsp_completion.new_text);
5664 }
5665 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5666 replace_range: word_replace_range.clone(),
5667 new_text: word.clone(),
5668 label: CodeLabel::plain(word, None),
5669 icon_path: None,
5670 documentation: None,
5671 source: CompletionSource::BufferWord {
5672 word_range,
5673 resolved: false,
5674 },
5675 insert_text_mode: Some(InsertTextMode::AS_IS),
5676 confirm: None,
5677 }));
5678
5679 let menu = if completions.is_empty() {
5680 None
5681 } else {
5682 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5683 let languages = editor
5684 .workspace
5685 .as_ref()
5686 .and_then(|(workspace, _)| workspace.upgrade())
5687 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5688 let menu = CompletionsMenu::new(
5689 id,
5690 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5691 sort_completions,
5692 show_completion_documentation,
5693 position,
5694 query.clone(),
5695 is_incomplete,
5696 buffer.clone(),
5697 completions.into(),
5698 display_options,
5699 snippet_sort_order,
5700 languages,
5701 language,
5702 cx,
5703 );
5704
5705 let query = if filter_completions { query } else { None };
5706 let matches_task = if let Some(query) = query {
5707 menu.do_async_filtering(query, cx)
5708 } else {
5709 Task::ready(menu.unfiltered_matches())
5710 };
5711 (menu, matches_task)
5712 }) else {
5713 return;
5714 };
5715
5716 let matches = matches_task.await;
5717
5718 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5719 // Newer menu already set, so exit.
5720 if let Some(CodeContextMenu::Completions(prev_menu)) =
5721 editor.context_menu.borrow().as_ref()
5722 && prev_menu.id > id
5723 {
5724 return;
5725 };
5726
5727 // Only valid to take prev_menu because it the new menu is immediately set
5728 // below, or the menu is hidden.
5729 if let Some(CodeContextMenu::Completions(prev_menu)) =
5730 editor.context_menu.borrow_mut().take()
5731 {
5732 let position_matches =
5733 if prev_menu.initial_position == menu.initial_position {
5734 true
5735 } else {
5736 let snapshot = editor.buffer.read(cx).read(cx);
5737 prev_menu.initial_position.to_offset(&snapshot)
5738 == menu.initial_position.to_offset(&snapshot)
5739 };
5740 if position_matches {
5741 // Preserve markdown cache before `set_filter_results` because it will
5742 // try to populate the documentation cache.
5743 menu.preserve_markdown_cache(prev_menu);
5744 }
5745 };
5746
5747 menu.set_filter_results(matches, provider, window, cx);
5748 }) else {
5749 return;
5750 };
5751
5752 menu.visible().then_some(menu)
5753 };
5754
5755 editor
5756 .update_in(cx, |editor, window, cx| {
5757 if editor.focus_handle.is_focused(window)
5758 && let Some(menu) = menu
5759 {
5760 *editor.context_menu.borrow_mut() =
5761 Some(CodeContextMenu::Completions(menu));
5762
5763 crate::hover_popover::hide_hover(editor, cx);
5764 if editor.show_edit_predictions_in_menu() {
5765 editor.update_visible_edit_prediction(window, cx);
5766 } else {
5767 editor.discard_edit_prediction(false, cx);
5768 }
5769
5770 cx.notify();
5771 return;
5772 }
5773
5774 if editor.completion_tasks.len() <= 1 {
5775 // If there are no more completion tasks and the last menu was empty, we should hide it.
5776 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5777 // If it was already hidden and we don't show edit predictions in the menu,
5778 // we should also show the edit prediction when available.
5779 if was_hidden && editor.show_edit_predictions_in_menu() {
5780 editor.update_visible_edit_prediction(window, cx);
5781 }
5782 }
5783 })
5784 .ok();
5785 });
5786
5787 self.completion_tasks.push((id, task));
5788 }
5789
5790 #[cfg(feature = "test-support")]
5791 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5792 let menu = self.context_menu.borrow();
5793 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5794 let completions = menu.completions.borrow();
5795 Some(completions.to_vec())
5796 } else {
5797 None
5798 }
5799 }
5800
5801 pub fn with_completions_menu_matching_id<R>(
5802 &self,
5803 id: CompletionId,
5804 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5805 ) -> R {
5806 let mut context_menu = self.context_menu.borrow_mut();
5807 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5808 return f(None);
5809 };
5810 if completions_menu.id != id {
5811 return f(None);
5812 }
5813 f(Some(completions_menu))
5814 }
5815
5816 pub fn confirm_completion(
5817 &mut self,
5818 action: &ConfirmCompletion,
5819 window: &mut Window,
5820 cx: &mut Context<Self>,
5821 ) -> Option<Task<Result<()>>> {
5822 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5823 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5824 }
5825
5826 pub fn confirm_completion_insert(
5827 &mut self,
5828 _: &ConfirmCompletionInsert,
5829 window: &mut Window,
5830 cx: &mut Context<Self>,
5831 ) -> Option<Task<Result<()>>> {
5832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5833 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5834 }
5835
5836 pub fn confirm_completion_replace(
5837 &mut self,
5838 _: &ConfirmCompletionReplace,
5839 window: &mut Window,
5840 cx: &mut Context<Self>,
5841 ) -> Option<Task<Result<()>>> {
5842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5843 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5844 }
5845
5846 pub fn compose_completion(
5847 &mut self,
5848 action: &ComposeCompletion,
5849 window: &mut Window,
5850 cx: &mut Context<Self>,
5851 ) -> Option<Task<Result<()>>> {
5852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5853 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5854 }
5855
5856 fn do_completion(
5857 &mut self,
5858 item_ix: Option<usize>,
5859 intent: CompletionIntent,
5860 window: &mut Window,
5861 cx: &mut Context<Editor>,
5862 ) -> Option<Task<Result<()>>> {
5863 use language::ToOffset as _;
5864
5865 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5866 else {
5867 return None;
5868 };
5869
5870 let candidate_id = {
5871 let entries = completions_menu.entries.borrow();
5872 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5873 if self.show_edit_predictions_in_menu() {
5874 self.discard_edit_prediction(true, cx);
5875 }
5876 mat.candidate_id
5877 };
5878
5879 let completion = completions_menu
5880 .completions
5881 .borrow()
5882 .get(candidate_id)?
5883 .clone();
5884 cx.stop_propagation();
5885
5886 let buffer_handle = completions_menu.buffer.clone();
5887
5888 let CompletionEdit {
5889 new_text,
5890 snippet,
5891 replace_range,
5892 } = process_completion_for_edit(
5893 &completion,
5894 intent,
5895 &buffer_handle,
5896 &completions_menu.initial_position.text_anchor,
5897 cx,
5898 );
5899
5900 let buffer = buffer_handle.read(cx);
5901 let snapshot = self.buffer.read(cx).snapshot(cx);
5902 let newest_anchor = self.selections.newest_anchor();
5903 let replace_range_multibuffer = {
5904 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5905 let multibuffer_anchor = snapshot
5906 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5907 .unwrap()
5908 ..snapshot
5909 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5910 .unwrap();
5911 multibuffer_anchor.start.to_offset(&snapshot)
5912 ..multibuffer_anchor.end.to_offset(&snapshot)
5913 };
5914 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5915 return None;
5916 }
5917
5918 let old_text = buffer
5919 .text_for_range(replace_range.clone())
5920 .collect::<String>();
5921 let lookbehind = newest_anchor
5922 .start
5923 .text_anchor
5924 .to_offset(buffer)
5925 .saturating_sub(replace_range.start);
5926 let lookahead = replace_range
5927 .end
5928 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5929 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5930 let suffix = &old_text[lookbehind.min(old_text.len())..];
5931
5932 let selections = self.selections.all::<usize>(cx);
5933 let mut ranges = Vec::new();
5934 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5935
5936 for selection in &selections {
5937 let range = if selection.id == newest_anchor.id {
5938 replace_range_multibuffer.clone()
5939 } else {
5940 let mut range = selection.range();
5941
5942 // if prefix is present, don't duplicate it
5943 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5944 range.start = range.start.saturating_sub(lookbehind);
5945
5946 // if suffix is also present, mimic the newest cursor and replace it
5947 if selection.id != newest_anchor.id
5948 && snapshot.contains_str_at(range.end, suffix)
5949 {
5950 range.end += lookahead;
5951 }
5952 }
5953 range
5954 };
5955
5956 ranges.push(range.clone());
5957
5958 if !self.linked_edit_ranges.is_empty() {
5959 let start_anchor = snapshot.anchor_before(range.start);
5960 let end_anchor = snapshot.anchor_after(range.end);
5961 if let Some(ranges) = self
5962 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5963 {
5964 for (buffer, edits) in ranges {
5965 linked_edits
5966 .entry(buffer.clone())
5967 .or_default()
5968 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5969 }
5970 }
5971 }
5972 }
5973
5974 let common_prefix_len = old_text
5975 .chars()
5976 .zip(new_text.chars())
5977 .take_while(|(a, b)| a == b)
5978 .map(|(a, _)| a.len_utf8())
5979 .sum::<usize>();
5980
5981 cx.emit(EditorEvent::InputHandled {
5982 utf16_range_to_replace: None,
5983 text: new_text[common_prefix_len..].into(),
5984 });
5985
5986 self.transact(window, cx, |editor, window, cx| {
5987 if let Some(mut snippet) = snippet {
5988 snippet.text = new_text.to_string();
5989 editor
5990 .insert_snippet(&ranges, snippet, window, cx)
5991 .log_err();
5992 } else {
5993 editor.buffer.update(cx, |multi_buffer, cx| {
5994 let auto_indent = match completion.insert_text_mode {
5995 Some(InsertTextMode::AS_IS) => None,
5996 _ => editor.autoindent_mode.clone(),
5997 };
5998 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5999 multi_buffer.edit(edits, auto_indent, cx);
6000 });
6001 }
6002 for (buffer, edits) in linked_edits {
6003 buffer.update(cx, |buffer, cx| {
6004 let snapshot = buffer.snapshot();
6005 let edits = edits
6006 .into_iter()
6007 .map(|(range, text)| {
6008 use text::ToPoint as TP;
6009 let end_point = TP::to_point(&range.end, &snapshot);
6010 let start_point = TP::to_point(&range.start, &snapshot);
6011 (start_point..end_point, text)
6012 })
6013 .sorted_by_key(|(range, _)| range.start);
6014 buffer.edit(edits, None, cx);
6015 })
6016 }
6017
6018 editor.refresh_edit_prediction(true, false, window, cx);
6019 });
6020 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6021
6022 let show_new_completions_on_confirm = completion
6023 .confirm
6024 .as_ref()
6025 .is_some_and(|confirm| confirm(intent, window, cx));
6026 if show_new_completions_on_confirm {
6027 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6028 }
6029
6030 let provider = self.completion_provider.as_ref()?;
6031 drop(completion);
6032 let apply_edits = provider.apply_additional_edits_for_completion(
6033 buffer_handle,
6034 completions_menu.completions.clone(),
6035 candidate_id,
6036 true,
6037 cx,
6038 );
6039
6040 let editor_settings = EditorSettings::get_global(cx);
6041 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6042 // After the code completion is finished, users often want to know what signatures are needed.
6043 // so we should automatically call signature_help
6044 self.show_signature_help(&ShowSignatureHelp, window, cx);
6045 }
6046
6047 Some(cx.foreground_executor().spawn(async move {
6048 apply_edits.await?;
6049 Ok(())
6050 }))
6051 }
6052
6053 pub fn toggle_code_actions(
6054 &mut self,
6055 action: &ToggleCodeActions,
6056 window: &mut Window,
6057 cx: &mut Context<Self>,
6058 ) {
6059 let quick_launch = action.quick_launch;
6060 let mut context_menu = self.context_menu.borrow_mut();
6061 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6062 if code_actions.deployed_from == action.deployed_from {
6063 // Toggle if we're selecting the same one
6064 *context_menu = None;
6065 cx.notify();
6066 return;
6067 } else {
6068 // Otherwise, clear it and start a new one
6069 *context_menu = None;
6070 cx.notify();
6071 }
6072 }
6073 drop(context_menu);
6074 let snapshot = self.snapshot(window, cx);
6075 let deployed_from = action.deployed_from.clone();
6076 let action = action.clone();
6077 self.completion_tasks.clear();
6078 self.discard_edit_prediction(false, cx);
6079
6080 let multibuffer_point = match &action.deployed_from {
6081 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6082 DisplayPoint::new(*row, 0).to_point(&snapshot)
6083 }
6084 _ => self.selections.newest::<Point>(cx).head(),
6085 };
6086 let Some((buffer, buffer_row)) = snapshot
6087 .buffer_snapshot
6088 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6089 .and_then(|(buffer_snapshot, range)| {
6090 self.buffer()
6091 .read(cx)
6092 .buffer(buffer_snapshot.remote_id())
6093 .map(|buffer| (buffer, range.start.row))
6094 })
6095 else {
6096 return;
6097 };
6098 let buffer_id = buffer.read(cx).remote_id();
6099 let tasks = self
6100 .tasks
6101 .get(&(buffer_id, buffer_row))
6102 .map(|t| Arc::new(t.to_owned()));
6103
6104 if !self.focus_handle.is_focused(window) {
6105 return;
6106 }
6107 let project = self.project.clone();
6108
6109 let code_actions_task = match deployed_from {
6110 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6111 _ => self.code_actions(buffer_row, window, cx),
6112 };
6113
6114 let runnable_task = match deployed_from {
6115 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6116 _ => {
6117 let mut task_context_task = Task::ready(None);
6118 if let Some(tasks) = &tasks
6119 && let Some(project) = project
6120 {
6121 task_context_task =
6122 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6123 }
6124
6125 cx.spawn_in(window, {
6126 let buffer = buffer.clone();
6127 async move |editor, cx| {
6128 let task_context = task_context_task.await;
6129
6130 let resolved_tasks =
6131 tasks
6132 .zip(task_context.clone())
6133 .map(|(tasks, task_context)| ResolvedTasks {
6134 templates: tasks.resolve(&task_context).collect(),
6135 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6136 multibuffer_point.row,
6137 tasks.column,
6138 )),
6139 });
6140 let debug_scenarios = editor
6141 .update(cx, |editor, cx| {
6142 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6143 })?
6144 .await;
6145 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6146 }
6147 })
6148 }
6149 };
6150
6151 cx.spawn_in(window, async move |editor, cx| {
6152 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6153 let code_actions = code_actions_task.await;
6154 let spawn_straight_away = quick_launch
6155 && resolved_tasks
6156 .as_ref()
6157 .is_some_and(|tasks| tasks.templates.len() == 1)
6158 && code_actions
6159 .as_ref()
6160 .is_none_or(|actions| actions.is_empty())
6161 && debug_scenarios.is_empty();
6162
6163 editor.update_in(cx, |editor, window, cx| {
6164 crate::hover_popover::hide_hover(editor, cx);
6165 let actions = CodeActionContents::new(
6166 resolved_tasks,
6167 code_actions,
6168 debug_scenarios,
6169 task_context.unwrap_or_default(),
6170 );
6171
6172 // Don't show the menu if there are no actions available
6173 if actions.is_empty() {
6174 cx.notify();
6175 return Task::ready(Ok(()));
6176 }
6177
6178 *editor.context_menu.borrow_mut() =
6179 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6180 buffer,
6181 actions,
6182 selected_item: Default::default(),
6183 scroll_handle: UniformListScrollHandle::default(),
6184 deployed_from,
6185 }));
6186 cx.notify();
6187 if spawn_straight_away
6188 && let Some(task) = editor.confirm_code_action(
6189 &ConfirmCodeAction { item_ix: Some(0) },
6190 window,
6191 cx,
6192 )
6193 {
6194 return task;
6195 }
6196
6197 Task::ready(Ok(()))
6198 })
6199 })
6200 .detach_and_log_err(cx);
6201 }
6202
6203 fn debug_scenarios(
6204 &mut self,
6205 resolved_tasks: &Option<ResolvedTasks>,
6206 buffer: &Entity<Buffer>,
6207 cx: &mut App,
6208 ) -> Task<Vec<task::DebugScenario>> {
6209 maybe!({
6210 let project = self.project()?;
6211 let dap_store = project.read(cx).dap_store();
6212 let mut scenarios = vec![];
6213 let resolved_tasks = resolved_tasks.as_ref()?;
6214 let buffer = buffer.read(cx);
6215 let language = buffer.language()?;
6216 let file = buffer.file();
6217 let debug_adapter = language_settings(language.name().into(), file, cx)
6218 .debuggers
6219 .first()
6220 .map(SharedString::from)
6221 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6222
6223 dap_store.update(cx, |dap_store, cx| {
6224 for (_, task) in &resolved_tasks.templates {
6225 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6226 task.original_task().clone(),
6227 debug_adapter.clone().into(),
6228 task.display_label().to_owned().into(),
6229 cx,
6230 );
6231 scenarios.push(maybe_scenario);
6232 }
6233 });
6234 Some(cx.background_spawn(async move {
6235 futures::future::join_all(scenarios)
6236 .await
6237 .into_iter()
6238 .flatten()
6239 .collect::<Vec<_>>()
6240 }))
6241 })
6242 .unwrap_or_else(|| Task::ready(vec![]))
6243 }
6244
6245 fn code_actions(
6246 &mut self,
6247 buffer_row: u32,
6248 window: &mut Window,
6249 cx: &mut Context<Self>,
6250 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6251 let mut task = self.code_actions_task.take();
6252 cx.spawn_in(window, async move |editor, cx| {
6253 while let Some(prev_task) = task {
6254 prev_task.await.log_err();
6255 task = editor
6256 .update(cx, |this, _| this.code_actions_task.take())
6257 .ok()?;
6258 }
6259
6260 editor
6261 .update(cx, |editor, cx| {
6262 editor
6263 .available_code_actions
6264 .clone()
6265 .and_then(|(location, code_actions)| {
6266 let snapshot = location.buffer.read(cx).snapshot();
6267 let point_range = location.range.to_point(&snapshot);
6268 let point_range = point_range.start.row..=point_range.end.row;
6269 if point_range.contains(&buffer_row) {
6270 Some(code_actions)
6271 } else {
6272 None
6273 }
6274 })
6275 })
6276 .ok()
6277 .flatten()
6278 })
6279 }
6280
6281 pub fn confirm_code_action(
6282 &mut self,
6283 action: &ConfirmCodeAction,
6284 window: &mut Window,
6285 cx: &mut Context<Self>,
6286 ) -> Option<Task<Result<()>>> {
6287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6288
6289 let actions_menu =
6290 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6291 menu
6292 } else {
6293 return None;
6294 };
6295
6296 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6297 let action = actions_menu.actions.get(action_ix)?;
6298 let title = action.label();
6299 let buffer = actions_menu.buffer;
6300 let workspace = self.workspace()?;
6301
6302 match action {
6303 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6304 workspace.update(cx, |workspace, cx| {
6305 workspace.schedule_resolved_task(
6306 task_source_kind,
6307 resolved_task,
6308 false,
6309 window,
6310 cx,
6311 );
6312
6313 Some(Task::ready(Ok(())))
6314 })
6315 }
6316 CodeActionsItem::CodeAction {
6317 excerpt_id,
6318 action,
6319 provider,
6320 } => {
6321 let apply_code_action =
6322 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6323 let workspace = workspace.downgrade();
6324 Some(cx.spawn_in(window, async move |editor, cx| {
6325 let project_transaction = apply_code_action.await?;
6326 Self::open_project_transaction(
6327 &editor,
6328 workspace,
6329 project_transaction,
6330 title,
6331 cx,
6332 )
6333 .await
6334 }))
6335 }
6336 CodeActionsItem::DebugScenario(scenario) => {
6337 let context = actions_menu.actions.context;
6338
6339 workspace.update(cx, |workspace, cx| {
6340 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6341 workspace.start_debug_session(
6342 scenario,
6343 context,
6344 Some(buffer),
6345 None,
6346 window,
6347 cx,
6348 );
6349 });
6350 Some(Task::ready(Ok(())))
6351 }
6352 }
6353 }
6354
6355 pub async fn open_project_transaction(
6356 editor: &WeakEntity<Editor>,
6357 workspace: WeakEntity<Workspace>,
6358 transaction: ProjectTransaction,
6359 title: String,
6360 cx: &mut AsyncWindowContext,
6361 ) -> Result<()> {
6362 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6363 cx.update(|_, cx| {
6364 entries.sort_unstable_by_key(|(buffer, _)| {
6365 buffer.read(cx).file().map(|f| f.path().clone())
6366 });
6367 })?;
6368
6369 // If the project transaction's edits are all contained within this editor, then
6370 // avoid opening a new editor to display them.
6371
6372 if let Some((buffer, transaction)) = entries.first() {
6373 if entries.len() == 1 {
6374 let excerpt = editor.update(cx, |editor, cx| {
6375 editor
6376 .buffer()
6377 .read(cx)
6378 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6379 })?;
6380 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6381 && excerpted_buffer == *buffer
6382 {
6383 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6384 let excerpt_range = excerpt_range.to_offset(buffer);
6385 buffer
6386 .edited_ranges_for_transaction::<usize>(transaction)
6387 .all(|range| {
6388 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6389 })
6390 })?;
6391
6392 if all_edits_within_excerpt {
6393 return Ok(());
6394 }
6395 }
6396 }
6397 } else {
6398 return Ok(());
6399 }
6400
6401 let mut ranges_to_highlight = Vec::new();
6402 let excerpt_buffer = cx.new(|cx| {
6403 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6404 for (buffer_handle, transaction) in &entries {
6405 let edited_ranges = buffer_handle
6406 .read(cx)
6407 .edited_ranges_for_transaction::<Point>(transaction)
6408 .collect::<Vec<_>>();
6409 let (ranges, _) = multibuffer.set_excerpts_for_path(
6410 PathKey::for_buffer(buffer_handle, cx),
6411 buffer_handle.clone(),
6412 edited_ranges,
6413 multibuffer_context_lines(cx),
6414 cx,
6415 );
6416
6417 ranges_to_highlight.extend(ranges);
6418 }
6419 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6420 multibuffer
6421 })?;
6422
6423 workspace.update_in(cx, |workspace, window, cx| {
6424 let project = workspace.project().clone();
6425 let editor =
6426 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6427 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6428 editor.update(cx, |editor, cx| {
6429 editor.highlight_background::<Self>(
6430 &ranges_to_highlight,
6431 |theme| theme.colors().editor_highlighted_line_background,
6432 cx,
6433 );
6434 });
6435 })?;
6436
6437 Ok(())
6438 }
6439
6440 pub fn clear_code_action_providers(&mut self) {
6441 self.code_action_providers.clear();
6442 self.available_code_actions.take();
6443 }
6444
6445 pub fn add_code_action_provider(
6446 &mut self,
6447 provider: Rc<dyn CodeActionProvider>,
6448 window: &mut Window,
6449 cx: &mut Context<Self>,
6450 ) {
6451 if self
6452 .code_action_providers
6453 .iter()
6454 .any(|existing_provider| existing_provider.id() == provider.id())
6455 {
6456 return;
6457 }
6458
6459 self.code_action_providers.push(provider);
6460 self.refresh_code_actions(window, cx);
6461 }
6462
6463 pub fn remove_code_action_provider(
6464 &mut self,
6465 id: Arc<str>,
6466 window: &mut Window,
6467 cx: &mut Context<Self>,
6468 ) {
6469 self.code_action_providers
6470 .retain(|provider| provider.id() != id);
6471 self.refresh_code_actions(window, cx);
6472 }
6473
6474 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6475 !self.code_action_providers.is_empty()
6476 && EditorSettings::get_global(cx).toolbar.code_actions
6477 }
6478
6479 pub fn has_available_code_actions(&self) -> bool {
6480 self.available_code_actions
6481 .as_ref()
6482 .is_some_and(|(_, actions)| !actions.is_empty())
6483 }
6484
6485 fn render_inline_code_actions(
6486 &self,
6487 icon_size: ui::IconSize,
6488 display_row: DisplayRow,
6489 is_active: bool,
6490 cx: &mut Context<Self>,
6491 ) -> AnyElement {
6492 let show_tooltip = !self.context_menu_visible();
6493 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6494 .icon_size(icon_size)
6495 .shape(ui::IconButtonShape::Square)
6496 .icon_color(ui::Color::Hidden)
6497 .toggle_state(is_active)
6498 .when(show_tooltip, |this| {
6499 this.tooltip({
6500 let focus_handle = self.focus_handle.clone();
6501 move |window, cx| {
6502 Tooltip::for_action_in(
6503 "Toggle Code Actions",
6504 &ToggleCodeActions {
6505 deployed_from: None,
6506 quick_launch: false,
6507 },
6508 &focus_handle,
6509 window,
6510 cx,
6511 )
6512 }
6513 })
6514 })
6515 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6516 window.focus(&editor.focus_handle(cx));
6517 editor.toggle_code_actions(
6518 &crate::actions::ToggleCodeActions {
6519 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6520 display_row,
6521 )),
6522 quick_launch: false,
6523 },
6524 window,
6525 cx,
6526 );
6527 }))
6528 .into_any_element()
6529 }
6530
6531 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6532 &self.context_menu
6533 }
6534
6535 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6536 let newest_selection = self.selections.newest_anchor().clone();
6537 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6538 let buffer = self.buffer.read(cx);
6539 if newest_selection.head().diff_base_anchor.is_some() {
6540 return None;
6541 }
6542 let (start_buffer, start) =
6543 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6544 let (end_buffer, end) =
6545 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6546 if start_buffer != end_buffer {
6547 return None;
6548 }
6549
6550 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6551 cx.background_executor()
6552 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6553 .await;
6554
6555 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6556 let providers = this.code_action_providers.clone();
6557 let tasks = this
6558 .code_action_providers
6559 .iter()
6560 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6561 .collect::<Vec<_>>();
6562 (providers, tasks)
6563 })?;
6564
6565 let mut actions = Vec::new();
6566 for (provider, provider_actions) in
6567 providers.into_iter().zip(future::join_all(tasks).await)
6568 {
6569 if let Some(provider_actions) = provider_actions.log_err() {
6570 actions.extend(provider_actions.into_iter().map(|action| {
6571 AvailableCodeAction {
6572 excerpt_id: newest_selection.start.excerpt_id,
6573 action,
6574 provider: provider.clone(),
6575 }
6576 }));
6577 }
6578 }
6579
6580 this.update(cx, |this, cx| {
6581 this.available_code_actions = if actions.is_empty() {
6582 None
6583 } else {
6584 Some((
6585 Location {
6586 buffer: start_buffer,
6587 range: start..end,
6588 },
6589 actions.into(),
6590 ))
6591 };
6592 cx.notify();
6593 })
6594 }));
6595 None
6596 }
6597
6598 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6599 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6600 self.show_git_blame_inline = false;
6601
6602 self.show_git_blame_inline_delay_task =
6603 Some(cx.spawn_in(window, async move |this, cx| {
6604 cx.background_executor().timer(delay).await;
6605
6606 this.update(cx, |this, cx| {
6607 this.show_git_blame_inline = true;
6608 cx.notify();
6609 })
6610 .log_err();
6611 }));
6612 }
6613 }
6614
6615 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6616 let snapshot = self.snapshot(window, cx);
6617 let cursor = self.selections.newest::<Point>(cx).head();
6618 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6619 else {
6620 return;
6621 };
6622
6623 let Some(blame) = self.blame.as_ref() else {
6624 return;
6625 };
6626
6627 let row_info = RowInfo {
6628 buffer_id: Some(buffer.remote_id()),
6629 buffer_row: Some(point.row),
6630 ..Default::default()
6631 };
6632 let Some((buffer, blame_entry)) = blame
6633 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6634 .flatten()
6635 else {
6636 return;
6637 };
6638
6639 let anchor = self.selections.newest_anchor().head();
6640 let position = self.to_pixel_point(anchor, &snapshot, window);
6641 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6642 self.show_blame_popover(
6643 buffer,
6644 &blame_entry,
6645 position + last_bounds.origin,
6646 true,
6647 cx,
6648 );
6649 };
6650 }
6651
6652 fn show_blame_popover(
6653 &mut self,
6654 buffer: BufferId,
6655 blame_entry: &BlameEntry,
6656 position: gpui::Point<Pixels>,
6657 ignore_timeout: bool,
6658 cx: &mut Context<Self>,
6659 ) {
6660 if let Some(state) = &mut self.inline_blame_popover {
6661 state.hide_task.take();
6662 } else {
6663 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6664 let blame_entry = blame_entry.clone();
6665 let show_task = cx.spawn(async move |editor, cx| {
6666 if !ignore_timeout {
6667 cx.background_executor()
6668 .timer(std::time::Duration::from_millis(blame_popover_delay))
6669 .await;
6670 }
6671 editor
6672 .update(cx, |editor, cx| {
6673 editor.inline_blame_popover_show_task.take();
6674 let Some(blame) = editor.blame.as_ref() else {
6675 return;
6676 };
6677 let blame = blame.read(cx);
6678 let details = blame.details_for_entry(buffer, &blame_entry);
6679 let markdown = cx.new(|cx| {
6680 Markdown::new(
6681 details
6682 .as_ref()
6683 .map(|message| message.message.clone())
6684 .unwrap_or_default(),
6685 None,
6686 None,
6687 cx,
6688 )
6689 });
6690 editor.inline_blame_popover = Some(InlineBlamePopover {
6691 position,
6692 hide_task: None,
6693 popover_bounds: None,
6694 popover_state: InlineBlamePopoverState {
6695 scroll_handle: ScrollHandle::new(),
6696 commit_message: details,
6697 markdown,
6698 },
6699 keyboard_grace: ignore_timeout,
6700 });
6701 cx.notify();
6702 })
6703 .ok();
6704 });
6705 self.inline_blame_popover_show_task = Some(show_task);
6706 }
6707 }
6708
6709 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6710 self.inline_blame_popover_show_task.take();
6711 if let Some(state) = &mut self.inline_blame_popover {
6712 let hide_task = cx.spawn(async move |editor, cx| {
6713 cx.background_executor()
6714 .timer(std::time::Duration::from_millis(100))
6715 .await;
6716 editor
6717 .update(cx, |editor, cx| {
6718 editor.inline_blame_popover.take();
6719 cx.notify();
6720 })
6721 .ok();
6722 });
6723 state.hide_task = Some(hide_task);
6724 }
6725 }
6726
6727 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6728 if self.pending_rename.is_some() {
6729 return None;
6730 }
6731
6732 let provider = self.semantics_provider.clone()?;
6733 let buffer = self.buffer.read(cx);
6734 let newest_selection = self.selections.newest_anchor().clone();
6735 let cursor_position = newest_selection.head();
6736 let (cursor_buffer, cursor_buffer_position) =
6737 buffer.text_anchor_for_position(cursor_position, cx)?;
6738 let (tail_buffer, tail_buffer_position) =
6739 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6740 if cursor_buffer != tail_buffer {
6741 return None;
6742 }
6743
6744 let snapshot = cursor_buffer.read(cx).snapshot();
6745 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6746 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6747 if start_word_range != end_word_range {
6748 self.document_highlights_task.take();
6749 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6750 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6751 return None;
6752 }
6753
6754 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6755 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6756 cx.background_executor()
6757 .timer(Duration::from_millis(debounce))
6758 .await;
6759
6760 let highlights = if let Some(highlights) = cx
6761 .update(|cx| {
6762 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6763 })
6764 .ok()
6765 .flatten()
6766 {
6767 highlights.await.log_err()
6768 } else {
6769 None
6770 };
6771
6772 if let Some(highlights) = highlights {
6773 this.update(cx, |this, cx| {
6774 if this.pending_rename.is_some() {
6775 return;
6776 }
6777
6778 let buffer = this.buffer.read(cx);
6779 if buffer
6780 .text_anchor_for_position(cursor_position, cx)
6781 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6782 {
6783 return;
6784 }
6785
6786 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6787 let mut write_ranges = Vec::new();
6788 let mut read_ranges = Vec::new();
6789 for highlight in highlights {
6790 let buffer_id = cursor_buffer.read(cx).remote_id();
6791 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6792 {
6793 let start = highlight
6794 .range
6795 .start
6796 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6797 let end = highlight
6798 .range
6799 .end
6800 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6801 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6802 continue;
6803 }
6804
6805 let range = Anchor {
6806 buffer_id: Some(buffer_id),
6807 excerpt_id,
6808 text_anchor: start,
6809 diff_base_anchor: None,
6810 }..Anchor {
6811 buffer_id: Some(buffer_id),
6812 excerpt_id,
6813 text_anchor: end,
6814 diff_base_anchor: None,
6815 };
6816 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6817 write_ranges.push(range);
6818 } else {
6819 read_ranges.push(range);
6820 }
6821 }
6822 }
6823
6824 this.highlight_background::<DocumentHighlightRead>(
6825 &read_ranges,
6826 |theme| theme.colors().editor_document_highlight_read_background,
6827 cx,
6828 );
6829 this.highlight_background::<DocumentHighlightWrite>(
6830 &write_ranges,
6831 |theme| theme.colors().editor_document_highlight_write_background,
6832 cx,
6833 );
6834 cx.notify();
6835 })
6836 .log_err();
6837 }
6838 }));
6839 None
6840 }
6841
6842 fn prepare_highlight_query_from_selection(
6843 &mut self,
6844 cx: &mut Context<Editor>,
6845 ) -> Option<(String, Range<Anchor>)> {
6846 if matches!(self.mode, EditorMode::SingleLine) {
6847 return None;
6848 }
6849 if !EditorSettings::get_global(cx).selection_highlight {
6850 return None;
6851 }
6852 if self.selections.count() != 1 || self.selections.line_mode {
6853 return None;
6854 }
6855 let selection = self.selections.newest::<Point>(cx);
6856 if selection.is_empty() || selection.start.row != selection.end.row {
6857 return None;
6858 }
6859 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6860 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6861 let query = multi_buffer_snapshot
6862 .text_for_range(selection_anchor_range.clone())
6863 .collect::<String>();
6864 if query.trim().is_empty() {
6865 return None;
6866 }
6867 Some((query, selection_anchor_range))
6868 }
6869
6870 fn update_selection_occurrence_highlights(
6871 &mut self,
6872 query_text: String,
6873 query_range: Range<Anchor>,
6874 multi_buffer_range_to_query: Range<Point>,
6875 use_debounce: bool,
6876 window: &mut Window,
6877 cx: &mut Context<Editor>,
6878 ) -> Task<()> {
6879 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6880 cx.spawn_in(window, async move |editor, cx| {
6881 if use_debounce {
6882 cx.background_executor()
6883 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6884 .await;
6885 }
6886 let match_task = cx.background_spawn(async move {
6887 let buffer_ranges = multi_buffer_snapshot
6888 .range_to_buffer_ranges(multi_buffer_range_to_query)
6889 .into_iter()
6890 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6891 let mut match_ranges = Vec::new();
6892 let Ok(regex) = project::search::SearchQuery::text(
6893 query_text.clone(),
6894 false,
6895 false,
6896 false,
6897 Default::default(),
6898 Default::default(),
6899 false,
6900 None,
6901 ) else {
6902 return Vec::default();
6903 };
6904 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6905 match_ranges.extend(
6906 regex
6907 .search(buffer_snapshot, Some(search_range.clone()))
6908 .await
6909 .into_iter()
6910 .filter_map(|match_range| {
6911 let match_start = buffer_snapshot
6912 .anchor_after(search_range.start + match_range.start);
6913 let match_end = buffer_snapshot
6914 .anchor_before(search_range.start + match_range.end);
6915 let match_anchor_range = Anchor::range_in_buffer(
6916 excerpt_id,
6917 buffer_snapshot.remote_id(),
6918 match_start..match_end,
6919 );
6920 (match_anchor_range != query_range).then_some(match_anchor_range)
6921 }),
6922 );
6923 }
6924 match_ranges
6925 });
6926 let match_ranges = match_task.await;
6927 editor
6928 .update_in(cx, |editor, _, cx| {
6929 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6930 if !match_ranges.is_empty() {
6931 editor.highlight_background::<SelectedTextHighlight>(
6932 &match_ranges,
6933 |theme| theme.colors().editor_document_highlight_bracket_background,
6934 cx,
6935 )
6936 }
6937 })
6938 .log_err();
6939 })
6940 }
6941
6942 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6943 struct NewlineFold;
6944 let type_id = std::any::TypeId::of::<NewlineFold>();
6945 if !self.mode.is_single_line() {
6946 return;
6947 }
6948 let snapshot = self.snapshot(window, cx);
6949 if snapshot.buffer_snapshot.max_point().row == 0 {
6950 return;
6951 }
6952 let task = cx.background_spawn(async move {
6953 let new_newlines = snapshot
6954 .buffer_chars_at(0)
6955 .filter_map(|(c, i)| {
6956 if c == '\n' {
6957 Some(
6958 snapshot.buffer_snapshot.anchor_after(i)
6959 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6960 )
6961 } else {
6962 None
6963 }
6964 })
6965 .collect::<Vec<_>>();
6966 let existing_newlines = snapshot
6967 .folds_in_range(0..snapshot.buffer_snapshot.len())
6968 .filter_map(|fold| {
6969 if fold.placeholder.type_tag == Some(type_id) {
6970 Some(fold.range.start..fold.range.end)
6971 } else {
6972 None
6973 }
6974 })
6975 .collect::<Vec<_>>();
6976
6977 (new_newlines, existing_newlines)
6978 });
6979 self.folding_newlines = cx.spawn(async move |this, cx| {
6980 let (new_newlines, existing_newlines) = task.await;
6981 if new_newlines == existing_newlines {
6982 return;
6983 }
6984 let placeholder = FoldPlaceholder {
6985 render: Arc::new(move |_, _, cx| {
6986 div()
6987 .bg(cx.theme().status().hint_background)
6988 .border_b_1()
6989 .size_full()
6990 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6991 .border_color(cx.theme().status().hint)
6992 .child("\\n")
6993 .into_any()
6994 }),
6995 constrain_width: false,
6996 merge_adjacent: false,
6997 type_tag: Some(type_id),
6998 };
6999 let creases = new_newlines
7000 .into_iter()
7001 .map(|range| Crease::simple(range, placeholder.clone()))
7002 .collect();
7003 this.update(cx, |this, cx| {
7004 this.display_map.update(cx, |display_map, cx| {
7005 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7006 display_map.fold(creases, cx);
7007 });
7008 })
7009 .ok();
7010 });
7011 }
7012
7013 fn refresh_selected_text_highlights(
7014 &mut self,
7015 on_buffer_edit: bool,
7016 window: &mut Window,
7017 cx: &mut Context<Editor>,
7018 ) {
7019 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7020 else {
7021 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7022 self.quick_selection_highlight_task.take();
7023 self.debounced_selection_highlight_task.take();
7024 return;
7025 };
7026 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7027 if on_buffer_edit
7028 || self
7029 .quick_selection_highlight_task
7030 .as_ref()
7031 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7032 {
7033 let multi_buffer_visible_start = self
7034 .scroll_manager
7035 .anchor()
7036 .anchor
7037 .to_point(&multi_buffer_snapshot);
7038 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7039 multi_buffer_visible_start
7040 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7041 Bias::Left,
7042 );
7043 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7044 self.quick_selection_highlight_task = Some((
7045 query_range.clone(),
7046 self.update_selection_occurrence_highlights(
7047 query_text.clone(),
7048 query_range.clone(),
7049 multi_buffer_visible_range,
7050 false,
7051 window,
7052 cx,
7053 ),
7054 ));
7055 }
7056 if on_buffer_edit
7057 || self
7058 .debounced_selection_highlight_task
7059 .as_ref()
7060 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7061 {
7062 let multi_buffer_start = multi_buffer_snapshot
7063 .anchor_before(0)
7064 .to_point(&multi_buffer_snapshot);
7065 let multi_buffer_end = multi_buffer_snapshot
7066 .anchor_after(multi_buffer_snapshot.len())
7067 .to_point(&multi_buffer_snapshot);
7068 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7069 self.debounced_selection_highlight_task = Some((
7070 query_range.clone(),
7071 self.update_selection_occurrence_highlights(
7072 query_text,
7073 query_range,
7074 multi_buffer_full_range,
7075 true,
7076 window,
7077 cx,
7078 ),
7079 ));
7080 }
7081 }
7082
7083 pub fn refresh_edit_prediction(
7084 &mut self,
7085 debounce: bool,
7086 user_requested: bool,
7087 window: &mut Window,
7088 cx: &mut Context<Self>,
7089 ) -> Option<()> {
7090 if DisableAiSettings::get_global(cx).disable_ai {
7091 return None;
7092 }
7093
7094 let provider = self.edit_prediction_provider()?;
7095 let cursor = self.selections.newest_anchor().head();
7096 let (buffer, cursor_buffer_position) =
7097 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7098
7099 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7100 self.discard_edit_prediction(false, cx);
7101 return None;
7102 }
7103
7104 if !user_requested
7105 && (!self.should_show_edit_predictions()
7106 || !self.is_focused(window)
7107 || buffer.read(cx).is_empty())
7108 {
7109 self.discard_edit_prediction(false, cx);
7110 return None;
7111 }
7112
7113 self.update_visible_edit_prediction(window, cx);
7114 provider.refresh(
7115 self.project.clone(),
7116 buffer,
7117 cursor_buffer_position,
7118 debounce,
7119 cx,
7120 );
7121 Some(())
7122 }
7123
7124 fn show_edit_predictions_in_menu(&self) -> bool {
7125 match self.edit_prediction_settings {
7126 EditPredictionSettings::Disabled => false,
7127 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7128 }
7129 }
7130
7131 pub fn edit_predictions_enabled(&self) -> bool {
7132 match self.edit_prediction_settings {
7133 EditPredictionSettings::Disabled => false,
7134 EditPredictionSettings::Enabled { .. } => true,
7135 }
7136 }
7137
7138 fn edit_prediction_requires_modifier(&self) -> bool {
7139 match self.edit_prediction_settings {
7140 EditPredictionSettings::Disabled => false,
7141 EditPredictionSettings::Enabled {
7142 preview_requires_modifier,
7143 ..
7144 } => preview_requires_modifier,
7145 }
7146 }
7147
7148 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7149 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7150 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7151 self.discard_edit_prediction(false, cx);
7152 } else {
7153 let selection = self.selections.newest_anchor();
7154 let cursor = selection.head();
7155
7156 if let Some((buffer, cursor_buffer_position)) =
7157 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7158 {
7159 self.edit_prediction_settings =
7160 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7161 }
7162 }
7163 }
7164
7165 fn edit_prediction_settings_at_position(
7166 &self,
7167 buffer: &Entity<Buffer>,
7168 buffer_position: language::Anchor,
7169 cx: &App,
7170 ) -> EditPredictionSettings {
7171 if !self.mode.is_full()
7172 || !self.show_edit_predictions_override.unwrap_or(true)
7173 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7174 {
7175 return EditPredictionSettings::Disabled;
7176 }
7177
7178 let buffer = buffer.read(cx);
7179
7180 let file = buffer.file();
7181
7182 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7183 return EditPredictionSettings::Disabled;
7184 };
7185
7186 let by_provider = matches!(
7187 self.menu_edit_predictions_policy,
7188 MenuEditPredictionsPolicy::ByProvider
7189 );
7190
7191 let show_in_menu = by_provider
7192 && self
7193 .edit_prediction_provider
7194 .as_ref()
7195 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7196
7197 let preview_requires_modifier =
7198 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7199
7200 EditPredictionSettings::Enabled {
7201 show_in_menu,
7202 preview_requires_modifier,
7203 }
7204 }
7205
7206 fn should_show_edit_predictions(&self) -> bool {
7207 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7208 }
7209
7210 pub fn edit_prediction_preview_is_active(&self) -> bool {
7211 matches!(
7212 self.edit_prediction_preview,
7213 EditPredictionPreview::Active { .. }
7214 )
7215 }
7216
7217 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7218 let cursor = self.selections.newest_anchor().head();
7219 if let Some((buffer, cursor_position)) =
7220 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7221 {
7222 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7223 } else {
7224 false
7225 }
7226 }
7227
7228 pub fn supports_minimap(&self, cx: &App) -> bool {
7229 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7230 }
7231
7232 fn edit_predictions_enabled_in_buffer(
7233 &self,
7234 buffer: &Entity<Buffer>,
7235 buffer_position: language::Anchor,
7236 cx: &App,
7237 ) -> bool {
7238 maybe!({
7239 if self.read_only(cx) {
7240 return Some(false);
7241 }
7242 let provider = self.edit_prediction_provider()?;
7243 if !provider.is_enabled(buffer, buffer_position, cx) {
7244 return Some(false);
7245 }
7246 let buffer = buffer.read(cx);
7247 let Some(file) = buffer.file() else {
7248 return Some(true);
7249 };
7250 let settings = all_language_settings(Some(file), cx);
7251 Some(settings.edit_predictions_enabled_for_file(file, cx))
7252 })
7253 .unwrap_or(false)
7254 }
7255
7256 fn cycle_edit_prediction(
7257 &mut self,
7258 direction: Direction,
7259 window: &mut Window,
7260 cx: &mut Context<Self>,
7261 ) -> Option<()> {
7262 let provider = self.edit_prediction_provider()?;
7263 let cursor = self.selections.newest_anchor().head();
7264 let (buffer, cursor_buffer_position) =
7265 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7266 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7267 return None;
7268 }
7269
7270 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7271 self.update_visible_edit_prediction(window, cx);
7272
7273 Some(())
7274 }
7275
7276 pub fn show_edit_prediction(
7277 &mut self,
7278 _: &ShowEditPrediction,
7279 window: &mut Window,
7280 cx: &mut Context<Self>,
7281 ) {
7282 if !self.has_active_edit_prediction() {
7283 self.refresh_edit_prediction(false, true, window, cx);
7284 return;
7285 }
7286
7287 self.update_visible_edit_prediction(window, cx);
7288 }
7289
7290 pub fn display_cursor_names(
7291 &mut self,
7292 _: &DisplayCursorNames,
7293 window: &mut Window,
7294 cx: &mut Context<Self>,
7295 ) {
7296 self.show_cursor_names(window, cx);
7297 }
7298
7299 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7300 self.show_cursor_names = true;
7301 cx.notify();
7302 cx.spawn_in(window, async move |this, cx| {
7303 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7304 this.update(cx, |this, cx| {
7305 this.show_cursor_names = false;
7306 cx.notify()
7307 })
7308 .ok()
7309 })
7310 .detach();
7311 }
7312
7313 pub fn next_edit_prediction(
7314 &mut self,
7315 _: &NextEditPrediction,
7316 window: &mut Window,
7317 cx: &mut Context<Self>,
7318 ) {
7319 if self.has_active_edit_prediction() {
7320 self.cycle_edit_prediction(Direction::Next, window, cx);
7321 } else {
7322 let is_copilot_disabled = self
7323 .refresh_edit_prediction(false, true, window, cx)
7324 .is_none();
7325 if is_copilot_disabled {
7326 cx.propagate();
7327 }
7328 }
7329 }
7330
7331 pub fn previous_edit_prediction(
7332 &mut self,
7333 _: &PreviousEditPrediction,
7334 window: &mut Window,
7335 cx: &mut Context<Self>,
7336 ) {
7337 if self.has_active_edit_prediction() {
7338 self.cycle_edit_prediction(Direction::Prev, window, cx);
7339 } else {
7340 let is_copilot_disabled = self
7341 .refresh_edit_prediction(false, true, window, cx)
7342 .is_none();
7343 if is_copilot_disabled {
7344 cx.propagate();
7345 }
7346 }
7347 }
7348
7349 pub fn accept_edit_prediction(
7350 &mut self,
7351 _: &AcceptEditPrediction,
7352 window: &mut Window,
7353 cx: &mut Context<Self>,
7354 ) {
7355 if self.show_edit_predictions_in_menu() {
7356 self.hide_context_menu(window, cx);
7357 }
7358
7359 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7360 return;
7361 };
7362
7363 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7364
7365 match &active_edit_prediction.completion {
7366 EditPrediction::Move { target, .. } => {
7367 let target = *target;
7368
7369 if let Some(position_map) = &self.last_position_map {
7370 if position_map
7371 .visible_row_range
7372 .contains(&target.to_display_point(&position_map.snapshot).row())
7373 || !self.edit_prediction_requires_modifier()
7374 {
7375 self.unfold_ranges(&[target..target], true, false, cx);
7376 // Note that this is also done in vim's handler of the Tab action.
7377 self.change_selections(
7378 SelectionEffects::scroll(Autoscroll::newest()),
7379 window,
7380 cx,
7381 |selections| {
7382 selections.select_anchor_ranges([target..target]);
7383 },
7384 );
7385 self.clear_row_highlights::<EditPredictionPreview>();
7386
7387 self.edit_prediction_preview
7388 .set_previous_scroll_position(None);
7389 } else {
7390 self.edit_prediction_preview
7391 .set_previous_scroll_position(Some(
7392 position_map.snapshot.scroll_anchor,
7393 ));
7394
7395 self.highlight_rows::<EditPredictionPreview>(
7396 target..target,
7397 cx.theme().colors().editor_highlighted_line_background,
7398 RowHighlightOptions {
7399 autoscroll: true,
7400 ..Default::default()
7401 },
7402 cx,
7403 );
7404 self.request_autoscroll(Autoscroll::fit(), cx);
7405 }
7406 }
7407 }
7408 EditPrediction::Edit { edits, .. } => {
7409 if let Some(provider) = self.edit_prediction_provider() {
7410 provider.accept(cx);
7411 }
7412
7413 // Store the transaction ID and selections before applying the edit
7414 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7415
7416 let snapshot = self.buffer.read(cx).snapshot(cx);
7417 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7418
7419 self.buffer.update(cx, |buffer, cx| {
7420 buffer.edit(edits.iter().cloned(), None, cx)
7421 });
7422
7423 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7424 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7425 });
7426
7427 let selections = self.selections.disjoint_anchors();
7428 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7429 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7430 if has_new_transaction {
7431 self.selection_history
7432 .insert_transaction(transaction_id_now, selections);
7433 }
7434 }
7435
7436 self.update_visible_edit_prediction(window, cx);
7437 if self.active_edit_prediction.is_none() {
7438 self.refresh_edit_prediction(true, true, window, cx);
7439 }
7440
7441 cx.notify();
7442 }
7443 }
7444
7445 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7446 }
7447
7448 pub fn accept_partial_edit_prediction(
7449 &mut self,
7450 _: &AcceptPartialEditPrediction,
7451 window: &mut Window,
7452 cx: &mut Context<Self>,
7453 ) {
7454 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7455 return;
7456 };
7457 if self.selections.count() != 1 {
7458 return;
7459 }
7460
7461 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7462
7463 match &active_edit_prediction.completion {
7464 EditPrediction::Move { target, .. } => {
7465 let target = *target;
7466 self.change_selections(
7467 SelectionEffects::scroll(Autoscroll::newest()),
7468 window,
7469 cx,
7470 |selections| {
7471 selections.select_anchor_ranges([target..target]);
7472 },
7473 );
7474 }
7475 EditPrediction::Edit { edits, .. } => {
7476 // Find an insertion that starts at the cursor position.
7477 let snapshot = self.buffer.read(cx).snapshot(cx);
7478 let cursor_offset = self.selections.newest::<usize>(cx).head();
7479 let insertion = edits.iter().find_map(|(range, text)| {
7480 let range = range.to_offset(&snapshot);
7481 if range.is_empty() && range.start == cursor_offset {
7482 Some(text)
7483 } else {
7484 None
7485 }
7486 });
7487
7488 if let Some(text) = insertion {
7489 let mut partial_completion = text
7490 .chars()
7491 .by_ref()
7492 .take_while(|c| c.is_alphabetic())
7493 .collect::<String>();
7494 if partial_completion.is_empty() {
7495 partial_completion = text
7496 .chars()
7497 .by_ref()
7498 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7499 .collect::<String>();
7500 }
7501
7502 cx.emit(EditorEvent::InputHandled {
7503 utf16_range_to_replace: None,
7504 text: partial_completion.clone().into(),
7505 });
7506
7507 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7508
7509 self.refresh_edit_prediction(true, true, window, cx);
7510 cx.notify();
7511 } else {
7512 self.accept_edit_prediction(&Default::default(), window, cx);
7513 }
7514 }
7515 }
7516 }
7517
7518 fn discard_edit_prediction(
7519 &mut self,
7520 should_report_edit_prediction_event: bool,
7521 cx: &mut Context<Self>,
7522 ) -> bool {
7523 if should_report_edit_prediction_event {
7524 let completion_id = self
7525 .active_edit_prediction
7526 .as_ref()
7527 .and_then(|active_completion| active_completion.completion_id.clone());
7528
7529 self.report_edit_prediction_event(completion_id, false, cx);
7530 }
7531
7532 if let Some(provider) = self.edit_prediction_provider() {
7533 provider.discard(cx);
7534 }
7535
7536 self.take_active_edit_prediction(cx)
7537 }
7538
7539 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7540 let Some(provider) = self.edit_prediction_provider() else {
7541 return;
7542 };
7543
7544 let Some((_, buffer, _)) = self
7545 .buffer
7546 .read(cx)
7547 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7548 else {
7549 return;
7550 };
7551
7552 let extension = buffer
7553 .read(cx)
7554 .file()
7555 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7556
7557 let event_type = match accepted {
7558 true => "Edit Prediction Accepted",
7559 false => "Edit Prediction Discarded",
7560 };
7561 telemetry::event!(
7562 event_type,
7563 provider = provider.name(),
7564 prediction_id = id,
7565 suggestion_accepted = accepted,
7566 file_extension = extension,
7567 );
7568 }
7569
7570 pub fn has_active_edit_prediction(&self) -> bool {
7571 self.active_edit_prediction.is_some()
7572 }
7573
7574 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7575 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7576 return false;
7577 };
7578
7579 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7580 self.clear_highlights::<EditPredictionHighlight>(cx);
7581 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7582 true
7583 }
7584
7585 /// Returns true when we're displaying the edit prediction popover below the cursor
7586 /// like we are not previewing and the LSP autocomplete menu is visible
7587 /// or we are in `when_holding_modifier` mode.
7588 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7589 if self.edit_prediction_preview_is_active()
7590 || !self.show_edit_predictions_in_menu()
7591 || !self.edit_predictions_enabled()
7592 {
7593 return false;
7594 }
7595
7596 if self.has_visible_completions_menu() {
7597 return true;
7598 }
7599
7600 has_completion && self.edit_prediction_requires_modifier()
7601 }
7602
7603 fn handle_modifiers_changed(
7604 &mut self,
7605 modifiers: Modifiers,
7606 position_map: &PositionMap,
7607 window: &mut Window,
7608 cx: &mut Context<Self>,
7609 ) {
7610 if self.show_edit_predictions_in_menu() {
7611 self.update_edit_prediction_preview(&modifiers, window, cx);
7612 }
7613
7614 self.update_selection_mode(&modifiers, position_map, window, cx);
7615
7616 let mouse_position = window.mouse_position();
7617 if !position_map.text_hitbox.is_hovered(window) {
7618 return;
7619 }
7620
7621 self.update_hovered_link(
7622 position_map.point_for_position(mouse_position),
7623 &position_map.snapshot,
7624 modifiers,
7625 window,
7626 cx,
7627 )
7628 }
7629
7630 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7631 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7632 if invert {
7633 match multi_cursor_setting {
7634 MultiCursorModifier::Alt => modifiers.alt,
7635 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7636 }
7637 } else {
7638 match multi_cursor_setting {
7639 MultiCursorModifier::Alt => modifiers.secondary(),
7640 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7641 }
7642 }
7643 }
7644
7645 fn columnar_selection_mode(
7646 modifiers: &Modifiers,
7647 cx: &mut Context<Self>,
7648 ) -> Option<ColumnarMode> {
7649 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7650 if Self::multi_cursor_modifier(false, modifiers, cx) {
7651 Some(ColumnarMode::FromMouse)
7652 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7653 Some(ColumnarMode::FromSelection)
7654 } else {
7655 None
7656 }
7657 } else {
7658 None
7659 }
7660 }
7661
7662 fn update_selection_mode(
7663 &mut self,
7664 modifiers: &Modifiers,
7665 position_map: &PositionMap,
7666 window: &mut Window,
7667 cx: &mut Context<Self>,
7668 ) {
7669 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7670 return;
7671 };
7672 if self.selections.pending.is_none() {
7673 return;
7674 }
7675
7676 let mouse_position = window.mouse_position();
7677 let point_for_position = position_map.point_for_position(mouse_position);
7678 let position = point_for_position.previous_valid;
7679
7680 self.select(
7681 SelectPhase::BeginColumnar {
7682 position,
7683 reset: false,
7684 mode,
7685 goal_column: point_for_position.exact_unclipped.column(),
7686 },
7687 window,
7688 cx,
7689 );
7690 }
7691
7692 fn update_edit_prediction_preview(
7693 &mut self,
7694 modifiers: &Modifiers,
7695 window: &mut Window,
7696 cx: &mut Context<Self>,
7697 ) {
7698 let mut modifiers_held = false;
7699 if let Some(accept_keystroke) = self
7700 .accept_edit_prediction_keybind(false, window, cx)
7701 .keystroke()
7702 {
7703 modifiers_held = modifiers_held
7704 || (accept_keystroke.modifiers() == modifiers
7705 && accept_keystroke.modifiers().modified());
7706 };
7707 if let Some(accept_partial_keystroke) = self
7708 .accept_edit_prediction_keybind(true, window, cx)
7709 .keystroke()
7710 {
7711 modifiers_held = modifiers_held
7712 || (accept_partial_keystroke.modifiers() == modifiers
7713 && accept_partial_keystroke.modifiers().modified());
7714 }
7715
7716 if modifiers_held {
7717 if matches!(
7718 self.edit_prediction_preview,
7719 EditPredictionPreview::Inactive { .. }
7720 ) {
7721 self.edit_prediction_preview = EditPredictionPreview::Active {
7722 previous_scroll_position: None,
7723 since: Instant::now(),
7724 };
7725
7726 self.update_visible_edit_prediction(window, cx);
7727 cx.notify();
7728 }
7729 } else if let EditPredictionPreview::Active {
7730 previous_scroll_position,
7731 since,
7732 } = self.edit_prediction_preview
7733 {
7734 if let (Some(previous_scroll_position), Some(position_map)) =
7735 (previous_scroll_position, self.last_position_map.as_ref())
7736 {
7737 self.set_scroll_position(
7738 previous_scroll_position
7739 .scroll_position(&position_map.snapshot.display_snapshot),
7740 window,
7741 cx,
7742 );
7743 }
7744
7745 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7746 released_too_fast: since.elapsed() < Duration::from_millis(200),
7747 };
7748 self.clear_row_highlights::<EditPredictionPreview>();
7749 self.update_visible_edit_prediction(window, cx);
7750 cx.notify();
7751 }
7752 }
7753
7754 fn update_visible_edit_prediction(
7755 &mut self,
7756 _window: &mut Window,
7757 cx: &mut Context<Self>,
7758 ) -> Option<()> {
7759 if DisableAiSettings::get_global(cx).disable_ai {
7760 return None;
7761 }
7762
7763 if self.ime_transaction.is_some() {
7764 self.discard_edit_prediction(false, cx);
7765 return None;
7766 }
7767
7768 let selection = self.selections.newest_anchor();
7769 let cursor = selection.head();
7770 let multibuffer = self.buffer.read(cx).snapshot(cx);
7771 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7772 let excerpt_id = cursor.excerpt_id;
7773
7774 let show_in_menu = self.show_edit_predictions_in_menu();
7775 let completions_menu_has_precedence = !show_in_menu
7776 && (self.context_menu.borrow().is_some()
7777 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7778
7779 if completions_menu_has_precedence
7780 || !offset_selection.is_empty()
7781 || self
7782 .active_edit_prediction
7783 .as_ref()
7784 .is_some_and(|completion| {
7785 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7786 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7787 !invalidation_range.contains(&offset_selection.head())
7788 })
7789 {
7790 self.discard_edit_prediction(false, cx);
7791 return None;
7792 }
7793
7794 self.take_active_edit_prediction(cx);
7795 let Some(provider) = self.edit_prediction_provider() else {
7796 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7797 return None;
7798 };
7799
7800 let (buffer, cursor_buffer_position) =
7801 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7802
7803 self.edit_prediction_settings =
7804 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7805
7806 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7807 self.discard_edit_prediction(false, cx);
7808 return None;
7809 };
7810
7811 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7812
7813 if self.edit_prediction_indent_conflict {
7814 let cursor_point = cursor.to_point(&multibuffer);
7815
7816 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7817
7818 if let Some((_, indent)) = indents.iter().next()
7819 && indent.len == cursor_point.column
7820 {
7821 self.edit_prediction_indent_conflict = false;
7822 }
7823 }
7824
7825 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7826 let edits = edit_prediction
7827 .edits
7828 .into_iter()
7829 .flat_map(|(range, new_text)| {
7830 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7831 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7832 Some((start..end, new_text))
7833 })
7834 .collect::<Vec<_>>();
7835 if edits.is_empty() {
7836 return None;
7837 }
7838
7839 let first_edit_start = edits.first().unwrap().0.start;
7840 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7841 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7842
7843 let last_edit_end = edits.last().unwrap().0.end;
7844 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7845 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7846
7847 let cursor_row = cursor.to_point(&multibuffer).row;
7848
7849 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7850
7851 let mut inlay_ids = Vec::new();
7852 let invalidation_row_range;
7853 let move_invalidation_row_range = if cursor_row < edit_start_row {
7854 Some(cursor_row..edit_end_row)
7855 } else if cursor_row > edit_end_row {
7856 Some(edit_start_row..cursor_row)
7857 } else {
7858 None
7859 };
7860 let supports_jump = self
7861 .edit_prediction_provider
7862 .as_ref()
7863 .map(|provider| provider.provider.supports_jump_to_edit())
7864 .unwrap_or(true);
7865
7866 let is_move = supports_jump
7867 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7868 let completion = if is_move {
7869 invalidation_row_range =
7870 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7871 let target = first_edit_start;
7872 EditPrediction::Move { target, snapshot }
7873 } else {
7874 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7875 && !self.edit_predictions_hidden_for_vim_mode;
7876
7877 if show_completions_in_buffer {
7878 if edits
7879 .iter()
7880 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7881 {
7882 let mut inlays = Vec::new();
7883 for (range, new_text) in &edits {
7884 let inlay = Inlay::edit_prediction(
7885 post_inc(&mut self.next_inlay_id),
7886 range.start,
7887 new_text.as_str(),
7888 );
7889 inlay_ids.push(inlay.id);
7890 inlays.push(inlay);
7891 }
7892
7893 self.splice_inlays(&[], inlays, cx);
7894 } else {
7895 let background_color = cx.theme().status().deleted_background;
7896 self.highlight_text::<EditPredictionHighlight>(
7897 edits.iter().map(|(range, _)| range.clone()).collect(),
7898 HighlightStyle {
7899 background_color: Some(background_color),
7900 ..Default::default()
7901 },
7902 cx,
7903 );
7904 }
7905 }
7906
7907 invalidation_row_range = edit_start_row..edit_end_row;
7908
7909 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7910 if provider.show_tab_accept_marker() {
7911 EditDisplayMode::TabAccept
7912 } else {
7913 EditDisplayMode::Inline
7914 }
7915 } else {
7916 EditDisplayMode::DiffPopover
7917 };
7918
7919 EditPrediction::Edit {
7920 edits,
7921 edit_preview: edit_prediction.edit_preview,
7922 display_mode,
7923 snapshot,
7924 }
7925 };
7926
7927 let invalidation_range = multibuffer
7928 .anchor_before(Point::new(invalidation_row_range.start, 0))
7929 ..multibuffer.anchor_after(Point::new(
7930 invalidation_row_range.end,
7931 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7932 ));
7933
7934 self.stale_edit_prediction_in_menu = None;
7935 self.active_edit_prediction = Some(EditPredictionState {
7936 inlay_ids,
7937 completion,
7938 completion_id: edit_prediction.id,
7939 invalidation_range,
7940 });
7941
7942 cx.notify();
7943
7944 Some(())
7945 }
7946
7947 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7948 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7949 }
7950
7951 fn clear_tasks(&mut self) {
7952 self.tasks.clear()
7953 }
7954
7955 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7956 if self.tasks.insert(key, value).is_some() {
7957 // This case should hopefully be rare, but just in case...
7958 log::error!(
7959 "multiple different run targets found on a single line, only the last target will be rendered"
7960 )
7961 }
7962 }
7963
7964 /// Get all display points of breakpoints that will be rendered within editor
7965 ///
7966 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7967 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7968 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7969 fn active_breakpoints(
7970 &self,
7971 range: Range<DisplayRow>,
7972 window: &mut Window,
7973 cx: &mut Context<Self>,
7974 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7975 let mut breakpoint_display_points = HashMap::default();
7976
7977 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7978 return breakpoint_display_points;
7979 };
7980
7981 let snapshot = self.snapshot(window, cx);
7982
7983 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7984 let Some(project) = self.project() else {
7985 return breakpoint_display_points;
7986 };
7987
7988 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7989 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7990
7991 for (buffer_snapshot, range, excerpt_id) in
7992 multi_buffer_snapshot.range_to_buffer_ranges(range)
7993 {
7994 let Some(buffer) = project
7995 .read(cx)
7996 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7997 else {
7998 continue;
7999 };
8000 let breakpoints = breakpoint_store.read(cx).breakpoints(
8001 &buffer,
8002 Some(
8003 buffer_snapshot.anchor_before(range.start)
8004 ..buffer_snapshot.anchor_after(range.end),
8005 ),
8006 buffer_snapshot,
8007 cx,
8008 );
8009 for (breakpoint, state) in breakpoints {
8010 let multi_buffer_anchor =
8011 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8012 let position = multi_buffer_anchor
8013 .to_point(multi_buffer_snapshot)
8014 .to_display_point(&snapshot);
8015
8016 breakpoint_display_points.insert(
8017 position.row(),
8018 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8019 );
8020 }
8021 }
8022
8023 breakpoint_display_points
8024 }
8025
8026 fn breakpoint_context_menu(
8027 &self,
8028 anchor: Anchor,
8029 window: &mut Window,
8030 cx: &mut Context<Self>,
8031 ) -> Entity<ui::ContextMenu> {
8032 let weak_editor = cx.weak_entity();
8033 let focus_handle = self.focus_handle(cx);
8034
8035 let row = self
8036 .buffer
8037 .read(cx)
8038 .snapshot(cx)
8039 .summary_for_anchor::<Point>(&anchor)
8040 .row;
8041
8042 let breakpoint = self
8043 .breakpoint_at_row(row, window, cx)
8044 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8045
8046 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8047 "Edit Log Breakpoint"
8048 } else {
8049 "Set Log Breakpoint"
8050 };
8051
8052 let condition_breakpoint_msg = if breakpoint
8053 .as_ref()
8054 .is_some_and(|bp| bp.1.condition.is_some())
8055 {
8056 "Edit Condition Breakpoint"
8057 } else {
8058 "Set Condition Breakpoint"
8059 };
8060
8061 let hit_condition_breakpoint_msg = if breakpoint
8062 .as_ref()
8063 .is_some_and(|bp| bp.1.hit_condition.is_some())
8064 {
8065 "Edit Hit Condition Breakpoint"
8066 } else {
8067 "Set Hit Condition Breakpoint"
8068 };
8069
8070 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8071 "Unset Breakpoint"
8072 } else {
8073 "Set Breakpoint"
8074 };
8075
8076 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8077
8078 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8079 BreakpointState::Enabled => Some("Disable"),
8080 BreakpointState::Disabled => Some("Enable"),
8081 });
8082
8083 let (anchor, breakpoint) =
8084 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8085
8086 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8087 menu.on_blur_subscription(Subscription::new(|| {}))
8088 .context(focus_handle)
8089 .when(run_to_cursor, |this| {
8090 let weak_editor = weak_editor.clone();
8091 this.entry("Run to cursor", None, move |window, cx| {
8092 weak_editor
8093 .update(cx, |editor, cx| {
8094 editor.change_selections(
8095 SelectionEffects::no_scroll(),
8096 window,
8097 cx,
8098 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8099 );
8100 })
8101 .ok();
8102
8103 window.dispatch_action(Box::new(RunToCursor), cx);
8104 })
8105 .separator()
8106 })
8107 .when_some(toggle_state_msg, |this, msg| {
8108 this.entry(msg, None, {
8109 let weak_editor = weak_editor.clone();
8110 let breakpoint = breakpoint.clone();
8111 move |_window, cx| {
8112 weak_editor
8113 .update(cx, |this, cx| {
8114 this.edit_breakpoint_at_anchor(
8115 anchor,
8116 breakpoint.as_ref().clone(),
8117 BreakpointEditAction::InvertState,
8118 cx,
8119 );
8120 })
8121 .log_err();
8122 }
8123 })
8124 })
8125 .entry(set_breakpoint_msg, None, {
8126 let weak_editor = weak_editor.clone();
8127 let breakpoint = breakpoint.clone();
8128 move |_window, cx| {
8129 weak_editor
8130 .update(cx, |this, cx| {
8131 this.edit_breakpoint_at_anchor(
8132 anchor,
8133 breakpoint.as_ref().clone(),
8134 BreakpointEditAction::Toggle,
8135 cx,
8136 );
8137 })
8138 .log_err();
8139 }
8140 })
8141 .entry(log_breakpoint_msg, None, {
8142 let breakpoint = breakpoint.clone();
8143 let weak_editor = weak_editor.clone();
8144 move |window, cx| {
8145 weak_editor
8146 .update(cx, |this, cx| {
8147 this.add_edit_breakpoint_block(
8148 anchor,
8149 breakpoint.as_ref(),
8150 BreakpointPromptEditAction::Log,
8151 window,
8152 cx,
8153 );
8154 })
8155 .log_err();
8156 }
8157 })
8158 .entry(condition_breakpoint_msg, None, {
8159 let breakpoint = breakpoint.clone();
8160 let weak_editor = weak_editor.clone();
8161 move |window, cx| {
8162 weak_editor
8163 .update(cx, |this, cx| {
8164 this.add_edit_breakpoint_block(
8165 anchor,
8166 breakpoint.as_ref(),
8167 BreakpointPromptEditAction::Condition,
8168 window,
8169 cx,
8170 );
8171 })
8172 .log_err();
8173 }
8174 })
8175 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8176 weak_editor
8177 .update(cx, |this, cx| {
8178 this.add_edit_breakpoint_block(
8179 anchor,
8180 breakpoint.as_ref(),
8181 BreakpointPromptEditAction::HitCondition,
8182 window,
8183 cx,
8184 );
8185 })
8186 .log_err();
8187 })
8188 })
8189 }
8190
8191 fn render_breakpoint(
8192 &self,
8193 position: Anchor,
8194 row: DisplayRow,
8195 breakpoint: &Breakpoint,
8196 state: Option<BreakpointSessionState>,
8197 cx: &mut Context<Self>,
8198 ) -> IconButton {
8199 let is_rejected = state.is_some_and(|s| !s.verified);
8200 // Is it a breakpoint that shows up when hovering over gutter?
8201 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8202 (false, false),
8203 |PhantomBreakpointIndicator {
8204 is_active,
8205 display_row,
8206 collides_with_existing_breakpoint,
8207 }| {
8208 (
8209 is_active && display_row == row,
8210 collides_with_existing_breakpoint,
8211 )
8212 },
8213 );
8214
8215 let (color, icon) = {
8216 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8217 (false, false) => ui::IconName::DebugBreakpoint,
8218 (true, false) => ui::IconName::DebugLogBreakpoint,
8219 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8220 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8221 };
8222
8223 let color = if is_phantom {
8224 Color::Hint
8225 } else if is_rejected {
8226 Color::Disabled
8227 } else {
8228 Color::Debugger
8229 };
8230
8231 (color, icon)
8232 };
8233
8234 let breakpoint = Arc::from(breakpoint.clone());
8235
8236 let alt_as_text = gpui::Keystroke {
8237 modifiers: Modifiers::secondary_key(),
8238 ..Default::default()
8239 };
8240 let primary_action_text = if breakpoint.is_disabled() {
8241 "Enable breakpoint"
8242 } else if is_phantom && !collides_with_existing {
8243 "Set breakpoint"
8244 } else {
8245 "Unset breakpoint"
8246 };
8247 let focus_handle = self.focus_handle.clone();
8248
8249 let meta = if is_rejected {
8250 SharedString::from("No executable code is associated with this line.")
8251 } else if collides_with_existing && !breakpoint.is_disabled() {
8252 SharedString::from(format!(
8253 "{alt_as_text}-click to disable,\nright-click for more options."
8254 ))
8255 } else {
8256 SharedString::from("Right-click for more options.")
8257 };
8258 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8259 .icon_size(IconSize::XSmall)
8260 .size(ui::ButtonSize::None)
8261 .when(is_rejected, |this| {
8262 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8263 })
8264 .icon_color(color)
8265 .style(ButtonStyle::Transparent)
8266 .on_click(cx.listener({
8267 move |editor, event: &ClickEvent, window, cx| {
8268 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8269 BreakpointEditAction::InvertState
8270 } else {
8271 BreakpointEditAction::Toggle
8272 };
8273
8274 window.focus(&editor.focus_handle(cx));
8275 editor.edit_breakpoint_at_anchor(
8276 position,
8277 breakpoint.as_ref().clone(),
8278 edit_action,
8279 cx,
8280 );
8281 }
8282 }))
8283 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8284 editor.set_breakpoint_context_menu(
8285 row,
8286 Some(position),
8287 event.position(),
8288 window,
8289 cx,
8290 );
8291 }))
8292 .tooltip(move |window, cx| {
8293 Tooltip::with_meta_in(
8294 primary_action_text,
8295 Some(&ToggleBreakpoint),
8296 meta.clone(),
8297 &focus_handle,
8298 window,
8299 cx,
8300 )
8301 })
8302 }
8303
8304 fn build_tasks_context(
8305 project: &Entity<Project>,
8306 buffer: &Entity<Buffer>,
8307 buffer_row: u32,
8308 tasks: &Arc<RunnableTasks>,
8309 cx: &mut Context<Self>,
8310 ) -> Task<Option<task::TaskContext>> {
8311 let position = Point::new(buffer_row, tasks.column);
8312 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8313 let location = Location {
8314 buffer: buffer.clone(),
8315 range: range_start..range_start,
8316 };
8317 // Fill in the environmental variables from the tree-sitter captures
8318 let mut captured_task_variables = TaskVariables::default();
8319 for (capture_name, value) in tasks.extra_variables.clone() {
8320 captured_task_variables.insert(
8321 task::VariableName::Custom(capture_name.into()),
8322 value.clone(),
8323 );
8324 }
8325 project.update(cx, |project, cx| {
8326 project.task_store().update(cx, |task_store, cx| {
8327 task_store.task_context_for_location(captured_task_variables, location, cx)
8328 })
8329 })
8330 }
8331
8332 pub fn spawn_nearest_task(
8333 &mut self,
8334 action: &SpawnNearestTask,
8335 window: &mut Window,
8336 cx: &mut Context<Self>,
8337 ) {
8338 let Some((workspace, _)) = self.workspace.clone() else {
8339 return;
8340 };
8341 let Some(project) = self.project.clone() else {
8342 return;
8343 };
8344
8345 // Try to find a closest, enclosing node using tree-sitter that has a task
8346 let Some((buffer, buffer_row, tasks)) = self
8347 .find_enclosing_node_task(cx)
8348 // Or find the task that's closest in row-distance.
8349 .or_else(|| self.find_closest_task(cx))
8350 else {
8351 return;
8352 };
8353
8354 let reveal_strategy = action.reveal;
8355 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8356 cx.spawn_in(window, async move |_, cx| {
8357 let context = task_context.await?;
8358 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8359
8360 let resolved = &mut resolved_task.resolved;
8361 resolved.reveal = reveal_strategy;
8362
8363 workspace
8364 .update_in(cx, |workspace, window, cx| {
8365 workspace.schedule_resolved_task(
8366 task_source_kind,
8367 resolved_task,
8368 false,
8369 window,
8370 cx,
8371 );
8372 })
8373 .ok()
8374 })
8375 .detach();
8376 }
8377
8378 fn find_closest_task(
8379 &mut self,
8380 cx: &mut Context<Self>,
8381 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8382 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8383
8384 let ((buffer_id, row), tasks) = self
8385 .tasks
8386 .iter()
8387 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8388
8389 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8390 let tasks = Arc::new(tasks.to_owned());
8391 Some((buffer, *row, tasks))
8392 }
8393
8394 fn find_enclosing_node_task(
8395 &mut self,
8396 cx: &mut Context<Self>,
8397 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8398 let snapshot = self.buffer.read(cx).snapshot(cx);
8399 let offset = self.selections.newest::<usize>(cx).head();
8400 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8401 let buffer_id = excerpt.buffer().remote_id();
8402
8403 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8404 let mut cursor = layer.node().walk();
8405
8406 while cursor.goto_first_child_for_byte(offset).is_some() {
8407 if cursor.node().end_byte() == offset {
8408 cursor.goto_next_sibling();
8409 }
8410 }
8411
8412 // Ascend to the smallest ancestor that contains the range and has a task.
8413 loop {
8414 let node = cursor.node();
8415 let node_range = node.byte_range();
8416 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8417
8418 // Check if this node contains our offset
8419 if node_range.start <= offset && node_range.end >= offset {
8420 // If it contains offset, check for task
8421 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8422 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8423 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8424 }
8425 }
8426
8427 if !cursor.goto_parent() {
8428 break;
8429 }
8430 }
8431 None
8432 }
8433
8434 fn render_run_indicator(
8435 &self,
8436 _style: &EditorStyle,
8437 is_active: bool,
8438 row: DisplayRow,
8439 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8440 cx: &mut Context<Self>,
8441 ) -> IconButton {
8442 let color = Color::Muted;
8443 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8444
8445 IconButton::new(
8446 ("run_indicator", row.0 as usize),
8447 ui::IconName::PlayOutlined,
8448 )
8449 .shape(ui::IconButtonShape::Square)
8450 .icon_size(IconSize::XSmall)
8451 .icon_color(color)
8452 .toggle_state(is_active)
8453 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8454 let quick_launch = match e {
8455 ClickEvent::Keyboard(_) => true,
8456 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8457 };
8458
8459 window.focus(&editor.focus_handle(cx));
8460 editor.toggle_code_actions(
8461 &ToggleCodeActions {
8462 deployed_from: Some(CodeActionSource::RunMenu(row)),
8463 quick_launch,
8464 },
8465 window,
8466 cx,
8467 );
8468 }))
8469 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8470 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8471 }))
8472 }
8473
8474 pub fn context_menu_visible(&self) -> bool {
8475 !self.edit_prediction_preview_is_active()
8476 && self
8477 .context_menu
8478 .borrow()
8479 .as_ref()
8480 .is_some_and(|menu| menu.visible())
8481 }
8482
8483 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8484 self.context_menu
8485 .borrow()
8486 .as_ref()
8487 .map(|menu| menu.origin())
8488 }
8489
8490 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8491 self.context_menu_options = Some(options);
8492 }
8493
8494 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8495 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8496
8497 fn render_edit_prediction_popover(
8498 &mut self,
8499 text_bounds: &Bounds<Pixels>,
8500 content_origin: gpui::Point<Pixels>,
8501 right_margin: Pixels,
8502 editor_snapshot: &EditorSnapshot,
8503 visible_row_range: Range<DisplayRow>,
8504 scroll_top: f32,
8505 scroll_bottom: f32,
8506 line_layouts: &[LineWithInvisibles],
8507 line_height: Pixels,
8508 scroll_pixel_position: gpui::Point<Pixels>,
8509 newest_selection_head: Option<DisplayPoint>,
8510 editor_width: Pixels,
8511 style: &EditorStyle,
8512 window: &mut Window,
8513 cx: &mut App,
8514 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8515 if self.mode().is_minimap() {
8516 return None;
8517 }
8518 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8519
8520 if self.edit_prediction_visible_in_cursor_popover(true) {
8521 return None;
8522 }
8523
8524 match &active_edit_prediction.completion {
8525 EditPrediction::Move { target, .. } => {
8526 let target_display_point = target.to_display_point(editor_snapshot);
8527
8528 if self.edit_prediction_requires_modifier() {
8529 if !self.edit_prediction_preview_is_active() {
8530 return None;
8531 }
8532
8533 self.render_edit_prediction_modifier_jump_popover(
8534 text_bounds,
8535 content_origin,
8536 visible_row_range,
8537 line_layouts,
8538 line_height,
8539 scroll_pixel_position,
8540 newest_selection_head,
8541 target_display_point,
8542 window,
8543 cx,
8544 )
8545 } else {
8546 self.render_edit_prediction_eager_jump_popover(
8547 text_bounds,
8548 content_origin,
8549 editor_snapshot,
8550 visible_row_range,
8551 scroll_top,
8552 scroll_bottom,
8553 line_height,
8554 scroll_pixel_position,
8555 target_display_point,
8556 editor_width,
8557 window,
8558 cx,
8559 )
8560 }
8561 }
8562 EditPrediction::Edit {
8563 display_mode: EditDisplayMode::Inline,
8564 ..
8565 } => None,
8566 EditPrediction::Edit {
8567 display_mode: EditDisplayMode::TabAccept,
8568 edits,
8569 ..
8570 } => {
8571 let range = &edits.first()?.0;
8572 let target_display_point = range.end.to_display_point(editor_snapshot);
8573
8574 self.render_edit_prediction_end_of_line_popover(
8575 "Accept",
8576 editor_snapshot,
8577 visible_row_range,
8578 target_display_point,
8579 line_height,
8580 scroll_pixel_position,
8581 content_origin,
8582 editor_width,
8583 window,
8584 cx,
8585 )
8586 }
8587 EditPrediction::Edit {
8588 edits,
8589 edit_preview,
8590 display_mode: EditDisplayMode::DiffPopover,
8591 snapshot,
8592 } => self.render_edit_prediction_diff_popover(
8593 text_bounds,
8594 content_origin,
8595 right_margin,
8596 editor_snapshot,
8597 visible_row_range,
8598 line_layouts,
8599 line_height,
8600 scroll_pixel_position,
8601 newest_selection_head,
8602 editor_width,
8603 style,
8604 edits,
8605 edit_preview,
8606 snapshot,
8607 window,
8608 cx,
8609 ),
8610 }
8611 }
8612
8613 fn render_edit_prediction_modifier_jump_popover(
8614 &mut self,
8615 text_bounds: &Bounds<Pixels>,
8616 content_origin: gpui::Point<Pixels>,
8617 visible_row_range: Range<DisplayRow>,
8618 line_layouts: &[LineWithInvisibles],
8619 line_height: Pixels,
8620 scroll_pixel_position: gpui::Point<Pixels>,
8621 newest_selection_head: Option<DisplayPoint>,
8622 target_display_point: DisplayPoint,
8623 window: &mut Window,
8624 cx: &mut App,
8625 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8626 let scrolled_content_origin =
8627 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8628
8629 const SCROLL_PADDING_Y: Pixels = px(12.);
8630
8631 if target_display_point.row() < visible_row_range.start {
8632 return self.render_edit_prediction_scroll_popover(
8633 |_| SCROLL_PADDING_Y,
8634 IconName::ArrowUp,
8635 visible_row_range,
8636 line_layouts,
8637 newest_selection_head,
8638 scrolled_content_origin,
8639 window,
8640 cx,
8641 );
8642 } else if target_display_point.row() >= visible_row_range.end {
8643 return self.render_edit_prediction_scroll_popover(
8644 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8645 IconName::ArrowDown,
8646 visible_row_range,
8647 line_layouts,
8648 newest_selection_head,
8649 scrolled_content_origin,
8650 window,
8651 cx,
8652 );
8653 }
8654
8655 const POLE_WIDTH: Pixels = px(2.);
8656
8657 let line_layout =
8658 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8659 let target_column = target_display_point.column() as usize;
8660
8661 let target_x = line_layout.x_for_index(target_column);
8662 let target_y =
8663 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8664
8665 let flag_on_right = target_x < text_bounds.size.width / 2.;
8666
8667 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8668 border_color.l += 0.001;
8669
8670 let mut element = v_flex()
8671 .items_end()
8672 .when(flag_on_right, |el| el.items_start())
8673 .child(if flag_on_right {
8674 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8675 .rounded_bl(px(0.))
8676 .rounded_tl(px(0.))
8677 .border_l_2()
8678 .border_color(border_color)
8679 } else {
8680 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8681 .rounded_br(px(0.))
8682 .rounded_tr(px(0.))
8683 .border_r_2()
8684 .border_color(border_color)
8685 })
8686 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8687 .into_any();
8688
8689 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8690
8691 let mut origin = scrolled_content_origin + point(target_x, target_y)
8692 - point(
8693 if flag_on_right {
8694 POLE_WIDTH
8695 } else {
8696 size.width - POLE_WIDTH
8697 },
8698 size.height - line_height,
8699 );
8700
8701 origin.x = origin.x.max(content_origin.x);
8702
8703 element.prepaint_at(origin, window, cx);
8704
8705 Some((element, origin))
8706 }
8707
8708 fn render_edit_prediction_scroll_popover(
8709 &mut self,
8710 to_y: impl Fn(Size<Pixels>) -> Pixels,
8711 scroll_icon: IconName,
8712 visible_row_range: Range<DisplayRow>,
8713 line_layouts: &[LineWithInvisibles],
8714 newest_selection_head: Option<DisplayPoint>,
8715 scrolled_content_origin: gpui::Point<Pixels>,
8716 window: &mut Window,
8717 cx: &mut App,
8718 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8719 let mut element = self
8720 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8721 .into_any();
8722
8723 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8724
8725 let cursor = newest_selection_head?;
8726 let cursor_row_layout =
8727 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8728 let cursor_column = cursor.column() as usize;
8729
8730 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8731
8732 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8733
8734 element.prepaint_at(origin, window, cx);
8735 Some((element, origin))
8736 }
8737
8738 fn render_edit_prediction_eager_jump_popover(
8739 &mut self,
8740 text_bounds: &Bounds<Pixels>,
8741 content_origin: gpui::Point<Pixels>,
8742 editor_snapshot: &EditorSnapshot,
8743 visible_row_range: Range<DisplayRow>,
8744 scroll_top: f32,
8745 scroll_bottom: f32,
8746 line_height: Pixels,
8747 scroll_pixel_position: gpui::Point<Pixels>,
8748 target_display_point: DisplayPoint,
8749 editor_width: Pixels,
8750 window: &mut Window,
8751 cx: &mut App,
8752 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8753 if target_display_point.row().as_f32() < scroll_top {
8754 let mut element = self
8755 .render_edit_prediction_line_popover(
8756 "Jump to Edit",
8757 Some(IconName::ArrowUp),
8758 window,
8759 cx,
8760 )?
8761 .into_any();
8762
8763 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8764 let offset = point(
8765 (text_bounds.size.width - size.width) / 2.,
8766 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8767 );
8768
8769 let origin = text_bounds.origin + offset;
8770 element.prepaint_at(origin, window, cx);
8771 Some((element, origin))
8772 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8773 let mut element = self
8774 .render_edit_prediction_line_popover(
8775 "Jump to Edit",
8776 Some(IconName::ArrowDown),
8777 window,
8778 cx,
8779 )?
8780 .into_any();
8781
8782 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8783 let offset = point(
8784 (text_bounds.size.width - size.width) / 2.,
8785 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8786 );
8787
8788 let origin = text_bounds.origin + offset;
8789 element.prepaint_at(origin, window, cx);
8790 Some((element, origin))
8791 } else {
8792 self.render_edit_prediction_end_of_line_popover(
8793 "Jump to Edit",
8794 editor_snapshot,
8795 visible_row_range,
8796 target_display_point,
8797 line_height,
8798 scroll_pixel_position,
8799 content_origin,
8800 editor_width,
8801 window,
8802 cx,
8803 )
8804 }
8805 }
8806
8807 fn render_edit_prediction_end_of_line_popover(
8808 self: &mut Editor,
8809 label: &'static str,
8810 editor_snapshot: &EditorSnapshot,
8811 visible_row_range: Range<DisplayRow>,
8812 target_display_point: DisplayPoint,
8813 line_height: Pixels,
8814 scroll_pixel_position: gpui::Point<Pixels>,
8815 content_origin: gpui::Point<Pixels>,
8816 editor_width: Pixels,
8817 window: &mut Window,
8818 cx: &mut App,
8819 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8820 let target_line_end = DisplayPoint::new(
8821 target_display_point.row(),
8822 editor_snapshot.line_len(target_display_point.row()),
8823 );
8824
8825 let mut element = self
8826 .render_edit_prediction_line_popover(label, None, window, cx)?
8827 .into_any();
8828
8829 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8830
8831 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8832
8833 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8834 let mut origin = start_point
8835 + line_origin
8836 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8837 origin.x = origin.x.max(content_origin.x);
8838
8839 let max_x = content_origin.x + editor_width - size.width;
8840
8841 if origin.x > max_x {
8842 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8843
8844 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8845 origin.y += offset;
8846 IconName::ArrowUp
8847 } else {
8848 origin.y -= offset;
8849 IconName::ArrowDown
8850 };
8851
8852 element = self
8853 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8854 .into_any();
8855
8856 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8857
8858 origin.x = content_origin.x + editor_width - size.width - px(2.);
8859 }
8860
8861 element.prepaint_at(origin, window, cx);
8862 Some((element, origin))
8863 }
8864
8865 fn render_edit_prediction_diff_popover(
8866 self: &Editor,
8867 text_bounds: &Bounds<Pixels>,
8868 content_origin: gpui::Point<Pixels>,
8869 right_margin: Pixels,
8870 editor_snapshot: &EditorSnapshot,
8871 visible_row_range: Range<DisplayRow>,
8872 line_layouts: &[LineWithInvisibles],
8873 line_height: Pixels,
8874 scroll_pixel_position: gpui::Point<Pixels>,
8875 newest_selection_head: Option<DisplayPoint>,
8876 editor_width: Pixels,
8877 style: &EditorStyle,
8878 edits: &Vec<(Range<Anchor>, String)>,
8879 edit_preview: &Option<language::EditPreview>,
8880 snapshot: &language::BufferSnapshot,
8881 window: &mut Window,
8882 cx: &mut App,
8883 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8884 let edit_start = edits
8885 .first()
8886 .unwrap()
8887 .0
8888 .start
8889 .to_display_point(editor_snapshot);
8890 let edit_end = edits
8891 .last()
8892 .unwrap()
8893 .0
8894 .end
8895 .to_display_point(editor_snapshot);
8896
8897 let is_visible = visible_row_range.contains(&edit_start.row())
8898 || visible_row_range.contains(&edit_end.row());
8899 if !is_visible {
8900 return None;
8901 }
8902
8903 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8904 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8905 } else {
8906 // Fallback for providers without edit_preview
8907 crate::edit_prediction_fallback_text(edits, cx)
8908 };
8909
8910 let styled_text = highlighted_edits.to_styled_text(&style.text);
8911 let line_count = highlighted_edits.text.lines().count();
8912
8913 const BORDER_WIDTH: Pixels = px(1.);
8914
8915 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8916 let has_keybind = keybind.is_some();
8917
8918 let mut element = h_flex()
8919 .items_start()
8920 .child(
8921 h_flex()
8922 .bg(cx.theme().colors().editor_background)
8923 .border(BORDER_WIDTH)
8924 .shadow_xs()
8925 .border_color(cx.theme().colors().border)
8926 .rounded_l_lg()
8927 .when(line_count > 1, |el| el.rounded_br_lg())
8928 .pr_1()
8929 .child(styled_text),
8930 )
8931 .child(
8932 h_flex()
8933 .h(line_height + BORDER_WIDTH * 2.)
8934 .px_1p5()
8935 .gap_1()
8936 // Workaround: For some reason, there's a gap if we don't do this
8937 .ml(-BORDER_WIDTH)
8938 .shadow(vec![gpui::BoxShadow {
8939 color: gpui::black().opacity(0.05),
8940 offset: point(px(1.), px(1.)),
8941 blur_radius: px(2.),
8942 spread_radius: px(0.),
8943 }])
8944 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8945 .border(BORDER_WIDTH)
8946 .border_color(cx.theme().colors().border)
8947 .rounded_r_lg()
8948 .id("edit_prediction_diff_popover_keybind")
8949 .when(!has_keybind, |el| {
8950 let status_colors = cx.theme().status();
8951
8952 el.bg(status_colors.error_background)
8953 .border_color(status_colors.error.opacity(0.6))
8954 .child(Icon::new(IconName::Info).color(Color::Error))
8955 .cursor_default()
8956 .hoverable_tooltip(move |_window, cx| {
8957 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8958 })
8959 })
8960 .children(keybind),
8961 )
8962 .into_any();
8963
8964 let longest_row =
8965 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8966 let longest_line_width = if visible_row_range.contains(&longest_row) {
8967 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8968 } else {
8969 layout_line(
8970 longest_row,
8971 editor_snapshot,
8972 style,
8973 editor_width,
8974 |_| false,
8975 window,
8976 cx,
8977 )
8978 .width
8979 };
8980
8981 let viewport_bounds =
8982 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8983 right: -right_margin,
8984 ..Default::default()
8985 });
8986
8987 let x_after_longest =
8988 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8989 - scroll_pixel_position.x;
8990
8991 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8992
8993 // Fully visible if it can be displayed within the window (allow overlapping other
8994 // panes). However, this is only allowed if the popover starts within text_bounds.
8995 let can_position_to_the_right = x_after_longest < text_bounds.right()
8996 && x_after_longest + element_bounds.width < viewport_bounds.right();
8997
8998 let mut origin = if can_position_to_the_right {
8999 point(
9000 x_after_longest,
9001 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9002 - scroll_pixel_position.y,
9003 )
9004 } else {
9005 let cursor_row = newest_selection_head.map(|head| head.row());
9006 let above_edit = edit_start
9007 .row()
9008 .0
9009 .checked_sub(line_count as u32)
9010 .map(DisplayRow);
9011 let below_edit = Some(edit_end.row() + 1);
9012 let above_cursor =
9013 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9014 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9015
9016 // Place the edit popover adjacent to the edit if there is a location
9017 // available that is onscreen and does not obscure the cursor. Otherwise,
9018 // place it adjacent to the cursor.
9019 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9020 .into_iter()
9021 .flatten()
9022 .find(|&start_row| {
9023 let end_row = start_row + line_count as u32;
9024 visible_row_range.contains(&start_row)
9025 && visible_row_range.contains(&end_row)
9026 && cursor_row
9027 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9028 })?;
9029
9030 content_origin
9031 + point(
9032 -scroll_pixel_position.x,
9033 row_target.as_f32() * line_height - scroll_pixel_position.y,
9034 )
9035 };
9036
9037 origin.x -= BORDER_WIDTH;
9038
9039 window.defer_draw(element, origin, 1);
9040
9041 // Do not return an element, since it will already be drawn due to defer_draw.
9042 None
9043 }
9044
9045 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9046 px(30.)
9047 }
9048
9049 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9050 if self.read_only(cx) {
9051 cx.theme().players().read_only()
9052 } else {
9053 self.style.as_ref().unwrap().local_player
9054 }
9055 }
9056
9057 fn render_edit_prediction_accept_keybind(
9058 &self,
9059 window: &mut Window,
9060 cx: &App,
9061 ) -> Option<AnyElement> {
9062 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9063 let accept_keystroke = accept_binding.keystroke()?;
9064
9065 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9066
9067 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9068 Color::Accent
9069 } else {
9070 Color::Muted
9071 };
9072
9073 h_flex()
9074 .px_0p5()
9075 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9076 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9077 .text_size(TextSize::XSmall.rems(cx))
9078 .child(h_flex().children(ui::render_modifiers(
9079 accept_keystroke.modifiers(),
9080 PlatformStyle::platform(),
9081 Some(modifiers_color),
9082 Some(IconSize::XSmall.rems().into()),
9083 true,
9084 )))
9085 .when(is_platform_style_mac, |parent| {
9086 parent.child(accept_keystroke.key().to_string())
9087 })
9088 .when(!is_platform_style_mac, |parent| {
9089 parent.child(
9090 Key::new(
9091 util::capitalize(accept_keystroke.key()),
9092 Some(Color::Default),
9093 )
9094 .size(Some(IconSize::XSmall.rems().into())),
9095 )
9096 })
9097 .into_any()
9098 .into()
9099 }
9100
9101 fn render_edit_prediction_line_popover(
9102 &self,
9103 label: impl Into<SharedString>,
9104 icon: Option<IconName>,
9105 window: &mut Window,
9106 cx: &App,
9107 ) -> Option<Stateful<Div>> {
9108 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9109
9110 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9111 let has_keybind = keybind.is_some();
9112
9113 let result = h_flex()
9114 .id("ep-line-popover")
9115 .py_0p5()
9116 .pl_1()
9117 .pr(padding_right)
9118 .gap_1()
9119 .rounded_md()
9120 .border_1()
9121 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9122 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9123 .shadow_xs()
9124 .when(!has_keybind, |el| {
9125 let status_colors = cx.theme().status();
9126
9127 el.bg(status_colors.error_background)
9128 .border_color(status_colors.error.opacity(0.6))
9129 .pl_2()
9130 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9131 .cursor_default()
9132 .hoverable_tooltip(move |_window, cx| {
9133 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9134 })
9135 })
9136 .children(keybind)
9137 .child(
9138 Label::new(label)
9139 .size(LabelSize::Small)
9140 .when(!has_keybind, |el| {
9141 el.color(cx.theme().status().error.into()).strikethrough()
9142 }),
9143 )
9144 .when(!has_keybind, |el| {
9145 el.child(
9146 h_flex().ml_1().child(
9147 Icon::new(IconName::Info)
9148 .size(IconSize::Small)
9149 .color(cx.theme().status().error.into()),
9150 ),
9151 )
9152 })
9153 .when_some(icon, |element, icon| {
9154 element.child(
9155 div()
9156 .mt(px(1.5))
9157 .child(Icon::new(icon).size(IconSize::Small)),
9158 )
9159 });
9160
9161 Some(result)
9162 }
9163
9164 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9165 let accent_color = cx.theme().colors().text_accent;
9166 let editor_bg_color = cx.theme().colors().editor_background;
9167 editor_bg_color.blend(accent_color.opacity(0.1))
9168 }
9169
9170 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9171 let accent_color = cx.theme().colors().text_accent;
9172 let editor_bg_color = cx.theme().colors().editor_background;
9173 editor_bg_color.blend(accent_color.opacity(0.6))
9174 }
9175 fn get_prediction_provider_icon_name(
9176 provider: &Option<RegisteredEditPredictionProvider>,
9177 ) -> IconName {
9178 match provider {
9179 Some(provider) => match provider.provider.name() {
9180 "copilot" => IconName::Copilot,
9181 "supermaven" => IconName::Supermaven,
9182 _ => IconName::ZedPredict,
9183 },
9184 None => IconName::ZedPredict,
9185 }
9186 }
9187
9188 fn render_edit_prediction_cursor_popover(
9189 &self,
9190 min_width: Pixels,
9191 max_width: Pixels,
9192 cursor_point: Point,
9193 style: &EditorStyle,
9194 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9195 _window: &Window,
9196 cx: &mut Context<Editor>,
9197 ) -> Option<AnyElement> {
9198 let provider = self.edit_prediction_provider.as_ref()?;
9199 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9200
9201 let is_refreshing = provider.provider.is_refreshing(cx);
9202
9203 fn pending_completion_container(icon: IconName) -> Div {
9204 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9205 }
9206
9207 let completion = match &self.active_edit_prediction {
9208 Some(prediction) => {
9209 if !self.has_visible_completions_menu() {
9210 const RADIUS: Pixels = px(6.);
9211 const BORDER_WIDTH: Pixels = px(1.);
9212
9213 return Some(
9214 h_flex()
9215 .elevation_2(cx)
9216 .border(BORDER_WIDTH)
9217 .border_color(cx.theme().colors().border)
9218 .when(accept_keystroke.is_none(), |el| {
9219 el.border_color(cx.theme().status().error)
9220 })
9221 .rounded(RADIUS)
9222 .rounded_tl(px(0.))
9223 .overflow_hidden()
9224 .child(div().px_1p5().child(match &prediction.completion {
9225 EditPrediction::Move { target, snapshot } => {
9226 use text::ToPoint as _;
9227 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9228 {
9229 Icon::new(IconName::ZedPredictDown)
9230 } else {
9231 Icon::new(IconName::ZedPredictUp)
9232 }
9233 }
9234 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9235 }))
9236 .child(
9237 h_flex()
9238 .gap_1()
9239 .py_1()
9240 .px_2()
9241 .rounded_r(RADIUS - BORDER_WIDTH)
9242 .border_l_1()
9243 .border_color(cx.theme().colors().border)
9244 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9245 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9246 el.child(
9247 Label::new("Hold")
9248 .size(LabelSize::Small)
9249 .when(accept_keystroke.is_none(), |el| {
9250 el.strikethrough()
9251 })
9252 .line_height_style(LineHeightStyle::UiLabel),
9253 )
9254 })
9255 .id("edit_prediction_cursor_popover_keybind")
9256 .when(accept_keystroke.is_none(), |el| {
9257 let status_colors = cx.theme().status();
9258
9259 el.bg(status_colors.error_background)
9260 .border_color(status_colors.error.opacity(0.6))
9261 .child(Icon::new(IconName::Info).color(Color::Error))
9262 .cursor_default()
9263 .hoverable_tooltip(move |_window, cx| {
9264 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9265 .into()
9266 })
9267 })
9268 .when_some(
9269 accept_keystroke.as_ref(),
9270 |el, accept_keystroke| {
9271 el.child(h_flex().children(ui::render_modifiers(
9272 accept_keystroke.modifiers(),
9273 PlatformStyle::platform(),
9274 Some(Color::Default),
9275 Some(IconSize::XSmall.rems().into()),
9276 false,
9277 )))
9278 },
9279 ),
9280 )
9281 .into_any(),
9282 );
9283 }
9284
9285 self.render_edit_prediction_cursor_popover_preview(
9286 prediction,
9287 cursor_point,
9288 style,
9289 cx,
9290 )?
9291 }
9292
9293 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9294 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9295 stale_completion,
9296 cursor_point,
9297 style,
9298 cx,
9299 )?,
9300
9301 None => pending_completion_container(provider_icon)
9302 .child(Label::new("...").size(LabelSize::Small)),
9303 },
9304
9305 None => pending_completion_container(provider_icon)
9306 .child(Label::new("...").size(LabelSize::Small)),
9307 };
9308
9309 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9310 completion
9311 .with_animation(
9312 "loading-completion",
9313 Animation::new(Duration::from_secs(2))
9314 .repeat()
9315 .with_easing(pulsating_between(0.4, 0.8)),
9316 |label, delta| label.opacity(delta),
9317 )
9318 .into_any_element()
9319 } else {
9320 completion.into_any_element()
9321 };
9322
9323 let has_completion = self.active_edit_prediction.is_some();
9324
9325 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9326 Some(
9327 h_flex()
9328 .min_w(min_width)
9329 .max_w(max_width)
9330 .flex_1()
9331 .elevation_2(cx)
9332 .border_color(cx.theme().colors().border)
9333 .child(
9334 div()
9335 .flex_1()
9336 .py_1()
9337 .px_2()
9338 .overflow_hidden()
9339 .child(completion),
9340 )
9341 .when_some(accept_keystroke, |el, accept_keystroke| {
9342 if !accept_keystroke.modifiers().modified() {
9343 return el;
9344 }
9345
9346 el.child(
9347 h_flex()
9348 .h_full()
9349 .border_l_1()
9350 .rounded_r_lg()
9351 .border_color(cx.theme().colors().border)
9352 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9353 .gap_1()
9354 .py_1()
9355 .px_2()
9356 .child(
9357 h_flex()
9358 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9359 .when(is_platform_style_mac, |parent| parent.gap_1())
9360 .child(h_flex().children(ui::render_modifiers(
9361 accept_keystroke.modifiers(),
9362 PlatformStyle::platform(),
9363 Some(if !has_completion {
9364 Color::Muted
9365 } else {
9366 Color::Default
9367 }),
9368 None,
9369 false,
9370 ))),
9371 )
9372 .child(Label::new("Preview").into_any_element())
9373 .opacity(if has_completion { 1.0 } else { 0.4 }),
9374 )
9375 })
9376 .into_any(),
9377 )
9378 }
9379
9380 fn render_edit_prediction_cursor_popover_preview(
9381 &self,
9382 completion: &EditPredictionState,
9383 cursor_point: Point,
9384 style: &EditorStyle,
9385 cx: &mut Context<Editor>,
9386 ) -> Option<Div> {
9387 use text::ToPoint as _;
9388
9389 fn render_relative_row_jump(
9390 prefix: impl Into<String>,
9391 current_row: u32,
9392 target_row: u32,
9393 ) -> Div {
9394 let (row_diff, arrow) = if target_row < current_row {
9395 (current_row - target_row, IconName::ArrowUp)
9396 } else {
9397 (target_row - current_row, IconName::ArrowDown)
9398 };
9399
9400 h_flex()
9401 .child(
9402 Label::new(format!("{}{}", prefix.into(), row_diff))
9403 .color(Color::Muted)
9404 .size(LabelSize::Small),
9405 )
9406 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9407 }
9408
9409 let supports_jump = self
9410 .edit_prediction_provider
9411 .as_ref()
9412 .map(|provider| provider.provider.supports_jump_to_edit())
9413 .unwrap_or(true);
9414
9415 match &completion.completion {
9416 EditPrediction::Move {
9417 target, snapshot, ..
9418 } => {
9419 if !supports_jump {
9420 return None;
9421 }
9422
9423 Some(
9424 h_flex()
9425 .px_2()
9426 .gap_2()
9427 .flex_1()
9428 .child(
9429 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9430 Icon::new(IconName::ZedPredictDown)
9431 } else {
9432 Icon::new(IconName::ZedPredictUp)
9433 },
9434 )
9435 .child(Label::new("Jump to Edit")),
9436 )
9437 }
9438
9439 EditPrediction::Edit {
9440 edits,
9441 edit_preview,
9442 snapshot,
9443 display_mode: _,
9444 } => {
9445 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9446
9447 let (highlighted_edits, has_more_lines) =
9448 if let Some(edit_preview) = edit_preview.as_ref() {
9449 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9450 .first_line_preview()
9451 } else {
9452 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9453 };
9454
9455 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9456 .with_default_highlights(&style.text, highlighted_edits.highlights);
9457
9458 let preview = h_flex()
9459 .gap_1()
9460 .min_w_16()
9461 .child(styled_text)
9462 .when(has_more_lines, |parent| parent.child("…"));
9463
9464 let left = if supports_jump && first_edit_row != cursor_point.row {
9465 render_relative_row_jump("", cursor_point.row, first_edit_row)
9466 .into_any_element()
9467 } else {
9468 let icon_name =
9469 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9470 Icon::new(icon_name).into_any_element()
9471 };
9472
9473 Some(
9474 h_flex()
9475 .h_full()
9476 .flex_1()
9477 .gap_2()
9478 .pr_1()
9479 .overflow_x_hidden()
9480 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9481 .child(left)
9482 .child(preview),
9483 )
9484 }
9485 }
9486 }
9487
9488 pub fn render_context_menu(
9489 &self,
9490 style: &EditorStyle,
9491 max_height_in_lines: u32,
9492 window: &mut Window,
9493 cx: &mut Context<Editor>,
9494 ) -> Option<AnyElement> {
9495 let menu = self.context_menu.borrow();
9496 let menu = menu.as_ref()?;
9497 if !menu.visible() {
9498 return None;
9499 };
9500 Some(menu.render(style, max_height_in_lines, window, cx))
9501 }
9502
9503 fn render_context_menu_aside(
9504 &mut self,
9505 max_size: Size<Pixels>,
9506 window: &mut Window,
9507 cx: &mut Context<Editor>,
9508 ) -> Option<AnyElement> {
9509 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9510 if menu.visible() {
9511 menu.render_aside(max_size, window, cx)
9512 } else {
9513 None
9514 }
9515 })
9516 }
9517
9518 fn hide_context_menu(
9519 &mut self,
9520 window: &mut Window,
9521 cx: &mut Context<Self>,
9522 ) -> Option<CodeContextMenu> {
9523 cx.notify();
9524 self.completion_tasks.clear();
9525 let context_menu = self.context_menu.borrow_mut().take();
9526 self.stale_edit_prediction_in_menu.take();
9527 self.update_visible_edit_prediction(window, cx);
9528 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9529 && let Some(completion_provider) = &self.completion_provider
9530 {
9531 completion_provider.selection_changed(None, window, cx);
9532 }
9533 context_menu
9534 }
9535
9536 fn show_snippet_choices(
9537 &mut self,
9538 choices: &Vec<String>,
9539 selection: Range<Anchor>,
9540 cx: &mut Context<Self>,
9541 ) {
9542 let Some((_, buffer, _)) = self
9543 .buffer()
9544 .read(cx)
9545 .excerpt_containing(selection.start, cx)
9546 else {
9547 return;
9548 };
9549 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9550 else {
9551 return;
9552 };
9553 if buffer != end_buffer {
9554 log::error!("expected anchor range to have matching buffer IDs");
9555 return;
9556 }
9557
9558 let id = post_inc(&mut self.next_completion_id);
9559 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9560 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9561 CompletionsMenu::new_snippet_choices(
9562 id,
9563 true,
9564 choices,
9565 selection,
9566 buffer,
9567 snippet_sort_order,
9568 ),
9569 ));
9570 }
9571
9572 pub fn insert_snippet(
9573 &mut self,
9574 insertion_ranges: &[Range<usize>],
9575 snippet: Snippet,
9576 window: &mut Window,
9577 cx: &mut Context<Self>,
9578 ) -> Result<()> {
9579 struct Tabstop<T> {
9580 is_end_tabstop: bool,
9581 ranges: Vec<Range<T>>,
9582 choices: Option<Vec<String>>,
9583 }
9584
9585 let tabstops = self.buffer.update(cx, |buffer, cx| {
9586 let snippet_text: Arc<str> = snippet.text.clone().into();
9587 let edits = insertion_ranges
9588 .iter()
9589 .cloned()
9590 .map(|range| (range, snippet_text.clone()));
9591 let autoindent_mode = AutoindentMode::Block {
9592 original_indent_columns: Vec::new(),
9593 };
9594 buffer.edit(edits, Some(autoindent_mode), cx);
9595
9596 let snapshot = &*buffer.read(cx);
9597 let snippet = &snippet;
9598 snippet
9599 .tabstops
9600 .iter()
9601 .map(|tabstop| {
9602 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9603 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9604 });
9605 let mut tabstop_ranges = tabstop
9606 .ranges
9607 .iter()
9608 .flat_map(|tabstop_range| {
9609 let mut delta = 0_isize;
9610 insertion_ranges.iter().map(move |insertion_range| {
9611 let insertion_start = insertion_range.start as isize + delta;
9612 delta +=
9613 snippet.text.len() as isize - insertion_range.len() as isize;
9614
9615 let start = ((insertion_start + tabstop_range.start) as usize)
9616 .min(snapshot.len());
9617 let end = ((insertion_start + tabstop_range.end) as usize)
9618 .min(snapshot.len());
9619 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9620 })
9621 })
9622 .collect::<Vec<_>>();
9623 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9624
9625 Tabstop {
9626 is_end_tabstop,
9627 ranges: tabstop_ranges,
9628 choices: tabstop.choices.clone(),
9629 }
9630 })
9631 .collect::<Vec<_>>()
9632 });
9633 if let Some(tabstop) = tabstops.first() {
9634 self.change_selections(Default::default(), window, cx, |s| {
9635 // Reverse order so that the first range is the newest created selection.
9636 // Completions will use it and autoscroll will prioritize it.
9637 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9638 });
9639
9640 if let Some(choices) = &tabstop.choices
9641 && let Some(selection) = tabstop.ranges.first()
9642 {
9643 self.show_snippet_choices(choices, selection.clone(), cx)
9644 }
9645
9646 // If we're already at the last tabstop and it's at the end of the snippet,
9647 // we're done, we don't need to keep the state around.
9648 if !tabstop.is_end_tabstop {
9649 let choices = tabstops
9650 .iter()
9651 .map(|tabstop| tabstop.choices.clone())
9652 .collect();
9653
9654 let ranges = tabstops
9655 .into_iter()
9656 .map(|tabstop| tabstop.ranges)
9657 .collect::<Vec<_>>();
9658
9659 self.snippet_stack.push(SnippetState {
9660 active_index: 0,
9661 ranges,
9662 choices,
9663 });
9664 }
9665
9666 // Check whether the just-entered snippet ends with an auto-closable bracket.
9667 if self.autoclose_regions.is_empty() {
9668 let snapshot = self.buffer.read(cx).snapshot(cx);
9669 let mut all_selections = self.selections.all::<Point>(cx);
9670 for selection in &mut all_selections {
9671 let selection_head = selection.head();
9672 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9673 continue;
9674 };
9675
9676 let mut bracket_pair = None;
9677 let max_lookup_length = scope
9678 .brackets()
9679 .map(|(pair, _)| {
9680 pair.start
9681 .as_str()
9682 .chars()
9683 .count()
9684 .max(pair.end.as_str().chars().count())
9685 })
9686 .max();
9687 if let Some(max_lookup_length) = max_lookup_length {
9688 let next_text = snapshot
9689 .chars_at(selection_head)
9690 .take(max_lookup_length)
9691 .collect::<String>();
9692 let prev_text = snapshot
9693 .reversed_chars_at(selection_head)
9694 .take(max_lookup_length)
9695 .collect::<String>();
9696
9697 for (pair, enabled) in scope.brackets() {
9698 if enabled
9699 && pair.close
9700 && prev_text.starts_with(pair.start.as_str())
9701 && next_text.starts_with(pair.end.as_str())
9702 {
9703 bracket_pair = Some(pair.clone());
9704 break;
9705 }
9706 }
9707 }
9708
9709 if let Some(pair) = bracket_pair {
9710 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9711 let autoclose_enabled =
9712 self.use_autoclose && snapshot_settings.use_autoclose;
9713 if autoclose_enabled {
9714 let start = snapshot.anchor_after(selection_head);
9715 let end = snapshot.anchor_after(selection_head);
9716 self.autoclose_regions.push(AutocloseRegion {
9717 selection_id: selection.id,
9718 range: start..end,
9719 pair,
9720 });
9721 }
9722 }
9723 }
9724 }
9725 }
9726 Ok(())
9727 }
9728
9729 pub fn move_to_next_snippet_tabstop(
9730 &mut self,
9731 window: &mut Window,
9732 cx: &mut Context<Self>,
9733 ) -> bool {
9734 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9735 }
9736
9737 pub fn move_to_prev_snippet_tabstop(
9738 &mut self,
9739 window: &mut Window,
9740 cx: &mut Context<Self>,
9741 ) -> bool {
9742 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9743 }
9744
9745 pub fn move_to_snippet_tabstop(
9746 &mut self,
9747 bias: Bias,
9748 window: &mut Window,
9749 cx: &mut Context<Self>,
9750 ) -> bool {
9751 if let Some(mut snippet) = self.snippet_stack.pop() {
9752 match bias {
9753 Bias::Left => {
9754 if snippet.active_index > 0 {
9755 snippet.active_index -= 1;
9756 } else {
9757 self.snippet_stack.push(snippet);
9758 return false;
9759 }
9760 }
9761 Bias::Right => {
9762 if snippet.active_index + 1 < snippet.ranges.len() {
9763 snippet.active_index += 1;
9764 } else {
9765 self.snippet_stack.push(snippet);
9766 return false;
9767 }
9768 }
9769 }
9770 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9771 self.change_selections(Default::default(), window, cx, |s| {
9772 // Reverse order so that the first range is the newest created selection.
9773 // Completions will use it and autoscroll will prioritize it.
9774 s.select_ranges(current_ranges.iter().rev().cloned())
9775 });
9776
9777 if let Some(choices) = &snippet.choices[snippet.active_index]
9778 && let Some(selection) = current_ranges.first()
9779 {
9780 self.show_snippet_choices(choices, selection.clone(), cx);
9781 }
9782
9783 // If snippet state is not at the last tabstop, push it back on the stack
9784 if snippet.active_index + 1 < snippet.ranges.len() {
9785 self.snippet_stack.push(snippet);
9786 }
9787 return true;
9788 }
9789 }
9790
9791 false
9792 }
9793
9794 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9795 self.transact(window, cx, |this, window, cx| {
9796 this.select_all(&SelectAll, window, cx);
9797 this.insert("", window, cx);
9798 });
9799 }
9800
9801 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9802 if self.read_only(cx) {
9803 return;
9804 }
9805 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9806 self.transact(window, cx, |this, window, cx| {
9807 this.select_autoclose_pair(window, cx);
9808 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9809 if !this.linked_edit_ranges.is_empty() {
9810 let selections = this.selections.all::<MultiBufferPoint>(cx);
9811 let snapshot = this.buffer.read(cx).snapshot(cx);
9812
9813 for selection in selections.iter() {
9814 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9815 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9816 if selection_start.buffer_id != selection_end.buffer_id {
9817 continue;
9818 }
9819 if let Some(ranges) =
9820 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9821 {
9822 for (buffer, entries) in ranges {
9823 linked_ranges.entry(buffer).or_default().extend(entries);
9824 }
9825 }
9826 }
9827 }
9828
9829 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9830 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9831 for selection in &mut selections {
9832 if selection.is_empty() {
9833 let old_head = selection.head();
9834 let mut new_head =
9835 movement::left(&display_map, old_head.to_display_point(&display_map))
9836 .to_point(&display_map);
9837 if let Some((buffer, line_buffer_range)) = display_map
9838 .buffer_snapshot
9839 .buffer_line_for_row(MultiBufferRow(old_head.row))
9840 {
9841 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9842 let indent_len = match indent_size.kind {
9843 IndentKind::Space => {
9844 buffer.settings_at(line_buffer_range.start, cx).tab_size
9845 }
9846 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9847 };
9848 if old_head.column <= indent_size.len && old_head.column > 0 {
9849 let indent_len = indent_len.get();
9850 new_head = cmp::min(
9851 new_head,
9852 MultiBufferPoint::new(
9853 old_head.row,
9854 ((old_head.column - 1) / indent_len) * indent_len,
9855 ),
9856 );
9857 }
9858 }
9859
9860 selection.set_head(new_head, SelectionGoal::None);
9861 }
9862 }
9863
9864 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9865 this.insert("", window, cx);
9866 let empty_str: Arc<str> = Arc::from("");
9867 for (buffer, edits) in linked_ranges {
9868 let snapshot = buffer.read(cx).snapshot();
9869 use text::ToPoint as TP;
9870
9871 let edits = edits
9872 .into_iter()
9873 .map(|range| {
9874 let end_point = TP::to_point(&range.end, &snapshot);
9875 let mut start_point = TP::to_point(&range.start, &snapshot);
9876
9877 if end_point == start_point {
9878 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9879 .saturating_sub(1);
9880 start_point =
9881 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9882 };
9883
9884 (start_point..end_point, empty_str.clone())
9885 })
9886 .sorted_by_key(|(range, _)| range.start)
9887 .collect::<Vec<_>>();
9888 buffer.update(cx, |this, cx| {
9889 this.edit(edits, None, cx);
9890 })
9891 }
9892 this.refresh_edit_prediction(true, false, window, cx);
9893 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9894 });
9895 }
9896
9897 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9898 if self.read_only(cx) {
9899 return;
9900 }
9901 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9902 self.transact(window, cx, |this, window, cx| {
9903 this.change_selections(Default::default(), window, cx, |s| {
9904 s.move_with(|map, selection| {
9905 if selection.is_empty() {
9906 let cursor = movement::right(map, selection.head());
9907 selection.end = cursor;
9908 selection.reversed = true;
9909 selection.goal = SelectionGoal::None;
9910 }
9911 })
9912 });
9913 this.insert("", window, cx);
9914 this.refresh_edit_prediction(true, false, window, cx);
9915 });
9916 }
9917
9918 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9919 if self.mode.is_single_line() {
9920 cx.propagate();
9921 return;
9922 }
9923
9924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9925 if self.move_to_prev_snippet_tabstop(window, cx) {
9926 return;
9927 }
9928 self.outdent(&Outdent, window, cx);
9929 }
9930
9931 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9932 if self.mode.is_single_line() {
9933 cx.propagate();
9934 return;
9935 }
9936
9937 if self.move_to_next_snippet_tabstop(window, cx) {
9938 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9939 return;
9940 }
9941 if self.read_only(cx) {
9942 return;
9943 }
9944 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9945 let mut selections = self.selections.all_adjusted(cx);
9946 let buffer = self.buffer.read(cx);
9947 let snapshot = buffer.snapshot(cx);
9948 let rows_iter = selections.iter().map(|s| s.head().row);
9949 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9950
9951 let has_some_cursor_in_whitespace = selections
9952 .iter()
9953 .filter(|selection| selection.is_empty())
9954 .any(|selection| {
9955 let cursor = selection.head();
9956 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9957 cursor.column < current_indent.len
9958 });
9959
9960 let mut edits = Vec::new();
9961 let mut prev_edited_row = 0;
9962 let mut row_delta = 0;
9963 for selection in &mut selections {
9964 if selection.start.row != prev_edited_row {
9965 row_delta = 0;
9966 }
9967 prev_edited_row = selection.end.row;
9968
9969 // If the selection is non-empty, then increase the indentation of the selected lines.
9970 if !selection.is_empty() {
9971 row_delta =
9972 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9973 continue;
9974 }
9975
9976 let cursor = selection.head();
9977 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9978 if let Some(suggested_indent) =
9979 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9980 {
9981 // Don't do anything if already at suggested indent
9982 // and there is any other cursor which is not
9983 if has_some_cursor_in_whitespace
9984 && cursor.column == current_indent.len
9985 && current_indent.len == suggested_indent.len
9986 {
9987 continue;
9988 }
9989
9990 // Adjust line and move cursor to suggested indent
9991 // if cursor is not at suggested indent
9992 if cursor.column < suggested_indent.len
9993 && cursor.column <= current_indent.len
9994 && current_indent.len <= suggested_indent.len
9995 {
9996 selection.start = Point::new(cursor.row, suggested_indent.len);
9997 selection.end = selection.start;
9998 if row_delta == 0 {
9999 edits.extend(Buffer::edit_for_indent_size_adjustment(
10000 cursor.row,
10001 current_indent,
10002 suggested_indent,
10003 ));
10004 row_delta = suggested_indent.len - current_indent.len;
10005 }
10006 continue;
10007 }
10008
10009 // If current indent is more than suggested indent
10010 // only move cursor to current indent and skip indent
10011 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10012 selection.start = Point::new(cursor.row, current_indent.len);
10013 selection.end = selection.start;
10014 continue;
10015 }
10016 }
10017
10018 // Otherwise, insert a hard or soft tab.
10019 let settings = buffer.language_settings_at(cursor, cx);
10020 let tab_size = if settings.hard_tabs {
10021 IndentSize::tab()
10022 } else {
10023 let tab_size = settings.tab_size.get();
10024 let indent_remainder = snapshot
10025 .text_for_range(Point::new(cursor.row, 0)..cursor)
10026 .flat_map(str::chars)
10027 .fold(row_delta % tab_size, |counter: u32, c| {
10028 if c == '\t' {
10029 0
10030 } else {
10031 (counter + 1) % tab_size
10032 }
10033 });
10034
10035 let chars_to_next_tab_stop = tab_size - indent_remainder;
10036 IndentSize::spaces(chars_to_next_tab_stop)
10037 };
10038 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10039 selection.end = selection.start;
10040 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10041 row_delta += tab_size.len;
10042 }
10043
10044 self.transact(window, cx, |this, window, cx| {
10045 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10046 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10047 this.refresh_edit_prediction(true, false, window, cx);
10048 });
10049 }
10050
10051 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10052 if self.read_only(cx) {
10053 return;
10054 }
10055 if self.mode.is_single_line() {
10056 cx.propagate();
10057 return;
10058 }
10059
10060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10061 let mut selections = self.selections.all::<Point>(cx);
10062 let mut prev_edited_row = 0;
10063 let mut row_delta = 0;
10064 let mut edits = Vec::new();
10065 let buffer = self.buffer.read(cx);
10066 let snapshot = buffer.snapshot(cx);
10067 for selection in &mut selections {
10068 if selection.start.row != prev_edited_row {
10069 row_delta = 0;
10070 }
10071 prev_edited_row = selection.end.row;
10072
10073 row_delta =
10074 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10075 }
10076
10077 self.transact(window, cx, |this, window, cx| {
10078 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10079 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10080 });
10081 }
10082
10083 fn indent_selection(
10084 buffer: &MultiBuffer,
10085 snapshot: &MultiBufferSnapshot,
10086 selection: &mut Selection<Point>,
10087 edits: &mut Vec<(Range<Point>, String)>,
10088 delta_for_start_row: u32,
10089 cx: &App,
10090 ) -> u32 {
10091 let settings = buffer.language_settings_at(selection.start, cx);
10092 let tab_size = settings.tab_size.get();
10093 let indent_kind = if settings.hard_tabs {
10094 IndentKind::Tab
10095 } else {
10096 IndentKind::Space
10097 };
10098 let mut start_row = selection.start.row;
10099 let mut end_row = selection.end.row + 1;
10100
10101 // If a selection ends at the beginning of a line, don't indent
10102 // that last line.
10103 if selection.end.column == 0 && selection.end.row > selection.start.row {
10104 end_row -= 1;
10105 }
10106
10107 // Avoid re-indenting a row that has already been indented by a
10108 // previous selection, but still update this selection's column
10109 // to reflect that indentation.
10110 if delta_for_start_row > 0 {
10111 start_row += 1;
10112 selection.start.column += delta_for_start_row;
10113 if selection.end.row == selection.start.row {
10114 selection.end.column += delta_for_start_row;
10115 }
10116 }
10117
10118 let mut delta_for_end_row = 0;
10119 let has_multiple_rows = start_row + 1 != end_row;
10120 for row in start_row..end_row {
10121 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10122 let indent_delta = match (current_indent.kind, indent_kind) {
10123 (IndentKind::Space, IndentKind::Space) => {
10124 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10125 IndentSize::spaces(columns_to_next_tab_stop)
10126 }
10127 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10128 (_, IndentKind::Tab) => IndentSize::tab(),
10129 };
10130
10131 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10132 0
10133 } else {
10134 selection.start.column
10135 };
10136 let row_start = Point::new(row, start);
10137 edits.push((
10138 row_start..row_start,
10139 indent_delta.chars().collect::<String>(),
10140 ));
10141
10142 // Update this selection's endpoints to reflect the indentation.
10143 if row == selection.start.row {
10144 selection.start.column += indent_delta.len;
10145 }
10146 if row == selection.end.row {
10147 selection.end.column += indent_delta.len;
10148 delta_for_end_row = indent_delta.len;
10149 }
10150 }
10151
10152 if selection.start.row == selection.end.row {
10153 delta_for_start_row + delta_for_end_row
10154 } else {
10155 delta_for_end_row
10156 }
10157 }
10158
10159 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10160 if self.read_only(cx) {
10161 return;
10162 }
10163 if self.mode.is_single_line() {
10164 cx.propagate();
10165 return;
10166 }
10167
10168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10169 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10170 let selections = self.selections.all::<Point>(cx);
10171 let mut deletion_ranges = Vec::new();
10172 let mut last_outdent = None;
10173 {
10174 let buffer = self.buffer.read(cx);
10175 let snapshot = buffer.snapshot(cx);
10176 for selection in &selections {
10177 let settings = buffer.language_settings_at(selection.start, cx);
10178 let tab_size = settings.tab_size.get();
10179 let mut rows = selection.spanned_rows(false, &display_map);
10180
10181 // Avoid re-outdenting a row that has already been outdented by a
10182 // previous selection.
10183 if let Some(last_row) = last_outdent
10184 && last_row == rows.start
10185 {
10186 rows.start = rows.start.next_row();
10187 }
10188 let has_multiple_rows = rows.len() > 1;
10189 for row in rows.iter_rows() {
10190 let indent_size = snapshot.indent_size_for_line(row);
10191 if indent_size.len > 0 {
10192 let deletion_len = match indent_size.kind {
10193 IndentKind::Space => {
10194 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10195 if columns_to_prev_tab_stop == 0 {
10196 tab_size
10197 } else {
10198 columns_to_prev_tab_stop
10199 }
10200 }
10201 IndentKind::Tab => 1,
10202 };
10203 let start = if has_multiple_rows
10204 || deletion_len > selection.start.column
10205 || indent_size.len < selection.start.column
10206 {
10207 0
10208 } else {
10209 selection.start.column - deletion_len
10210 };
10211 deletion_ranges.push(
10212 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10213 );
10214 last_outdent = Some(row);
10215 }
10216 }
10217 }
10218 }
10219
10220 self.transact(window, cx, |this, window, cx| {
10221 this.buffer.update(cx, |buffer, cx| {
10222 let empty_str: Arc<str> = Arc::default();
10223 buffer.edit(
10224 deletion_ranges
10225 .into_iter()
10226 .map(|range| (range, empty_str.clone())),
10227 None,
10228 cx,
10229 );
10230 });
10231 let selections = this.selections.all::<usize>(cx);
10232 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10233 });
10234 }
10235
10236 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10237 if self.read_only(cx) {
10238 return;
10239 }
10240 if self.mode.is_single_line() {
10241 cx.propagate();
10242 return;
10243 }
10244
10245 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10246 let selections = self
10247 .selections
10248 .all::<usize>(cx)
10249 .into_iter()
10250 .map(|s| s.range());
10251
10252 self.transact(window, cx, |this, window, cx| {
10253 this.buffer.update(cx, |buffer, cx| {
10254 buffer.autoindent_ranges(selections, cx);
10255 });
10256 let selections = this.selections.all::<usize>(cx);
10257 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10258 });
10259 }
10260
10261 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10262 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10263 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10264 let selections = self.selections.all::<Point>(cx);
10265
10266 let mut new_cursors = Vec::new();
10267 let mut edit_ranges = Vec::new();
10268 let mut selections = selections.iter().peekable();
10269 while let Some(selection) = selections.next() {
10270 let mut rows = selection.spanned_rows(false, &display_map);
10271 let goal_display_column = selection.head().to_display_point(&display_map).column();
10272
10273 // Accumulate contiguous regions of rows that we want to delete.
10274 while let Some(next_selection) = selections.peek() {
10275 let next_rows = next_selection.spanned_rows(false, &display_map);
10276 if next_rows.start <= rows.end {
10277 rows.end = next_rows.end;
10278 selections.next().unwrap();
10279 } else {
10280 break;
10281 }
10282 }
10283
10284 let buffer = &display_map.buffer_snapshot;
10285 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10286 let edit_end;
10287 let cursor_buffer_row;
10288 if buffer.max_point().row >= rows.end.0 {
10289 // If there's a line after the range, delete the \n from the end of the row range
10290 // and position the cursor on the next line.
10291 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10292 cursor_buffer_row = rows.end;
10293 } else {
10294 // If there isn't a line after the range, delete the \n from the line before the
10295 // start of the row range and position the cursor there.
10296 edit_start = edit_start.saturating_sub(1);
10297 edit_end = buffer.len();
10298 cursor_buffer_row = rows.start.previous_row();
10299 }
10300
10301 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10302 *cursor.column_mut() =
10303 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10304
10305 new_cursors.push((
10306 selection.id,
10307 buffer.anchor_after(cursor.to_point(&display_map)),
10308 ));
10309 edit_ranges.push(edit_start..edit_end);
10310 }
10311
10312 self.transact(window, cx, |this, window, cx| {
10313 let buffer = this.buffer.update(cx, |buffer, cx| {
10314 let empty_str: Arc<str> = Arc::default();
10315 buffer.edit(
10316 edit_ranges
10317 .into_iter()
10318 .map(|range| (range, empty_str.clone())),
10319 None,
10320 cx,
10321 );
10322 buffer.snapshot(cx)
10323 });
10324 let new_selections = new_cursors
10325 .into_iter()
10326 .map(|(id, cursor)| {
10327 let cursor = cursor.to_point(&buffer);
10328 Selection {
10329 id,
10330 start: cursor,
10331 end: cursor,
10332 reversed: false,
10333 goal: SelectionGoal::None,
10334 }
10335 })
10336 .collect();
10337
10338 this.change_selections(Default::default(), window, cx, |s| {
10339 s.select(new_selections);
10340 });
10341 });
10342 }
10343
10344 pub fn join_lines_impl(
10345 &mut self,
10346 insert_whitespace: bool,
10347 window: &mut Window,
10348 cx: &mut Context<Self>,
10349 ) {
10350 if self.read_only(cx) {
10351 return;
10352 }
10353 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10354 for selection in self.selections.all::<Point>(cx) {
10355 let start = MultiBufferRow(selection.start.row);
10356 // Treat single line selections as if they include the next line. Otherwise this action
10357 // would do nothing for single line selections individual cursors.
10358 let end = if selection.start.row == selection.end.row {
10359 MultiBufferRow(selection.start.row + 1)
10360 } else {
10361 MultiBufferRow(selection.end.row)
10362 };
10363
10364 if let Some(last_row_range) = row_ranges.last_mut()
10365 && start <= last_row_range.end
10366 {
10367 last_row_range.end = end;
10368 continue;
10369 }
10370 row_ranges.push(start..end);
10371 }
10372
10373 let snapshot = self.buffer.read(cx).snapshot(cx);
10374 let mut cursor_positions = Vec::new();
10375 for row_range in &row_ranges {
10376 let anchor = snapshot.anchor_before(Point::new(
10377 row_range.end.previous_row().0,
10378 snapshot.line_len(row_range.end.previous_row()),
10379 ));
10380 cursor_positions.push(anchor..anchor);
10381 }
10382
10383 self.transact(window, cx, |this, window, cx| {
10384 for row_range in row_ranges.into_iter().rev() {
10385 for row in row_range.iter_rows().rev() {
10386 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10387 let next_line_row = row.next_row();
10388 let indent = snapshot.indent_size_for_line(next_line_row);
10389 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10390
10391 let replace =
10392 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10393 " "
10394 } else {
10395 ""
10396 };
10397
10398 this.buffer.update(cx, |buffer, cx| {
10399 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10400 });
10401 }
10402 }
10403
10404 this.change_selections(Default::default(), window, cx, |s| {
10405 s.select_anchor_ranges(cursor_positions)
10406 });
10407 });
10408 }
10409
10410 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10412 self.join_lines_impl(true, window, cx);
10413 }
10414
10415 pub fn sort_lines_case_sensitive(
10416 &mut self,
10417 _: &SortLinesCaseSensitive,
10418 window: &mut Window,
10419 cx: &mut Context<Self>,
10420 ) {
10421 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10422 }
10423
10424 pub fn sort_lines_by_length(
10425 &mut self,
10426 _: &SortLinesByLength,
10427 window: &mut Window,
10428 cx: &mut Context<Self>,
10429 ) {
10430 self.manipulate_immutable_lines(window, cx, |lines| {
10431 lines.sort_by_key(|&line| line.chars().count())
10432 })
10433 }
10434
10435 pub fn sort_lines_case_insensitive(
10436 &mut self,
10437 _: &SortLinesCaseInsensitive,
10438 window: &mut Window,
10439 cx: &mut Context<Self>,
10440 ) {
10441 self.manipulate_immutable_lines(window, cx, |lines| {
10442 lines.sort_by_key(|line| line.to_lowercase())
10443 })
10444 }
10445
10446 pub fn unique_lines_case_insensitive(
10447 &mut self,
10448 _: &UniqueLinesCaseInsensitive,
10449 window: &mut Window,
10450 cx: &mut Context<Self>,
10451 ) {
10452 self.manipulate_immutable_lines(window, cx, |lines| {
10453 let mut seen = HashSet::default();
10454 lines.retain(|line| seen.insert(line.to_lowercase()));
10455 })
10456 }
10457
10458 pub fn unique_lines_case_sensitive(
10459 &mut self,
10460 _: &UniqueLinesCaseSensitive,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.manipulate_immutable_lines(window, cx, |lines| {
10465 let mut seen = HashSet::default();
10466 lines.retain(|line| seen.insert(*line));
10467 })
10468 }
10469
10470 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10471 let snapshot = self.buffer.read(cx).snapshot(cx);
10472 for selection in self.selections.disjoint_anchors().iter() {
10473 if snapshot
10474 .language_at(selection.start)
10475 .and_then(|lang| lang.config().wrap_characters.as_ref())
10476 .is_some()
10477 {
10478 return true;
10479 }
10480 }
10481 false
10482 }
10483
10484 fn wrap_selections_in_tag(
10485 &mut self,
10486 _: &WrapSelectionsInTag,
10487 window: &mut Window,
10488 cx: &mut Context<Self>,
10489 ) {
10490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10491
10492 let snapshot = self.buffer.read(cx).snapshot(cx);
10493
10494 let mut edits = Vec::new();
10495 let mut boundaries = Vec::new();
10496
10497 for selection in self.selections.all::<Point>(cx).iter() {
10498 let Some(wrap_config) = snapshot
10499 .language_at(selection.start)
10500 .and_then(|lang| lang.config().wrap_characters.clone())
10501 else {
10502 continue;
10503 };
10504
10505 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10506 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10507
10508 let start_before = snapshot.anchor_before(selection.start);
10509 let end_after = snapshot.anchor_after(selection.end);
10510
10511 edits.push((start_before..start_before, open_tag));
10512 edits.push((end_after..end_after, close_tag));
10513
10514 boundaries.push((
10515 start_before,
10516 end_after,
10517 wrap_config.start_prefix.len(),
10518 wrap_config.end_suffix.len(),
10519 ));
10520 }
10521
10522 if edits.is_empty() {
10523 return;
10524 }
10525
10526 self.transact(window, cx, |this, window, cx| {
10527 let buffer = this.buffer.update(cx, |buffer, cx| {
10528 buffer.edit(edits, None, cx);
10529 buffer.snapshot(cx)
10530 });
10531
10532 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10533 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10534 boundaries.into_iter()
10535 {
10536 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10537 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10538 new_selections.push(open_offset..open_offset);
10539 new_selections.push(close_offset..close_offset);
10540 }
10541
10542 this.change_selections(Default::default(), window, cx, |s| {
10543 s.select_ranges(new_selections);
10544 });
10545
10546 this.request_autoscroll(Autoscroll::fit(), cx);
10547 });
10548 }
10549
10550 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10551 let Some(project) = self.project.clone() else {
10552 return;
10553 };
10554 self.reload(project, window, cx)
10555 .detach_and_notify_err(window, cx);
10556 }
10557
10558 pub fn restore_file(
10559 &mut self,
10560 _: &::git::RestoreFile,
10561 window: &mut Window,
10562 cx: &mut Context<Self>,
10563 ) {
10564 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10565 let mut buffer_ids = HashSet::default();
10566 let snapshot = self.buffer().read(cx).snapshot(cx);
10567 for selection in self.selections.all::<usize>(cx) {
10568 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10569 }
10570
10571 let buffer = self.buffer().read(cx);
10572 let ranges = buffer_ids
10573 .into_iter()
10574 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10575 .collect::<Vec<_>>();
10576
10577 self.restore_hunks_in_ranges(ranges, window, cx);
10578 }
10579
10580 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10581 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10582 let selections = self
10583 .selections
10584 .all(cx)
10585 .into_iter()
10586 .map(|s| s.range())
10587 .collect();
10588 self.restore_hunks_in_ranges(selections, window, cx);
10589 }
10590
10591 pub fn restore_hunks_in_ranges(
10592 &mut self,
10593 ranges: Vec<Range<Point>>,
10594 window: &mut Window,
10595 cx: &mut Context<Editor>,
10596 ) {
10597 let mut revert_changes = HashMap::default();
10598 let chunk_by = self
10599 .snapshot(window, cx)
10600 .hunks_for_ranges(ranges)
10601 .into_iter()
10602 .chunk_by(|hunk| hunk.buffer_id);
10603 for (buffer_id, hunks) in &chunk_by {
10604 let hunks = hunks.collect::<Vec<_>>();
10605 for hunk in &hunks {
10606 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10607 }
10608 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10609 }
10610 drop(chunk_by);
10611 if !revert_changes.is_empty() {
10612 self.transact(window, cx, |editor, window, cx| {
10613 editor.restore(revert_changes, window, cx);
10614 });
10615 }
10616 }
10617
10618 pub fn open_active_item_in_terminal(
10619 &mut self,
10620 _: &OpenInTerminal,
10621 window: &mut Window,
10622 cx: &mut Context<Self>,
10623 ) {
10624 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10625 let project_path = buffer.read(cx).project_path(cx)?;
10626 let project = self.project()?.read(cx);
10627 let entry = project.entry_for_path(&project_path, cx)?;
10628 let parent = match &entry.canonical_path {
10629 Some(canonical_path) => canonical_path.to_path_buf(),
10630 None => project.absolute_path(&project_path, cx)?,
10631 }
10632 .parent()?
10633 .to_path_buf();
10634 Some(parent)
10635 }) {
10636 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10637 }
10638 }
10639
10640 fn set_breakpoint_context_menu(
10641 &mut self,
10642 display_row: DisplayRow,
10643 position: Option<Anchor>,
10644 clicked_point: gpui::Point<Pixels>,
10645 window: &mut Window,
10646 cx: &mut Context<Self>,
10647 ) {
10648 let source = self
10649 .buffer
10650 .read(cx)
10651 .snapshot(cx)
10652 .anchor_before(Point::new(display_row.0, 0u32));
10653
10654 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10655
10656 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10657 self,
10658 source,
10659 clicked_point,
10660 context_menu,
10661 window,
10662 cx,
10663 );
10664 }
10665
10666 fn add_edit_breakpoint_block(
10667 &mut self,
10668 anchor: Anchor,
10669 breakpoint: &Breakpoint,
10670 edit_action: BreakpointPromptEditAction,
10671 window: &mut Window,
10672 cx: &mut Context<Self>,
10673 ) {
10674 let weak_editor = cx.weak_entity();
10675 let bp_prompt = cx.new(|cx| {
10676 BreakpointPromptEditor::new(
10677 weak_editor,
10678 anchor,
10679 breakpoint.clone(),
10680 edit_action,
10681 window,
10682 cx,
10683 )
10684 });
10685
10686 let height = bp_prompt.update(cx, |this, cx| {
10687 this.prompt
10688 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10689 });
10690 let cloned_prompt = bp_prompt.clone();
10691 let blocks = vec![BlockProperties {
10692 style: BlockStyle::Sticky,
10693 placement: BlockPlacement::Above(anchor),
10694 height: Some(height),
10695 render: Arc::new(move |cx| {
10696 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10697 cloned_prompt.clone().into_any_element()
10698 }),
10699 priority: 0,
10700 }];
10701
10702 let focus_handle = bp_prompt.focus_handle(cx);
10703 window.focus(&focus_handle);
10704
10705 let block_ids = self.insert_blocks(blocks, None, cx);
10706 bp_prompt.update(cx, |prompt, _| {
10707 prompt.add_block_ids(block_ids);
10708 });
10709 }
10710
10711 pub(crate) fn breakpoint_at_row(
10712 &self,
10713 row: u32,
10714 window: &mut Window,
10715 cx: &mut Context<Self>,
10716 ) -> Option<(Anchor, Breakpoint)> {
10717 let snapshot = self.snapshot(window, cx);
10718 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10719
10720 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10721 }
10722
10723 pub(crate) fn breakpoint_at_anchor(
10724 &self,
10725 breakpoint_position: Anchor,
10726 snapshot: &EditorSnapshot,
10727 cx: &mut Context<Self>,
10728 ) -> Option<(Anchor, Breakpoint)> {
10729 let buffer = self
10730 .buffer
10731 .read(cx)
10732 .buffer_for_anchor(breakpoint_position, cx)?;
10733
10734 let enclosing_excerpt = breakpoint_position.excerpt_id;
10735 let buffer_snapshot = buffer.read(cx).snapshot();
10736
10737 let row = buffer_snapshot
10738 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10739 .row;
10740
10741 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10742 let anchor_end = snapshot
10743 .buffer_snapshot
10744 .anchor_after(Point::new(row, line_len));
10745
10746 self.breakpoint_store
10747 .as_ref()?
10748 .read_with(cx, |breakpoint_store, cx| {
10749 breakpoint_store
10750 .breakpoints(
10751 &buffer,
10752 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10753 &buffer_snapshot,
10754 cx,
10755 )
10756 .next()
10757 .and_then(|(bp, _)| {
10758 let breakpoint_row = buffer_snapshot
10759 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10760 .row;
10761
10762 if breakpoint_row == row {
10763 snapshot
10764 .buffer_snapshot
10765 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10766 .map(|position| (position, bp.bp.clone()))
10767 } else {
10768 None
10769 }
10770 })
10771 })
10772 }
10773
10774 pub fn edit_log_breakpoint(
10775 &mut self,
10776 _: &EditLogBreakpoint,
10777 window: &mut Window,
10778 cx: &mut Context<Self>,
10779 ) {
10780 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10781 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10782 message: None,
10783 state: BreakpointState::Enabled,
10784 condition: None,
10785 hit_condition: None,
10786 });
10787
10788 self.add_edit_breakpoint_block(
10789 anchor,
10790 &breakpoint,
10791 BreakpointPromptEditAction::Log,
10792 window,
10793 cx,
10794 );
10795 }
10796 }
10797
10798 fn breakpoints_at_cursors(
10799 &self,
10800 window: &mut Window,
10801 cx: &mut Context<Self>,
10802 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10803 let snapshot = self.snapshot(window, cx);
10804 let cursors = self
10805 .selections
10806 .disjoint_anchors()
10807 .iter()
10808 .map(|selection| {
10809 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10810
10811 let breakpoint_position = self
10812 .breakpoint_at_row(cursor_position.row, window, cx)
10813 .map(|bp| bp.0)
10814 .unwrap_or_else(|| {
10815 snapshot
10816 .display_snapshot
10817 .buffer_snapshot
10818 .anchor_after(Point::new(cursor_position.row, 0))
10819 });
10820
10821 let breakpoint = self
10822 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10823 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10824
10825 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10826 })
10827 // 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.
10828 .collect::<HashMap<Anchor, _>>();
10829
10830 cursors.into_iter().collect()
10831 }
10832
10833 pub fn enable_breakpoint(
10834 &mut self,
10835 _: &crate::actions::EnableBreakpoint,
10836 window: &mut Window,
10837 cx: &mut Context<Self>,
10838 ) {
10839 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10840 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10841 continue;
10842 };
10843 self.edit_breakpoint_at_anchor(
10844 anchor,
10845 breakpoint,
10846 BreakpointEditAction::InvertState,
10847 cx,
10848 );
10849 }
10850 }
10851
10852 pub fn disable_breakpoint(
10853 &mut self,
10854 _: &crate::actions::DisableBreakpoint,
10855 window: &mut Window,
10856 cx: &mut Context<Self>,
10857 ) {
10858 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10859 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10860 continue;
10861 };
10862 self.edit_breakpoint_at_anchor(
10863 anchor,
10864 breakpoint,
10865 BreakpointEditAction::InvertState,
10866 cx,
10867 );
10868 }
10869 }
10870
10871 pub fn toggle_breakpoint(
10872 &mut self,
10873 _: &crate::actions::ToggleBreakpoint,
10874 window: &mut Window,
10875 cx: &mut Context<Self>,
10876 ) {
10877 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10878 if let Some(breakpoint) = breakpoint {
10879 self.edit_breakpoint_at_anchor(
10880 anchor,
10881 breakpoint,
10882 BreakpointEditAction::Toggle,
10883 cx,
10884 );
10885 } else {
10886 self.edit_breakpoint_at_anchor(
10887 anchor,
10888 Breakpoint::new_standard(),
10889 BreakpointEditAction::Toggle,
10890 cx,
10891 );
10892 }
10893 }
10894 }
10895
10896 pub fn edit_breakpoint_at_anchor(
10897 &mut self,
10898 breakpoint_position: Anchor,
10899 breakpoint: Breakpoint,
10900 edit_action: BreakpointEditAction,
10901 cx: &mut Context<Self>,
10902 ) {
10903 let Some(breakpoint_store) = &self.breakpoint_store else {
10904 return;
10905 };
10906
10907 let Some(buffer) = self
10908 .buffer
10909 .read(cx)
10910 .buffer_for_anchor(breakpoint_position, cx)
10911 else {
10912 return;
10913 };
10914
10915 breakpoint_store.update(cx, |breakpoint_store, cx| {
10916 breakpoint_store.toggle_breakpoint(
10917 buffer,
10918 BreakpointWithPosition {
10919 position: breakpoint_position.text_anchor,
10920 bp: breakpoint,
10921 },
10922 edit_action,
10923 cx,
10924 );
10925 });
10926
10927 cx.notify();
10928 }
10929
10930 #[cfg(any(test, feature = "test-support"))]
10931 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10932 self.breakpoint_store.clone()
10933 }
10934
10935 pub fn prepare_restore_change(
10936 &self,
10937 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10938 hunk: &MultiBufferDiffHunk,
10939 cx: &mut App,
10940 ) -> Option<()> {
10941 if hunk.is_created_file() {
10942 return None;
10943 }
10944 let buffer = self.buffer.read(cx);
10945 let diff = buffer.diff_for(hunk.buffer_id)?;
10946 let buffer = buffer.buffer(hunk.buffer_id)?;
10947 let buffer = buffer.read(cx);
10948 let original_text = diff
10949 .read(cx)
10950 .base_text()
10951 .as_rope()
10952 .slice(hunk.diff_base_byte_range.clone());
10953 let buffer_snapshot = buffer.snapshot();
10954 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10955 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10956 probe
10957 .0
10958 .start
10959 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10960 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10961 }) {
10962 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10963 Some(())
10964 } else {
10965 None
10966 }
10967 }
10968
10969 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10970 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10971 }
10972
10973 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10974 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10975 }
10976
10977 fn manipulate_lines<M>(
10978 &mut self,
10979 window: &mut Window,
10980 cx: &mut Context<Self>,
10981 mut manipulate: M,
10982 ) where
10983 M: FnMut(&str) -> LineManipulationResult,
10984 {
10985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10986
10987 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10988 let buffer = self.buffer.read(cx).snapshot(cx);
10989
10990 let mut edits = Vec::new();
10991
10992 let selections = self.selections.all::<Point>(cx);
10993 let mut selections = selections.iter().peekable();
10994 let mut contiguous_row_selections = Vec::new();
10995 let mut new_selections = Vec::new();
10996 let mut added_lines = 0;
10997 let mut removed_lines = 0;
10998
10999 while let Some(selection) = selections.next() {
11000 let (start_row, end_row) = consume_contiguous_rows(
11001 &mut contiguous_row_selections,
11002 selection,
11003 &display_map,
11004 &mut selections,
11005 );
11006
11007 let start_point = Point::new(start_row.0, 0);
11008 let end_point = Point::new(
11009 end_row.previous_row().0,
11010 buffer.line_len(end_row.previous_row()),
11011 );
11012 let text = buffer
11013 .text_for_range(start_point..end_point)
11014 .collect::<String>();
11015
11016 let LineManipulationResult {
11017 new_text,
11018 line_count_before,
11019 line_count_after,
11020 } = manipulate(&text);
11021
11022 edits.push((start_point..end_point, new_text));
11023
11024 // Selections must change based on added and removed line count
11025 let start_row =
11026 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11027 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11028 new_selections.push(Selection {
11029 id: selection.id,
11030 start: start_row,
11031 end: end_row,
11032 goal: SelectionGoal::None,
11033 reversed: selection.reversed,
11034 });
11035
11036 if line_count_after > line_count_before {
11037 added_lines += line_count_after - line_count_before;
11038 } else if line_count_before > line_count_after {
11039 removed_lines += line_count_before - line_count_after;
11040 }
11041 }
11042
11043 self.transact(window, cx, |this, window, cx| {
11044 let buffer = this.buffer.update(cx, |buffer, cx| {
11045 buffer.edit(edits, None, cx);
11046 buffer.snapshot(cx)
11047 });
11048
11049 // Recalculate offsets on newly edited buffer
11050 let new_selections = new_selections
11051 .iter()
11052 .map(|s| {
11053 let start_point = Point::new(s.start.0, 0);
11054 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11055 Selection {
11056 id: s.id,
11057 start: buffer.point_to_offset(start_point),
11058 end: buffer.point_to_offset(end_point),
11059 goal: s.goal,
11060 reversed: s.reversed,
11061 }
11062 })
11063 .collect();
11064
11065 this.change_selections(Default::default(), window, cx, |s| {
11066 s.select(new_selections);
11067 });
11068
11069 this.request_autoscroll(Autoscroll::fit(), cx);
11070 });
11071 }
11072
11073 fn manipulate_immutable_lines<Fn>(
11074 &mut self,
11075 window: &mut Window,
11076 cx: &mut Context<Self>,
11077 mut callback: Fn,
11078 ) where
11079 Fn: FnMut(&mut Vec<&str>),
11080 {
11081 self.manipulate_lines(window, cx, |text| {
11082 let mut lines: Vec<&str> = text.split('\n').collect();
11083 let line_count_before = lines.len();
11084
11085 callback(&mut lines);
11086
11087 LineManipulationResult {
11088 new_text: lines.join("\n"),
11089 line_count_before,
11090 line_count_after: lines.len(),
11091 }
11092 });
11093 }
11094
11095 fn manipulate_mutable_lines<Fn>(
11096 &mut self,
11097 window: &mut Window,
11098 cx: &mut Context<Self>,
11099 mut callback: Fn,
11100 ) where
11101 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11102 {
11103 self.manipulate_lines(window, cx, |text| {
11104 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11105 let line_count_before = lines.len();
11106
11107 callback(&mut lines);
11108
11109 LineManipulationResult {
11110 new_text: lines.join("\n"),
11111 line_count_before,
11112 line_count_after: lines.len(),
11113 }
11114 });
11115 }
11116
11117 pub fn convert_indentation_to_spaces(
11118 &mut self,
11119 _: &ConvertIndentationToSpaces,
11120 window: &mut Window,
11121 cx: &mut Context<Self>,
11122 ) {
11123 let settings = self.buffer.read(cx).language_settings(cx);
11124 let tab_size = settings.tab_size.get() as usize;
11125
11126 self.manipulate_mutable_lines(window, cx, |lines| {
11127 // Allocates a reasonably sized scratch buffer once for the whole loop
11128 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11129 // Avoids recomputing spaces that could be inserted many times
11130 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11131 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11132 .collect();
11133
11134 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11135 let mut chars = line.as_ref().chars();
11136 let mut col = 0;
11137 let mut changed = false;
11138
11139 for ch in chars.by_ref() {
11140 match ch {
11141 ' ' => {
11142 reindented_line.push(' ');
11143 col += 1;
11144 }
11145 '\t' => {
11146 // \t are converted to spaces depending on the current column
11147 let spaces_len = tab_size - (col % tab_size);
11148 reindented_line.extend(&space_cache[spaces_len - 1]);
11149 col += spaces_len;
11150 changed = true;
11151 }
11152 _ => {
11153 // If we dont append before break, the character is consumed
11154 reindented_line.push(ch);
11155 break;
11156 }
11157 }
11158 }
11159
11160 if !changed {
11161 reindented_line.clear();
11162 continue;
11163 }
11164 // Append the rest of the line and replace old reference with new one
11165 reindented_line.extend(chars);
11166 *line = Cow::Owned(reindented_line.clone());
11167 reindented_line.clear();
11168 }
11169 });
11170 }
11171
11172 pub fn convert_indentation_to_tabs(
11173 &mut self,
11174 _: &ConvertIndentationToTabs,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 let settings = self.buffer.read(cx).language_settings(cx);
11179 let tab_size = settings.tab_size.get() as usize;
11180
11181 self.manipulate_mutable_lines(window, cx, |lines| {
11182 // Allocates a reasonably sized buffer once for the whole loop
11183 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11184 // Avoids recomputing spaces that could be inserted many times
11185 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11186 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11187 .collect();
11188
11189 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11190 let mut chars = line.chars();
11191 let mut spaces_count = 0;
11192 let mut first_non_indent_char = None;
11193 let mut changed = false;
11194
11195 for ch in chars.by_ref() {
11196 match ch {
11197 ' ' => {
11198 // Keep track of spaces. Append \t when we reach tab_size
11199 spaces_count += 1;
11200 changed = true;
11201 if spaces_count == tab_size {
11202 reindented_line.push('\t');
11203 spaces_count = 0;
11204 }
11205 }
11206 '\t' => {
11207 reindented_line.push('\t');
11208 spaces_count = 0;
11209 }
11210 _ => {
11211 // Dont append it yet, we might have remaining spaces
11212 first_non_indent_char = Some(ch);
11213 break;
11214 }
11215 }
11216 }
11217
11218 if !changed {
11219 reindented_line.clear();
11220 continue;
11221 }
11222 // Remaining spaces that didn't make a full tab stop
11223 if spaces_count > 0 {
11224 reindented_line.extend(&space_cache[spaces_count - 1]);
11225 }
11226 // If we consume an extra character that was not indentation, add it back
11227 if let Some(extra_char) = first_non_indent_char {
11228 reindented_line.push(extra_char);
11229 }
11230 // Append the rest of the line and replace old reference with new one
11231 reindented_line.extend(chars);
11232 *line = Cow::Owned(reindented_line.clone());
11233 reindented_line.clear();
11234 }
11235 });
11236 }
11237
11238 pub fn convert_to_upper_case(
11239 &mut self,
11240 _: &ConvertToUpperCase,
11241 window: &mut Window,
11242 cx: &mut Context<Self>,
11243 ) {
11244 self.manipulate_text(window, cx, |text| text.to_uppercase())
11245 }
11246
11247 pub fn convert_to_lower_case(
11248 &mut self,
11249 _: &ConvertToLowerCase,
11250 window: &mut Window,
11251 cx: &mut Context<Self>,
11252 ) {
11253 self.manipulate_text(window, cx, |text| text.to_lowercase())
11254 }
11255
11256 pub fn convert_to_title_case(
11257 &mut self,
11258 _: &ConvertToTitleCase,
11259 window: &mut Window,
11260 cx: &mut Context<Self>,
11261 ) {
11262 self.manipulate_text(window, cx, |text| {
11263 text.split('\n')
11264 .map(|line| line.to_case(Case::Title))
11265 .join("\n")
11266 })
11267 }
11268
11269 pub fn convert_to_snake_case(
11270 &mut self,
11271 _: &ConvertToSnakeCase,
11272 window: &mut Window,
11273 cx: &mut Context<Self>,
11274 ) {
11275 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11276 }
11277
11278 pub fn convert_to_kebab_case(
11279 &mut self,
11280 _: &ConvertToKebabCase,
11281 window: &mut Window,
11282 cx: &mut Context<Self>,
11283 ) {
11284 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11285 }
11286
11287 pub fn convert_to_upper_camel_case(
11288 &mut self,
11289 _: &ConvertToUpperCamelCase,
11290 window: &mut Window,
11291 cx: &mut Context<Self>,
11292 ) {
11293 self.manipulate_text(window, cx, |text| {
11294 text.split('\n')
11295 .map(|line| line.to_case(Case::UpperCamel))
11296 .join("\n")
11297 })
11298 }
11299
11300 pub fn convert_to_lower_camel_case(
11301 &mut self,
11302 _: &ConvertToLowerCamelCase,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11307 }
11308
11309 pub fn convert_to_opposite_case(
11310 &mut self,
11311 _: &ConvertToOppositeCase,
11312 window: &mut Window,
11313 cx: &mut Context<Self>,
11314 ) {
11315 self.manipulate_text(window, cx, |text| {
11316 text.chars()
11317 .fold(String::with_capacity(text.len()), |mut t, c| {
11318 if c.is_uppercase() {
11319 t.extend(c.to_lowercase());
11320 } else {
11321 t.extend(c.to_uppercase());
11322 }
11323 t
11324 })
11325 })
11326 }
11327
11328 pub fn convert_to_sentence_case(
11329 &mut self,
11330 _: &ConvertToSentenceCase,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 ) {
11334 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11335 }
11336
11337 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11338 self.manipulate_text(window, cx, |text| {
11339 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11340 if has_upper_case_characters {
11341 text.to_lowercase()
11342 } else {
11343 text.to_uppercase()
11344 }
11345 })
11346 }
11347
11348 pub fn convert_to_rot13(
11349 &mut self,
11350 _: &ConvertToRot13,
11351 window: &mut Window,
11352 cx: &mut Context<Self>,
11353 ) {
11354 self.manipulate_text(window, cx, |text| {
11355 text.chars()
11356 .map(|c| match c {
11357 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11358 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11359 _ => c,
11360 })
11361 .collect()
11362 })
11363 }
11364
11365 pub fn convert_to_rot47(
11366 &mut self,
11367 _: &ConvertToRot47,
11368 window: &mut Window,
11369 cx: &mut Context<Self>,
11370 ) {
11371 self.manipulate_text(window, cx, |text| {
11372 text.chars()
11373 .map(|c| {
11374 let code_point = c as u32;
11375 if code_point >= 33 && code_point <= 126 {
11376 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11377 }
11378 c
11379 })
11380 .collect()
11381 })
11382 }
11383
11384 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11385 where
11386 Fn: FnMut(&str) -> String,
11387 {
11388 let buffer = self.buffer.read(cx).snapshot(cx);
11389
11390 let mut new_selections = Vec::new();
11391 let mut edits = Vec::new();
11392 let mut selection_adjustment = 0i32;
11393
11394 for selection in self.selections.all::<usize>(cx) {
11395 let selection_is_empty = selection.is_empty();
11396
11397 let (start, end) = if selection_is_empty {
11398 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11399 (word_range.start, word_range.end)
11400 } else {
11401 (selection.start, selection.end)
11402 };
11403
11404 let text = buffer.text_for_range(start..end).collect::<String>();
11405 let old_length = text.len() as i32;
11406 let text = callback(&text);
11407
11408 new_selections.push(Selection {
11409 start: (start as i32 - selection_adjustment) as usize,
11410 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11411 goal: SelectionGoal::None,
11412 ..selection
11413 });
11414
11415 selection_adjustment += old_length - text.len() as i32;
11416
11417 edits.push((start..end, text));
11418 }
11419
11420 self.transact(window, cx, |this, window, cx| {
11421 this.buffer.update(cx, |buffer, cx| {
11422 buffer.edit(edits, None, cx);
11423 });
11424
11425 this.change_selections(Default::default(), window, cx, |s| {
11426 s.select(new_selections);
11427 });
11428
11429 this.request_autoscroll(Autoscroll::fit(), cx);
11430 });
11431 }
11432
11433 pub fn move_selection_on_drop(
11434 &mut self,
11435 selection: &Selection<Anchor>,
11436 target: DisplayPoint,
11437 is_cut: bool,
11438 window: &mut Window,
11439 cx: &mut Context<Self>,
11440 ) {
11441 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11442 let buffer = &display_map.buffer_snapshot;
11443 let mut edits = Vec::new();
11444 let insert_point = display_map
11445 .clip_point(target, Bias::Left)
11446 .to_point(&display_map);
11447 let text = buffer
11448 .text_for_range(selection.start..selection.end)
11449 .collect::<String>();
11450 if is_cut {
11451 edits.push(((selection.start..selection.end), String::new()));
11452 }
11453 let insert_anchor = buffer.anchor_before(insert_point);
11454 edits.push(((insert_anchor..insert_anchor), text));
11455 let last_edit_start = insert_anchor.bias_left(buffer);
11456 let last_edit_end = insert_anchor.bias_right(buffer);
11457 self.transact(window, cx, |this, window, cx| {
11458 this.buffer.update(cx, |buffer, cx| {
11459 buffer.edit(edits, None, cx);
11460 });
11461 this.change_selections(Default::default(), window, cx, |s| {
11462 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11463 });
11464 });
11465 }
11466
11467 pub fn clear_selection_drag_state(&mut self) {
11468 self.selection_drag_state = SelectionDragState::None;
11469 }
11470
11471 pub fn duplicate(
11472 &mut self,
11473 upwards: bool,
11474 whole_lines: bool,
11475 window: &mut Window,
11476 cx: &mut Context<Self>,
11477 ) {
11478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11479
11480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11481 let buffer = &display_map.buffer_snapshot;
11482 let selections = self.selections.all::<Point>(cx);
11483
11484 let mut edits = Vec::new();
11485 let mut selections_iter = selections.iter().peekable();
11486 while let Some(selection) = selections_iter.next() {
11487 let mut rows = selection.spanned_rows(false, &display_map);
11488 // duplicate line-wise
11489 if whole_lines || selection.start == selection.end {
11490 // Avoid duplicating the same lines twice.
11491 while let Some(next_selection) = selections_iter.peek() {
11492 let next_rows = next_selection.spanned_rows(false, &display_map);
11493 if next_rows.start < rows.end {
11494 rows.end = next_rows.end;
11495 selections_iter.next().unwrap();
11496 } else {
11497 break;
11498 }
11499 }
11500
11501 // Copy the text from the selected row region and splice it either at the start
11502 // or end of the region.
11503 let start = Point::new(rows.start.0, 0);
11504 let end = Point::new(
11505 rows.end.previous_row().0,
11506 buffer.line_len(rows.end.previous_row()),
11507 );
11508 let text = buffer
11509 .text_for_range(start..end)
11510 .chain(Some("\n"))
11511 .collect::<String>();
11512 let insert_location = if upwards {
11513 Point::new(rows.end.0, 0)
11514 } else {
11515 start
11516 };
11517 edits.push((insert_location..insert_location, text));
11518 } else {
11519 // duplicate character-wise
11520 let start = selection.start;
11521 let end = selection.end;
11522 let text = buffer.text_for_range(start..end).collect::<String>();
11523 edits.push((selection.end..selection.end, text));
11524 }
11525 }
11526
11527 self.transact(window, cx, |this, _, cx| {
11528 this.buffer.update(cx, |buffer, cx| {
11529 buffer.edit(edits, None, cx);
11530 });
11531
11532 this.request_autoscroll(Autoscroll::fit(), cx);
11533 });
11534 }
11535
11536 pub fn duplicate_line_up(
11537 &mut self,
11538 _: &DuplicateLineUp,
11539 window: &mut Window,
11540 cx: &mut Context<Self>,
11541 ) {
11542 self.duplicate(true, true, window, cx);
11543 }
11544
11545 pub fn duplicate_line_down(
11546 &mut self,
11547 _: &DuplicateLineDown,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) {
11551 self.duplicate(false, true, window, cx);
11552 }
11553
11554 pub fn duplicate_selection(
11555 &mut self,
11556 _: &DuplicateSelection,
11557 window: &mut Window,
11558 cx: &mut Context<Self>,
11559 ) {
11560 self.duplicate(false, false, window, cx);
11561 }
11562
11563 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11564 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11565 if self.mode.is_single_line() {
11566 cx.propagate();
11567 return;
11568 }
11569
11570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11571 let buffer = self.buffer.read(cx).snapshot(cx);
11572
11573 let mut edits = Vec::new();
11574 let mut unfold_ranges = Vec::new();
11575 let mut refold_creases = Vec::new();
11576
11577 let selections = self.selections.all::<Point>(cx);
11578 let mut selections = selections.iter().peekable();
11579 let mut contiguous_row_selections = Vec::new();
11580 let mut new_selections = Vec::new();
11581
11582 while let Some(selection) = selections.next() {
11583 // Find all the selections that span a contiguous row range
11584 let (start_row, end_row) = consume_contiguous_rows(
11585 &mut contiguous_row_selections,
11586 selection,
11587 &display_map,
11588 &mut selections,
11589 );
11590
11591 // Move the text spanned by the row range to be before the line preceding the row range
11592 if start_row.0 > 0 {
11593 let range_to_move = Point::new(
11594 start_row.previous_row().0,
11595 buffer.line_len(start_row.previous_row()),
11596 )
11597 ..Point::new(
11598 end_row.previous_row().0,
11599 buffer.line_len(end_row.previous_row()),
11600 );
11601 let insertion_point = display_map
11602 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11603 .0;
11604
11605 // Don't move lines across excerpts
11606 if buffer
11607 .excerpt_containing(insertion_point..range_to_move.end)
11608 .is_some()
11609 {
11610 let text = buffer
11611 .text_for_range(range_to_move.clone())
11612 .flat_map(|s| s.chars())
11613 .skip(1)
11614 .chain(['\n'])
11615 .collect::<String>();
11616
11617 edits.push((
11618 buffer.anchor_after(range_to_move.start)
11619 ..buffer.anchor_before(range_to_move.end),
11620 String::new(),
11621 ));
11622 let insertion_anchor = buffer.anchor_after(insertion_point);
11623 edits.push((insertion_anchor..insertion_anchor, text));
11624
11625 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11626
11627 // Move selections up
11628 new_selections.extend(contiguous_row_selections.drain(..).map(
11629 |mut selection| {
11630 selection.start.row -= row_delta;
11631 selection.end.row -= row_delta;
11632 selection
11633 },
11634 ));
11635
11636 // Move folds up
11637 unfold_ranges.push(range_to_move.clone());
11638 for fold in display_map.folds_in_range(
11639 buffer.anchor_before(range_to_move.start)
11640 ..buffer.anchor_after(range_to_move.end),
11641 ) {
11642 let mut start = fold.range.start.to_point(&buffer);
11643 let mut end = fold.range.end.to_point(&buffer);
11644 start.row -= row_delta;
11645 end.row -= row_delta;
11646 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11647 }
11648 }
11649 }
11650
11651 // If we didn't move line(s), preserve the existing selections
11652 new_selections.append(&mut contiguous_row_selections);
11653 }
11654
11655 self.transact(window, cx, |this, window, cx| {
11656 this.unfold_ranges(&unfold_ranges, true, true, cx);
11657 this.buffer.update(cx, |buffer, cx| {
11658 for (range, text) in edits {
11659 buffer.edit([(range, text)], None, cx);
11660 }
11661 });
11662 this.fold_creases(refold_creases, true, window, cx);
11663 this.change_selections(Default::default(), window, cx, |s| {
11664 s.select(new_selections);
11665 })
11666 });
11667 }
11668
11669 pub fn move_line_down(
11670 &mut self,
11671 _: &MoveLineDown,
11672 window: &mut Window,
11673 cx: &mut Context<Self>,
11674 ) {
11675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11676 if self.mode.is_single_line() {
11677 cx.propagate();
11678 return;
11679 }
11680
11681 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11682 let buffer = self.buffer.read(cx).snapshot(cx);
11683
11684 let mut edits = Vec::new();
11685 let mut unfold_ranges = Vec::new();
11686 let mut refold_creases = Vec::new();
11687
11688 let selections = self.selections.all::<Point>(cx);
11689 let mut selections = selections.iter().peekable();
11690 let mut contiguous_row_selections = Vec::new();
11691 let mut new_selections = Vec::new();
11692
11693 while let Some(selection) = selections.next() {
11694 // Find all the selections that span a contiguous row range
11695 let (start_row, end_row) = consume_contiguous_rows(
11696 &mut contiguous_row_selections,
11697 selection,
11698 &display_map,
11699 &mut selections,
11700 );
11701
11702 // Move the text spanned by the row range to be after the last line of the row range
11703 if end_row.0 <= buffer.max_point().row {
11704 let range_to_move =
11705 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11706 let insertion_point = display_map
11707 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11708 .0;
11709
11710 // Don't move lines across excerpt boundaries
11711 if buffer
11712 .excerpt_containing(range_to_move.start..insertion_point)
11713 .is_some()
11714 {
11715 let mut text = String::from("\n");
11716 text.extend(buffer.text_for_range(range_to_move.clone()));
11717 text.pop(); // Drop trailing newline
11718 edits.push((
11719 buffer.anchor_after(range_to_move.start)
11720 ..buffer.anchor_before(range_to_move.end),
11721 String::new(),
11722 ));
11723 let insertion_anchor = buffer.anchor_after(insertion_point);
11724 edits.push((insertion_anchor..insertion_anchor, text));
11725
11726 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11727
11728 // Move selections down
11729 new_selections.extend(contiguous_row_selections.drain(..).map(
11730 |mut selection| {
11731 selection.start.row += row_delta;
11732 selection.end.row += row_delta;
11733 selection
11734 },
11735 ));
11736
11737 // Move folds down
11738 unfold_ranges.push(range_to_move.clone());
11739 for fold in display_map.folds_in_range(
11740 buffer.anchor_before(range_to_move.start)
11741 ..buffer.anchor_after(range_to_move.end),
11742 ) {
11743 let mut start = fold.range.start.to_point(&buffer);
11744 let mut end = fold.range.end.to_point(&buffer);
11745 start.row += row_delta;
11746 end.row += row_delta;
11747 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11748 }
11749 }
11750 }
11751
11752 // If we didn't move line(s), preserve the existing selections
11753 new_selections.append(&mut contiguous_row_selections);
11754 }
11755
11756 self.transact(window, cx, |this, window, cx| {
11757 this.unfold_ranges(&unfold_ranges, true, true, cx);
11758 this.buffer.update(cx, |buffer, cx| {
11759 for (range, text) in edits {
11760 buffer.edit([(range, text)], None, cx);
11761 }
11762 });
11763 this.fold_creases(refold_creases, true, window, cx);
11764 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11765 });
11766 }
11767
11768 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11770 let text_layout_details = &self.text_layout_details(window);
11771 self.transact(window, cx, |this, window, cx| {
11772 let edits = this.change_selections(Default::default(), window, cx, |s| {
11773 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11774 s.move_with(|display_map, selection| {
11775 if !selection.is_empty() {
11776 return;
11777 }
11778
11779 let mut head = selection.head();
11780 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11781 if head.column() == display_map.line_len(head.row()) {
11782 transpose_offset = display_map
11783 .buffer_snapshot
11784 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11785 }
11786
11787 if transpose_offset == 0 {
11788 return;
11789 }
11790
11791 *head.column_mut() += 1;
11792 head = display_map.clip_point(head, Bias::Right);
11793 let goal = SelectionGoal::HorizontalPosition(
11794 display_map
11795 .x_for_display_point(head, text_layout_details)
11796 .into(),
11797 );
11798 selection.collapse_to(head, goal);
11799
11800 let transpose_start = display_map
11801 .buffer_snapshot
11802 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11803 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11804 let transpose_end = display_map
11805 .buffer_snapshot
11806 .clip_offset(transpose_offset + 1, Bias::Right);
11807 if let Some(ch) =
11808 display_map.buffer_snapshot.chars_at(transpose_start).next()
11809 {
11810 edits.push((transpose_start..transpose_offset, String::new()));
11811 edits.push((transpose_end..transpose_end, ch.to_string()));
11812 }
11813 }
11814 });
11815 edits
11816 });
11817 this.buffer
11818 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11819 let selections = this.selections.all::<usize>(cx);
11820 this.change_selections(Default::default(), window, cx, |s| {
11821 s.select(selections);
11822 });
11823 });
11824 }
11825
11826 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11828 if self.mode.is_single_line() {
11829 cx.propagate();
11830 return;
11831 }
11832
11833 self.rewrap_impl(RewrapOptions::default(), cx)
11834 }
11835
11836 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11837 let buffer = self.buffer.read(cx).snapshot(cx);
11838 let selections = self.selections.all::<Point>(cx);
11839
11840 #[derive(Clone, Debug, PartialEq)]
11841 enum CommentFormat {
11842 /// single line comment, with prefix for line
11843 Line(String),
11844 /// single line within a block comment, with prefix for line
11845 BlockLine(String),
11846 /// a single line of a block comment that includes the initial delimiter
11847 BlockCommentWithStart(BlockCommentConfig),
11848 /// a single line of a block comment that includes the ending delimiter
11849 BlockCommentWithEnd(BlockCommentConfig),
11850 }
11851
11852 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11853 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11854 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11855 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11856 .peekable();
11857
11858 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11859 row
11860 } else {
11861 return Vec::new();
11862 };
11863
11864 let language_settings = buffer.language_settings_at(selection.head(), cx);
11865 let language_scope = buffer.language_scope_at(selection.head());
11866
11867 let indent_and_prefix_for_row =
11868 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11869 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11870 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11871 &language_scope
11872 {
11873 let indent_end = Point::new(row, indent.len);
11874 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11875 let line_text_after_indent = buffer
11876 .text_for_range(indent_end..line_end)
11877 .collect::<String>();
11878
11879 let is_within_comment_override = buffer
11880 .language_scope_at(indent_end)
11881 .is_some_and(|scope| scope.override_name() == Some("comment"));
11882 let comment_delimiters = if is_within_comment_override {
11883 // we are within a comment syntax node, but we don't
11884 // yet know what kind of comment: block, doc or line
11885 match (
11886 language_scope.documentation_comment(),
11887 language_scope.block_comment(),
11888 ) {
11889 (Some(config), _) | (_, Some(config))
11890 if buffer.contains_str_at(indent_end, &config.start) =>
11891 {
11892 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11893 }
11894 (Some(config), _) | (_, Some(config))
11895 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11896 {
11897 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11898 }
11899 (Some(config), _) | (_, Some(config))
11900 if buffer.contains_str_at(indent_end, &config.prefix) =>
11901 {
11902 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11903 }
11904 (_, _) => language_scope
11905 .line_comment_prefixes()
11906 .iter()
11907 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11908 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11909 }
11910 } else {
11911 // we not in an overridden comment node, but we may
11912 // be within a non-overridden line comment node
11913 language_scope
11914 .line_comment_prefixes()
11915 .iter()
11916 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11917 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11918 };
11919
11920 let rewrap_prefix = language_scope
11921 .rewrap_prefixes()
11922 .iter()
11923 .find_map(|prefix_regex| {
11924 prefix_regex.find(&line_text_after_indent).map(|mat| {
11925 if mat.start() == 0 {
11926 Some(mat.as_str().to_string())
11927 } else {
11928 None
11929 }
11930 })
11931 })
11932 .flatten();
11933 (comment_delimiters, rewrap_prefix)
11934 } else {
11935 (None, None)
11936 };
11937 (indent, comment_prefix, rewrap_prefix)
11938 };
11939
11940 let mut ranges = Vec::new();
11941 let from_empty_selection = selection.is_empty();
11942
11943 let mut current_range_start = first_row;
11944 let mut prev_row = first_row;
11945 let (
11946 mut current_range_indent,
11947 mut current_range_comment_delimiters,
11948 mut current_range_rewrap_prefix,
11949 ) = indent_and_prefix_for_row(first_row);
11950
11951 for row in non_blank_rows_iter.skip(1) {
11952 let has_paragraph_break = row > prev_row + 1;
11953
11954 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
11955 indent_and_prefix_for_row(row);
11956
11957 let has_indent_change = row_indent != current_range_indent;
11958 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
11959
11960 let has_boundary_change = has_comment_change
11961 || row_rewrap_prefix.is_some()
11962 || (has_indent_change && current_range_comment_delimiters.is_some());
11963
11964 if has_paragraph_break || has_boundary_change {
11965 ranges.push((
11966 language_settings.clone(),
11967 Point::new(current_range_start, 0)
11968 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11969 current_range_indent,
11970 current_range_comment_delimiters.clone(),
11971 current_range_rewrap_prefix.clone(),
11972 from_empty_selection,
11973 ));
11974 current_range_start = row;
11975 current_range_indent = row_indent;
11976 current_range_comment_delimiters = row_comment_delimiters;
11977 current_range_rewrap_prefix = row_rewrap_prefix;
11978 }
11979 prev_row = row;
11980 }
11981
11982 ranges.push((
11983 language_settings.clone(),
11984 Point::new(current_range_start, 0)
11985 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11986 current_range_indent,
11987 current_range_comment_delimiters,
11988 current_range_rewrap_prefix,
11989 from_empty_selection,
11990 ));
11991
11992 ranges
11993 });
11994
11995 let mut edits = Vec::new();
11996 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11997
11998 for (
11999 language_settings,
12000 wrap_range,
12001 mut indent_size,
12002 comment_prefix,
12003 rewrap_prefix,
12004 from_empty_selection,
12005 ) in wrap_ranges
12006 {
12007 let mut start_row = wrap_range.start.row;
12008 let mut end_row = wrap_range.end.row;
12009
12010 // Skip selections that overlap with a range that has already been rewrapped.
12011 let selection_range = start_row..end_row;
12012 if rewrapped_row_ranges
12013 .iter()
12014 .any(|range| range.overlaps(&selection_range))
12015 {
12016 continue;
12017 }
12018
12019 let tab_size = language_settings.tab_size;
12020
12021 let (line_prefix, inside_comment) = match &comment_prefix {
12022 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12023 (Some(prefix.as_str()), true)
12024 }
12025 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12026 (Some(prefix.as_ref()), true)
12027 }
12028 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12029 start: _,
12030 end: _,
12031 prefix,
12032 tab_size,
12033 })) => {
12034 indent_size.len += tab_size;
12035 (Some(prefix.as_ref()), true)
12036 }
12037 None => (None, false),
12038 };
12039 let indent_prefix = indent_size.chars().collect::<String>();
12040 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12041
12042 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12043 RewrapBehavior::InComments => inside_comment,
12044 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12045 RewrapBehavior::Anywhere => true,
12046 };
12047
12048 let should_rewrap = options.override_language_settings
12049 || allow_rewrap_based_on_language
12050 || self.hard_wrap.is_some();
12051 if !should_rewrap {
12052 continue;
12053 }
12054
12055 if from_empty_selection {
12056 'expand_upwards: while start_row > 0 {
12057 let prev_row = start_row - 1;
12058 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12059 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12060 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12061 {
12062 start_row = prev_row;
12063 } else {
12064 break 'expand_upwards;
12065 }
12066 }
12067
12068 'expand_downwards: while end_row < buffer.max_point().row {
12069 let next_row = end_row + 1;
12070 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12071 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12072 && !buffer.is_line_blank(MultiBufferRow(next_row))
12073 {
12074 end_row = next_row;
12075 } else {
12076 break 'expand_downwards;
12077 }
12078 }
12079 }
12080
12081 let start = Point::new(start_row, 0);
12082 let start_offset = start.to_offset(&buffer);
12083 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12084 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12085 let mut first_line_delimiter = None;
12086 let mut last_line_delimiter = None;
12087 let Some(lines_without_prefixes) = selection_text
12088 .lines()
12089 .enumerate()
12090 .map(|(ix, line)| {
12091 let line_trimmed = line.trim_start();
12092 if rewrap_prefix.is_some() && ix > 0 {
12093 Ok(line_trimmed)
12094 } else if let Some(
12095 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12096 start,
12097 prefix,
12098 end,
12099 tab_size,
12100 })
12101 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12102 start,
12103 prefix,
12104 end,
12105 tab_size,
12106 }),
12107 ) = &comment_prefix
12108 {
12109 let line_trimmed = line_trimmed
12110 .strip_prefix(start.as_ref())
12111 .map(|s| {
12112 let mut indent_size = indent_size;
12113 indent_size.len -= tab_size;
12114 let indent_prefix: String = indent_size.chars().collect();
12115 first_line_delimiter = Some((indent_prefix, start));
12116 s.trim_start()
12117 })
12118 .unwrap_or(line_trimmed);
12119 let line_trimmed = line_trimmed
12120 .strip_suffix(end.as_ref())
12121 .map(|s| {
12122 last_line_delimiter = Some(end);
12123 s.trim_end()
12124 })
12125 .unwrap_or(line_trimmed);
12126 let line_trimmed = line_trimmed
12127 .strip_prefix(prefix.as_ref())
12128 .unwrap_or(line_trimmed);
12129 Ok(line_trimmed)
12130 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12131 line_trimmed.strip_prefix(prefix).with_context(|| {
12132 format!("line did not start with prefix {prefix:?}: {line:?}")
12133 })
12134 } else {
12135 line_trimmed
12136 .strip_prefix(&line_prefix.trim_start())
12137 .with_context(|| {
12138 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12139 })
12140 }
12141 })
12142 .collect::<Result<Vec<_>, _>>()
12143 .log_err()
12144 else {
12145 continue;
12146 };
12147
12148 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12149 buffer
12150 .language_settings_at(Point::new(start_row, 0), cx)
12151 .preferred_line_length as usize
12152 });
12153
12154 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12155 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12156 } else {
12157 line_prefix.clone()
12158 };
12159
12160 let wrapped_text = {
12161 let mut wrapped_text = wrap_with_prefix(
12162 line_prefix,
12163 subsequent_lines_prefix,
12164 lines_without_prefixes.join("\n"),
12165 wrap_column,
12166 tab_size,
12167 options.preserve_existing_whitespace,
12168 );
12169
12170 if let Some((indent, delimiter)) = first_line_delimiter {
12171 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12172 }
12173 if let Some(last_line) = last_line_delimiter {
12174 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12175 }
12176
12177 wrapped_text
12178 };
12179
12180 // TODO: should always use char-based diff while still supporting cursor behavior that
12181 // matches vim.
12182 let mut diff_options = DiffOptions::default();
12183 if options.override_language_settings {
12184 diff_options.max_word_diff_len = 0;
12185 diff_options.max_word_diff_line_count = 0;
12186 } else {
12187 diff_options.max_word_diff_len = usize::MAX;
12188 diff_options.max_word_diff_line_count = usize::MAX;
12189 }
12190
12191 for (old_range, new_text) in
12192 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12193 {
12194 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12195 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12196 edits.push((edit_start..edit_end, new_text));
12197 }
12198
12199 rewrapped_row_ranges.push(start_row..=end_row);
12200 }
12201
12202 self.buffer
12203 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12204 }
12205
12206 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12207 let mut text = String::new();
12208 let buffer = self.buffer.read(cx).snapshot(cx);
12209 let mut selections = self.selections.all::<Point>(cx);
12210 let mut clipboard_selections = Vec::with_capacity(selections.len());
12211 {
12212 let max_point = buffer.max_point();
12213 let mut is_first = true;
12214 for selection in &mut selections {
12215 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12216 if is_entire_line {
12217 selection.start = Point::new(selection.start.row, 0);
12218 if !selection.is_empty() && selection.end.column == 0 {
12219 selection.end = cmp::min(max_point, selection.end);
12220 } else {
12221 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12222 }
12223 selection.goal = SelectionGoal::None;
12224 }
12225 if is_first {
12226 is_first = false;
12227 } else {
12228 text += "\n";
12229 }
12230 let mut len = 0;
12231 for chunk in buffer.text_for_range(selection.start..selection.end) {
12232 text.push_str(chunk);
12233 len += chunk.len();
12234 }
12235 clipboard_selections.push(ClipboardSelection {
12236 len,
12237 is_entire_line,
12238 first_line_indent: buffer
12239 .indent_size_for_line(MultiBufferRow(selection.start.row))
12240 .len,
12241 });
12242 }
12243 }
12244
12245 self.transact(window, cx, |this, window, cx| {
12246 this.change_selections(Default::default(), window, cx, |s| {
12247 s.select(selections);
12248 });
12249 this.insert("", window, cx);
12250 });
12251 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12252 }
12253
12254 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12255 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12256 let item = self.cut_common(window, cx);
12257 cx.write_to_clipboard(item);
12258 }
12259
12260 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12261 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12262 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12263 s.move_with(|snapshot, sel| {
12264 if sel.is_empty() {
12265 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12266 }
12267 });
12268 });
12269 let item = self.cut_common(window, cx);
12270 cx.set_global(KillRing(item))
12271 }
12272
12273 pub fn kill_ring_yank(
12274 &mut self,
12275 _: &KillRingYank,
12276 window: &mut Window,
12277 cx: &mut Context<Self>,
12278 ) {
12279 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12280 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12281 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12282 (kill_ring.text().to_string(), kill_ring.metadata_json())
12283 } else {
12284 return;
12285 }
12286 } else {
12287 return;
12288 };
12289 self.do_paste(&text, metadata, false, window, cx);
12290 }
12291
12292 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12293 self.do_copy(true, cx);
12294 }
12295
12296 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12297 self.do_copy(false, cx);
12298 }
12299
12300 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12301 let selections = self.selections.all::<Point>(cx);
12302 let buffer = self.buffer.read(cx).read(cx);
12303 let mut text = String::new();
12304
12305 let mut clipboard_selections = Vec::with_capacity(selections.len());
12306 {
12307 let max_point = buffer.max_point();
12308 let mut is_first = true;
12309 for selection in &selections {
12310 let mut start = selection.start;
12311 let mut end = selection.end;
12312 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12313 if is_entire_line {
12314 start = Point::new(start.row, 0);
12315 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12316 }
12317
12318 let mut trimmed_selections = Vec::new();
12319 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12320 let row = MultiBufferRow(start.row);
12321 let first_indent = buffer.indent_size_for_line(row);
12322 if first_indent.len == 0 || start.column > first_indent.len {
12323 trimmed_selections.push(start..end);
12324 } else {
12325 trimmed_selections.push(
12326 Point::new(row.0, first_indent.len)
12327 ..Point::new(row.0, buffer.line_len(row)),
12328 );
12329 for row in start.row + 1..=end.row {
12330 let mut line_len = buffer.line_len(MultiBufferRow(row));
12331 if row == end.row {
12332 line_len = end.column;
12333 }
12334 if line_len == 0 {
12335 trimmed_selections
12336 .push(Point::new(row, 0)..Point::new(row, line_len));
12337 continue;
12338 }
12339 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12340 if row_indent_size.len >= first_indent.len {
12341 trimmed_selections.push(
12342 Point::new(row, first_indent.len)..Point::new(row, line_len),
12343 );
12344 } else {
12345 trimmed_selections.clear();
12346 trimmed_selections.push(start..end);
12347 break;
12348 }
12349 }
12350 }
12351 } else {
12352 trimmed_selections.push(start..end);
12353 }
12354
12355 for trimmed_range in trimmed_selections {
12356 if is_first {
12357 is_first = false;
12358 } else {
12359 text += "\n";
12360 }
12361 let mut len = 0;
12362 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12363 text.push_str(chunk);
12364 len += chunk.len();
12365 }
12366 clipboard_selections.push(ClipboardSelection {
12367 len,
12368 is_entire_line,
12369 first_line_indent: buffer
12370 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12371 .len,
12372 });
12373 }
12374 }
12375 }
12376
12377 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12378 text,
12379 clipboard_selections,
12380 ));
12381 }
12382
12383 pub fn do_paste(
12384 &mut self,
12385 text: &String,
12386 clipboard_selections: Option<Vec<ClipboardSelection>>,
12387 handle_entire_lines: bool,
12388 window: &mut Window,
12389 cx: &mut Context<Self>,
12390 ) {
12391 if self.read_only(cx) {
12392 return;
12393 }
12394
12395 let clipboard_text = Cow::Borrowed(text);
12396
12397 self.transact(window, cx, |this, window, cx| {
12398 let had_active_edit_prediction = this.has_active_edit_prediction();
12399
12400 if let Some(mut clipboard_selections) = clipboard_selections {
12401 let old_selections = this.selections.all::<usize>(cx);
12402 let all_selections_were_entire_line =
12403 clipboard_selections.iter().all(|s| s.is_entire_line);
12404 let first_selection_indent_column =
12405 clipboard_selections.first().map(|s| s.first_line_indent);
12406 if clipboard_selections.len() != old_selections.len() {
12407 clipboard_selections.drain(..);
12408 }
12409 let cursor_offset = this.selections.last::<usize>(cx).head();
12410 let mut auto_indent_on_paste = true;
12411
12412 this.buffer.update(cx, |buffer, cx| {
12413 let snapshot = buffer.read(cx);
12414 auto_indent_on_paste = snapshot
12415 .language_settings_at(cursor_offset, cx)
12416 .auto_indent_on_paste;
12417
12418 let mut start_offset = 0;
12419 let mut edits = Vec::new();
12420 let mut original_indent_columns = Vec::new();
12421 for (ix, selection) in old_selections.iter().enumerate() {
12422 let to_insert;
12423 let entire_line;
12424 let original_indent_column;
12425 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12426 let end_offset = start_offset + clipboard_selection.len;
12427 to_insert = &clipboard_text[start_offset..end_offset];
12428 entire_line = clipboard_selection.is_entire_line;
12429 start_offset = end_offset + 1;
12430 original_indent_column = Some(clipboard_selection.first_line_indent);
12431 } else {
12432 to_insert = clipboard_text.as_str();
12433 entire_line = all_selections_were_entire_line;
12434 original_indent_column = first_selection_indent_column
12435 }
12436
12437 // If the corresponding selection was empty when this slice of the
12438 // clipboard text was written, then the entire line containing the
12439 // selection was copied. If this selection is also currently empty,
12440 // then paste the line before the current line of the buffer.
12441 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12442 let column = selection.start.to_point(&snapshot).column as usize;
12443 let line_start = selection.start - column;
12444 line_start..line_start
12445 } else {
12446 selection.range()
12447 };
12448
12449 edits.push((range, to_insert));
12450 original_indent_columns.push(original_indent_column);
12451 }
12452 drop(snapshot);
12453
12454 buffer.edit(
12455 edits,
12456 if auto_indent_on_paste {
12457 Some(AutoindentMode::Block {
12458 original_indent_columns,
12459 })
12460 } else {
12461 None
12462 },
12463 cx,
12464 );
12465 });
12466
12467 let selections = this.selections.all::<usize>(cx);
12468 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12469 } else {
12470 this.insert(&clipboard_text, window, cx);
12471 }
12472
12473 let trigger_in_words =
12474 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12475
12476 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12477 });
12478 }
12479
12480 pub fn diff_clipboard_with_selection(
12481 &mut self,
12482 _: &DiffClipboardWithSelection,
12483 window: &mut Window,
12484 cx: &mut Context<Self>,
12485 ) {
12486 let selections = self.selections.all::<usize>(cx);
12487
12488 if selections.is_empty() {
12489 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12490 return;
12491 };
12492
12493 let clipboard_text = match cx.read_from_clipboard() {
12494 Some(item) => match item.entries().first() {
12495 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12496 _ => None,
12497 },
12498 None => None,
12499 };
12500
12501 let Some(clipboard_text) = clipboard_text else {
12502 log::warn!("Clipboard doesn't contain text.");
12503 return;
12504 };
12505
12506 window.dispatch_action(
12507 Box::new(DiffClipboardWithSelectionData {
12508 clipboard_text,
12509 editor: cx.entity(),
12510 }),
12511 cx,
12512 );
12513 }
12514
12515 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12516 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12517 if let Some(item) = cx.read_from_clipboard() {
12518 let entries = item.entries();
12519
12520 match entries.first() {
12521 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12522 // of all the pasted entries.
12523 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12524 .do_paste(
12525 clipboard_string.text(),
12526 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12527 true,
12528 window,
12529 cx,
12530 ),
12531 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12532 }
12533 }
12534 }
12535
12536 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12537 if self.read_only(cx) {
12538 return;
12539 }
12540
12541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12542
12543 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12544 if let Some((selections, _)) =
12545 self.selection_history.transaction(transaction_id).cloned()
12546 {
12547 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12548 s.select_anchors(selections.to_vec());
12549 });
12550 } else {
12551 log::error!(
12552 "No entry in selection_history found for undo. \
12553 This may correspond to a bug where undo does not update the selection. \
12554 If this is occurring, please add details to \
12555 https://github.com/zed-industries/zed/issues/22692"
12556 );
12557 }
12558 self.request_autoscroll(Autoscroll::fit(), cx);
12559 self.unmark_text(window, cx);
12560 self.refresh_edit_prediction(true, false, window, cx);
12561 cx.emit(EditorEvent::Edited { transaction_id });
12562 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12563 }
12564 }
12565
12566 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12567 if self.read_only(cx) {
12568 return;
12569 }
12570
12571 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12572
12573 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12574 if let Some((_, Some(selections))) =
12575 self.selection_history.transaction(transaction_id).cloned()
12576 {
12577 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12578 s.select_anchors(selections.to_vec());
12579 });
12580 } else {
12581 log::error!(
12582 "No entry in selection_history found for redo. \
12583 This may correspond to a bug where undo does not update the selection. \
12584 If this is occurring, please add details to \
12585 https://github.com/zed-industries/zed/issues/22692"
12586 );
12587 }
12588 self.request_autoscroll(Autoscroll::fit(), cx);
12589 self.unmark_text(window, cx);
12590 self.refresh_edit_prediction(true, false, window, cx);
12591 cx.emit(EditorEvent::Edited { transaction_id });
12592 }
12593 }
12594
12595 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12596 self.buffer
12597 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12598 }
12599
12600 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12601 self.buffer
12602 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12603 }
12604
12605 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12607 self.change_selections(Default::default(), window, cx, |s| {
12608 s.move_with(|map, selection| {
12609 let cursor = if selection.is_empty() {
12610 movement::left(map, selection.start)
12611 } else {
12612 selection.start
12613 };
12614 selection.collapse_to(cursor, SelectionGoal::None);
12615 });
12616 })
12617 }
12618
12619 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12621 self.change_selections(Default::default(), window, cx, |s| {
12622 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12623 })
12624 }
12625
12626 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12627 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12628 self.change_selections(Default::default(), window, cx, |s| {
12629 s.move_with(|map, selection| {
12630 let cursor = if selection.is_empty() {
12631 movement::right(map, selection.end)
12632 } else {
12633 selection.end
12634 };
12635 selection.collapse_to(cursor, SelectionGoal::None)
12636 });
12637 })
12638 }
12639
12640 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12642 self.change_selections(Default::default(), window, cx, |s| {
12643 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12644 })
12645 }
12646
12647 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12648 if self.take_rename(true, window, cx).is_some() {
12649 return;
12650 }
12651
12652 if self.mode.is_single_line() {
12653 cx.propagate();
12654 return;
12655 }
12656
12657 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12658
12659 let text_layout_details = &self.text_layout_details(window);
12660 let selection_count = self.selections.count();
12661 let first_selection = self.selections.first_anchor();
12662
12663 self.change_selections(Default::default(), window, cx, |s| {
12664 s.move_with(|map, selection| {
12665 if !selection.is_empty() {
12666 selection.goal = SelectionGoal::None;
12667 }
12668 let (cursor, goal) = movement::up(
12669 map,
12670 selection.start,
12671 selection.goal,
12672 false,
12673 text_layout_details,
12674 );
12675 selection.collapse_to(cursor, goal);
12676 });
12677 });
12678
12679 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12680 {
12681 cx.propagate();
12682 }
12683 }
12684
12685 pub fn move_up_by_lines(
12686 &mut self,
12687 action: &MoveUpByLines,
12688 window: &mut Window,
12689 cx: &mut Context<Self>,
12690 ) {
12691 if self.take_rename(true, window, cx).is_some() {
12692 return;
12693 }
12694
12695 if self.mode.is_single_line() {
12696 cx.propagate();
12697 return;
12698 }
12699
12700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12701
12702 let text_layout_details = &self.text_layout_details(window);
12703
12704 self.change_selections(Default::default(), window, cx, |s| {
12705 s.move_with(|map, selection| {
12706 if !selection.is_empty() {
12707 selection.goal = SelectionGoal::None;
12708 }
12709 let (cursor, goal) = movement::up_by_rows(
12710 map,
12711 selection.start,
12712 action.lines,
12713 selection.goal,
12714 false,
12715 text_layout_details,
12716 );
12717 selection.collapse_to(cursor, goal);
12718 });
12719 })
12720 }
12721
12722 pub fn move_down_by_lines(
12723 &mut self,
12724 action: &MoveDownByLines,
12725 window: &mut Window,
12726 cx: &mut Context<Self>,
12727 ) {
12728 if self.take_rename(true, window, cx).is_some() {
12729 return;
12730 }
12731
12732 if self.mode.is_single_line() {
12733 cx.propagate();
12734 return;
12735 }
12736
12737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12738
12739 let text_layout_details = &self.text_layout_details(window);
12740
12741 self.change_selections(Default::default(), window, cx, |s| {
12742 s.move_with(|map, selection| {
12743 if !selection.is_empty() {
12744 selection.goal = SelectionGoal::None;
12745 }
12746 let (cursor, goal) = movement::down_by_rows(
12747 map,
12748 selection.start,
12749 action.lines,
12750 selection.goal,
12751 false,
12752 text_layout_details,
12753 );
12754 selection.collapse_to(cursor, goal);
12755 });
12756 })
12757 }
12758
12759 pub fn select_down_by_lines(
12760 &mut self,
12761 action: &SelectDownByLines,
12762 window: &mut Window,
12763 cx: &mut Context<Self>,
12764 ) {
12765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12766 let text_layout_details = &self.text_layout_details(window);
12767 self.change_selections(Default::default(), window, cx, |s| {
12768 s.move_heads_with(|map, head, goal| {
12769 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12770 })
12771 })
12772 }
12773
12774 pub fn select_up_by_lines(
12775 &mut self,
12776 action: &SelectUpByLines,
12777 window: &mut Window,
12778 cx: &mut Context<Self>,
12779 ) {
12780 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12781 let text_layout_details = &self.text_layout_details(window);
12782 self.change_selections(Default::default(), window, cx, |s| {
12783 s.move_heads_with(|map, head, goal| {
12784 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12785 })
12786 })
12787 }
12788
12789 pub fn select_page_up(
12790 &mut self,
12791 _: &SelectPageUp,
12792 window: &mut Window,
12793 cx: &mut Context<Self>,
12794 ) {
12795 let Some(row_count) = self.visible_row_count() else {
12796 return;
12797 };
12798
12799 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12800
12801 let text_layout_details = &self.text_layout_details(window);
12802
12803 self.change_selections(Default::default(), window, cx, |s| {
12804 s.move_heads_with(|map, head, goal| {
12805 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12806 })
12807 })
12808 }
12809
12810 pub fn move_page_up(
12811 &mut self,
12812 action: &MovePageUp,
12813 window: &mut Window,
12814 cx: &mut Context<Self>,
12815 ) {
12816 if self.take_rename(true, window, cx).is_some() {
12817 return;
12818 }
12819
12820 if self
12821 .context_menu
12822 .borrow_mut()
12823 .as_mut()
12824 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12825 .unwrap_or(false)
12826 {
12827 return;
12828 }
12829
12830 if matches!(self.mode, EditorMode::SingleLine) {
12831 cx.propagate();
12832 return;
12833 }
12834
12835 let Some(row_count) = self.visible_row_count() else {
12836 return;
12837 };
12838
12839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12840
12841 let effects = if action.center_cursor {
12842 SelectionEffects::scroll(Autoscroll::center())
12843 } else {
12844 SelectionEffects::default()
12845 };
12846
12847 let text_layout_details = &self.text_layout_details(window);
12848
12849 self.change_selections(effects, window, cx, |s| {
12850 s.move_with(|map, selection| {
12851 if !selection.is_empty() {
12852 selection.goal = SelectionGoal::None;
12853 }
12854 let (cursor, goal) = movement::up_by_rows(
12855 map,
12856 selection.end,
12857 row_count,
12858 selection.goal,
12859 false,
12860 text_layout_details,
12861 );
12862 selection.collapse_to(cursor, goal);
12863 });
12864 });
12865 }
12866
12867 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12869 let text_layout_details = &self.text_layout_details(window);
12870 self.change_selections(Default::default(), window, cx, |s| {
12871 s.move_heads_with(|map, head, goal| {
12872 movement::up(map, head, goal, false, text_layout_details)
12873 })
12874 })
12875 }
12876
12877 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12878 self.take_rename(true, window, cx);
12879
12880 if self.mode.is_single_line() {
12881 cx.propagate();
12882 return;
12883 }
12884
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12886
12887 let text_layout_details = &self.text_layout_details(window);
12888 let selection_count = self.selections.count();
12889 let first_selection = self.selections.first_anchor();
12890
12891 self.change_selections(Default::default(), window, cx, |s| {
12892 s.move_with(|map, selection| {
12893 if !selection.is_empty() {
12894 selection.goal = SelectionGoal::None;
12895 }
12896 let (cursor, goal) = movement::down(
12897 map,
12898 selection.end,
12899 selection.goal,
12900 false,
12901 text_layout_details,
12902 );
12903 selection.collapse_to(cursor, goal);
12904 });
12905 });
12906
12907 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12908 {
12909 cx.propagate();
12910 }
12911 }
12912
12913 pub fn select_page_down(
12914 &mut self,
12915 _: &SelectPageDown,
12916 window: &mut Window,
12917 cx: &mut Context<Self>,
12918 ) {
12919 let Some(row_count) = self.visible_row_count() else {
12920 return;
12921 };
12922
12923 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12924
12925 let text_layout_details = &self.text_layout_details(window);
12926
12927 self.change_selections(Default::default(), window, cx, |s| {
12928 s.move_heads_with(|map, head, goal| {
12929 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12930 })
12931 })
12932 }
12933
12934 pub fn move_page_down(
12935 &mut self,
12936 action: &MovePageDown,
12937 window: &mut Window,
12938 cx: &mut Context<Self>,
12939 ) {
12940 if self.take_rename(true, window, cx).is_some() {
12941 return;
12942 }
12943
12944 if self
12945 .context_menu
12946 .borrow_mut()
12947 .as_mut()
12948 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12949 .unwrap_or(false)
12950 {
12951 return;
12952 }
12953
12954 if matches!(self.mode, EditorMode::SingleLine) {
12955 cx.propagate();
12956 return;
12957 }
12958
12959 let Some(row_count) = self.visible_row_count() else {
12960 return;
12961 };
12962
12963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12964
12965 let effects = if action.center_cursor {
12966 SelectionEffects::scroll(Autoscroll::center())
12967 } else {
12968 SelectionEffects::default()
12969 };
12970
12971 let text_layout_details = &self.text_layout_details(window);
12972 self.change_selections(effects, window, cx, |s| {
12973 s.move_with(|map, selection| {
12974 if !selection.is_empty() {
12975 selection.goal = SelectionGoal::None;
12976 }
12977 let (cursor, goal) = movement::down_by_rows(
12978 map,
12979 selection.end,
12980 row_count,
12981 selection.goal,
12982 false,
12983 text_layout_details,
12984 );
12985 selection.collapse_to(cursor, goal);
12986 });
12987 });
12988 }
12989
12990 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12992 let text_layout_details = &self.text_layout_details(window);
12993 self.change_selections(Default::default(), window, cx, |s| {
12994 s.move_heads_with(|map, head, goal| {
12995 movement::down(map, head, goal, false, text_layout_details)
12996 })
12997 });
12998 }
12999
13000 pub fn context_menu_first(
13001 &mut self,
13002 _: &ContextMenuFirst,
13003 window: &mut Window,
13004 cx: &mut Context<Self>,
13005 ) {
13006 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13007 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13008 }
13009 }
13010
13011 pub fn context_menu_prev(
13012 &mut self,
13013 _: &ContextMenuPrevious,
13014 window: &mut Window,
13015 cx: &mut Context<Self>,
13016 ) {
13017 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13018 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13019 }
13020 }
13021
13022 pub fn context_menu_next(
13023 &mut self,
13024 _: &ContextMenuNext,
13025 window: &mut Window,
13026 cx: &mut Context<Self>,
13027 ) {
13028 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13029 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13030 }
13031 }
13032
13033 pub fn context_menu_last(
13034 &mut self,
13035 _: &ContextMenuLast,
13036 window: &mut Window,
13037 cx: &mut Context<Self>,
13038 ) {
13039 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13040 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13041 }
13042 }
13043
13044 pub fn signature_help_prev(
13045 &mut self,
13046 _: &SignatureHelpPrevious,
13047 _: &mut Window,
13048 cx: &mut Context<Self>,
13049 ) {
13050 if let Some(popover) = self.signature_help_state.popover_mut() {
13051 if popover.current_signature == 0 {
13052 popover.current_signature = popover.signatures.len() - 1;
13053 } else {
13054 popover.current_signature -= 1;
13055 }
13056 cx.notify();
13057 }
13058 }
13059
13060 pub fn signature_help_next(
13061 &mut self,
13062 _: &SignatureHelpNext,
13063 _: &mut Window,
13064 cx: &mut Context<Self>,
13065 ) {
13066 if let Some(popover) = self.signature_help_state.popover_mut() {
13067 if popover.current_signature + 1 == popover.signatures.len() {
13068 popover.current_signature = 0;
13069 } else {
13070 popover.current_signature += 1;
13071 }
13072 cx.notify();
13073 }
13074 }
13075
13076 pub fn move_to_previous_word_start(
13077 &mut self,
13078 _: &MoveToPreviousWordStart,
13079 window: &mut Window,
13080 cx: &mut Context<Self>,
13081 ) {
13082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13083 self.change_selections(Default::default(), window, cx, |s| {
13084 s.move_cursors_with(|map, head, _| {
13085 (
13086 movement::previous_word_start(map, head),
13087 SelectionGoal::None,
13088 )
13089 });
13090 })
13091 }
13092
13093 pub fn move_to_previous_subword_start(
13094 &mut self,
13095 _: &MoveToPreviousSubwordStart,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13100 self.change_selections(Default::default(), window, cx, |s| {
13101 s.move_cursors_with(|map, head, _| {
13102 (
13103 movement::previous_subword_start(map, head),
13104 SelectionGoal::None,
13105 )
13106 });
13107 })
13108 }
13109
13110 pub fn select_to_previous_word_start(
13111 &mut self,
13112 _: &SelectToPreviousWordStart,
13113 window: &mut Window,
13114 cx: &mut Context<Self>,
13115 ) {
13116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13117 self.change_selections(Default::default(), window, cx, |s| {
13118 s.move_heads_with(|map, head, _| {
13119 (
13120 movement::previous_word_start(map, head),
13121 SelectionGoal::None,
13122 )
13123 });
13124 })
13125 }
13126
13127 pub fn select_to_previous_subword_start(
13128 &mut self,
13129 _: &SelectToPreviousSubwordStart,
13130 window: &mut Window,
13131 cx: &mut Context<Self>,
13132 ) {
13133 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13134 self.change_selections(Default::default(), window, cx, |s| {
13135 s.move_heads_with(|map, head, _| {
13136 (
13137 movement::previous_subword_start(map, head),
13138 SelectionGoal::None,
13139 )
13140 });
13141 })
13142 }
13143
13144 pub fn delete_to_previous_word_start(
13145 &mut self,
13146 action: &DeleteToPreviousWordStart,
13147 window: &mut Window,
13148 cx: &mut Context<Self>,
13149 ) {
13150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13151 self.transact(window, cx, |this, window, cx| {
13152 this.select_autoclose_pair(window, cx);
13153 this.change_selections(Default::default(), window, cx, |s| {
13154 s.move_with(|map, selection| {
13155 if selection.is_empty() {
13156 let mut cursor = if action.ignore_newlines {
13157 movement::previous_word_start(map, selection.head())
13158 } else {
13159 movement::previous_word_start_or_newline(map, selection.head())
13160 };
13161 cursor = movement::adjust_greedy_deletion(
13162 map,
13163 selection.head(),
13164 cursor,
13165 action.ignore_brackets,
13166 );
13167 selection.set_head(cursor, SelectionGoal::None);
13168 }
13169 });
13170 });
13171 this.insert("", window, cx);
13172 });
13173 }
13174
13175 pub fn delete_to_previous_subword_start(
13176 &mut self,
13177 _: &DeleteToPreviousSubwordStart,
13178 window: &mut Window,
13179 cx: &mut Context<Self>,
13180 ) {
13181 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13182 self.transact(window, cx, |this, window, cx| {
13183 this.select_autoclose_pair(window, cx);
13184 this.change_selections(Default::default(), window, cx, |s| {
13185 s.move_with(|map, selection| {
13186 if selection.is_empty() {
13187 let mut cursor = movement::previous_subword_start(map, selection.head());
13188 cursor =
13189 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13190 selection.set_head(cursor, SelectionGoal::None);
13191 }
13192 });
13193 });
13194 this.insert("", window, cx);
13195 });
13196 }
13197
13198 pub fn move_to_next_word_end(
13199 &mut self,
13200 _: &MoveToNextWordEnd,
13201 window: &mut Window,
13202 cx: &mut Context<Self>,
13203 ) {
13204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13205 self.change_selections(Default::default(), window, cx, |s| {
13206 s.move_cursors_with(|map, head, _| {
13207 (movement::next_word_end(map, head), SelectionGoal::None)
13208 });
13209 })
13210 }
13211
13212 pub fn move_to_next_subword_end(
13213 &mut self,
13214 _: &MoveToNextSubwordEnd,
13215 window: &mut Window,
13216 cx: &mut Context<Self>,
13217 ) {
13218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13219 self.change_selections(Default::default(), window, cx, |s| {
13220 s.move_cursors_with(|map, head, _| {
13221 (movement::next_subword_end(map, head), SelectionGoal::None)
13222 });
13223 })
13224 }
13225
13226 pub fn select_to_next_word_end(
13227 &mut self,
13228 _: &SelectToNextWordEnd,
13229 window: &mut Window,
13230 cx: &mut Context<Self>,
13231 ) {
13232 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13233 self.change_selections(Default::default(), window, cx, |s| {
13234 s.move_heads_with(|map, head, _| {
13235 (movement::next_word_end(map, head), SelectionGoal::None)
13236 });
13237 })
13238 }
13239
13240 pub fn select_to_next_subword_end(
13241 &mut self,
13242 _: &SelectToNextSubwordEnd,
13243 window: &mut Window,
13244 cx: &mut Context<Self>,
13245 ) {
13246 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13247 self.change_selections(Default::default(), window, cx, |s| {
13248 s.move_heads_with(|map, head, _| {
13249 (movement::next_subword_end(map, head), SelectionGoal::None)
13250 });
13251 })
13252 }
13253
13254 pub fn delete_to_next_word_end(
13255 &mut self,
13256 action: &DeleteToNextWordEnd,
13257 window: &mut Window,
13258 cx: &mut Context<Self>,
13259 ) {
13260 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13261 self.transact(window, cx, |this, window, cx| {
13262 this.change_selections(Default::default(), window, cx, |s| {
13263 s.move_with(|map, selection| {
13264 if selection.is_empty() {
13265 let mut cursor = if action.ignore_newlines {
13266 movement::next_word_end(map, selection.head())
13267 } else {
13268 movement::next_word_end_or_newline(map, selection.head())
13269 };
13270 cursor = movement::adjust_greedy_deletion(
13271 map,
13272 selection.head(),
13273 cursor,
13274 action.ignore_brackets,
13275 );
13276 selection.set_head(cursor, SelectionGoal::None);
13277 }
13278 });
13279 });
13280 this.insert("", window, cx);
13281 });
13282 }
13283
13284 pub fn delete_to_next_subword_end(
13285 &mut self,
13286 _: &DeleteToNextSubwordEnd,
13287 window: &mut Window,
13288 cx: &mut Context<Self>,
13289 ) {
13290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13291 self.transact(window, cx, |this, window, cx| {
13292 this.change_selections(Default::default(), window, cx, |s| {
13293 s.move_with(|map, selection| {
13294 if selection.is_empty() {
13295 let mut cursor = movement::next_subword_end(map, selection.head());
13296 cursor =
13297 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13298 selection.set_head(cursor, SelectionGoal::None);
13299 }
13300 });
13301 });
13302 this.insert("", window, cx);
13303 });
13304 }
13305
13306 pub fn move_to_beginning_of_line(
13307 &mut self,
13308 action: &MoveToBeginningOfLine,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13313 self.change_selections(Default::default(), window, cx, |s| {
13314 s.move_cursors_with(|map, head, _| {
13315 (
13316 movement::indented_line_beginning(
13317 map,
13318 head,
13319 action.stop_at_soft_wraps,
13320 action.stop_at_indent,
13321 ),
13322 SelectionGoal::None,
13323 )
13324 });
13325 })
13326 }
13327
13328 pub fn select_to_beginning_of_line(
13329 &mut self,
13330 action: &SelectToBeginningOfLine,
13331 window: &mut Window,
13332 cx: &mut Context<Self>,
13333 ) {
13334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13335 self.change_selections(Default::default(), window, cx, |s| {
13336 s.move_heads_with(|map, head, _| {
13337 (
13338 movement::indented_line_beginning(
13339 map,
13340 head,
13341 action.stop_at_soft_wraps,
13342 action.stop_at_indent,
13343 ),
13344 SelectionGoal::None,
13345 )
13346 });
13347 });
13348 }
13349
13350 pub fn delete_to_beginning_of_line(
13351 &mut self,
13352 action: &DeleteToBeginningOfLine,
13353 window: &mut Window,
13354 cx: &mut Context<Self>,
13355 ) {
13356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13357 self.transact(window, cx, |this, window, cx| {
13358 this.change_selections(Default::default(), window, cx, |s| {
13359 s.move_with(|_, selection| {
13360 selection.reversed = true;
13361 });
13362 });
13363
13364 this.select_to_beginning_of_line(
13365 &SelectToBeginningOfLine {
13366 stop_at_soft_wraps: false,
13367 stop_at_indent: action.stop_at_indent,
13368 },
13369 window,
13370 cx,
13371 );
13372 this.backspace(&Backspace, window, cx);
13373 });
13374 }
13375
13376 pub fn move_to_end_of_line(
13377 &mut self,
13378 action: &MoveToEndOfLine,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383 self.change_selections(Default::default(), window, cx, |s| {
13384 s.move_cursors_with(|map, head, _| {
13385 (
13386 movement::line_end(map, head, action.stop_at_soft_wraps),
13387 SelectionGoal::None,
13388 )
13389 });
13390 })
13391 }
13392
13393 pub fn select_to_end_of_line(
13394 &mut self,
13395 action: &SelectToEndOfLine,
13396 window: &mut Window,
13397 cx: &mut Context<Self>,
13398 ) {
13399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13400 self.change_selections(Default::default(), window, cx, |s| {
13401 s.move_heads_with(|map, head, _| {
13402 (
13403 movement::line_end(map, head, action.stop_at_soft_wraps),
13404 SelectionGoal::None,
13405 )
13406 });
13407 })
13408 }
13409
13410 pub fn delete_to_end_of_line(
13411 &mut self,
13412 _: &DeleteToEndOfLine,
13413 window: &mut Window,
13414 cx: &mut Context<Self>,
13415 ) {
13416 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13417 self.transact(window, cx, |this, window, cx| {
13418 this.select_to_end_of_line(
13419 &SelectToEndOfLine {
13420 stop_at_soft_wraps: false,
13421 },
13422 window,
13423 cx,
13424 );
13425 this.delete(&Delete, window, cx);
13426 });
13427 }
13428
13429 pub fn cut_to_end_of_line(
13430 &mut self,
13431 _: &CutToEndOfLine,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13436 self.transact(window, cx, |this, window, cx| {
13437 this.select_to_end_of_line(
13438 &SelectToEndOfLine {
13439 stop_at_soft_wraps: false,
13440 },
13441 window,
13442 cx,
13443 );
13444 this.cut(&Cut, window, cx);
13445 });
13446 }
13447
13448 pub fn move_to_start_of_paragraph(
13449 &mut self,
13450 _: &MoveToStartOfParagraph,
13451 window: &mut Window,
13452 cx: &mut Context<Self>,
13453 ) {
13454 if matches!(self.mode, EditorMode::SingleLine) {
13455 cx.propagate();
13456 return;
13457 }
13458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13459 self.change_selections(Default::default(), window, cx, |s| {
13460 s.move_with(|map, selection| {
13461 selection.collapse_to(
13462 movement::start_of_paragraph(map, selection.head(), 1),
13463 SelectionGoal::None,
13464 )
13465 });
13466 })
13467 }
13468
13469 pub fn move_to_end_of_paragraph(
13470 &mut self,
13471 _: &MoveToEndOfParagraph,
13472 window: &mut Window,
13473 cx: &mut Context<Self>,
13474 ) {
13475 if matches!(self.mode, EditorMode::SingleLine) {
13476 cx.propagate();
13477 return;
13478 }
13479 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13480 self.change_selections(Default::default(), window, cx, |s| {
13481 s.move_with(|map, selection| {
13482 selection.collapse_to(
13483 movement::end_of_paragraph(map, selection.head(), 1),
13484 SelectionGoal::None,
13485 )
13486 });
13487 })
13488 }
13489
13490 pub fn select_to_start_of_paragraph(
13491 &mut self,
13492 _: &SelectToStartOfParagraph,
13493 window: &mut Window,
13494 cx: &mut Context<Self>,
13495 ) {
13496 if matches!(self.mode, EditorMode::SingleLine) {
13497 cx.propagate();
13498 return;
13499 }
13500 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13501 self.change_selections(Default::default(), window, cx, |s| {
13502 s.move_heads_with(|map, head, _| {
13503 (
13504 movement::start_of_paragraph(map, head, 1),
13505 SelectionGoal::None,
13506 )
13507 });
13508 })
13509 }
13510
13511 pub fn select_to_end_of_paragraph(
13512 &mut self,
13513 _: &SelectToEndOfParagraph,
13514 window: &mut Window,
13515 cx: &mut Context<Self>,
13516 ) {
13517 if matches!(self.mode, EditorMode::SingleLine) {
13518 cx.propagate();
13519 return;
13520 }
13521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13522 self.change_selections(Default::default(), window, cx, |s| {
13523 s.move_heads_with(|map, head, _| {
13524 (
13525 movement::end_of_paragraph(map, head, 1),
13526 SelectionGoal::None,
13527 )
13528 });
13529 })
13530 }
13531
13532 pub fn move_to_start_of_excerpt(
13533 &mut self,
13534 _: &MoveToStartOfExcerpt,
13535 window: &mut Window,
13536 cx: &mut Context<Self>,
13537 ) {
13538 if matches!(self.mode, EditorMode::SingleLine) {
13539 cx.propagate();
13540 return;
13541 }
13542 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13543 self.change_selections(Default::default(), window, cx, |s| {
13544 s.move_with(|map, selection| {
13545 selection.collapse_to(
13546 movement::start_of_excerpt(
13547 map,
13548 selection.head(),
13549 workspace::searchable::Direction::Prev,
13550 ),
13551 SelectionGoal::None,
13552 )
13553 });
13554 })
13555 }
13556
13557 pub fn move_to_start_of_next_excerpt(
13558 &mut self,
13559 _: &MoveToStartOfNextExcerpt,
13560 window: &mut Window,
13561 cx: &mut Context<Self>,
13562 ) {
13563 if matches!(self.mode, EditorMode::SingleLine) {
13564 cx.propagate();
13565 return;
13566 }
13567
13568 self.change_selections(Default::default(), window, cx, |s| {
13569 s.move_with(|map, selection| {
13570 selection.collapse_to(
13571 movement::start_of_excerpt(
13572 map,
13573 selection.head(),
13574 workspace::searchable::Direction::Next,
13575 ),
13576 SelectionGoal::None,
13577 )
13578 });
13579 })
13580 }
13581
13582 pub fn move_to_end_of_excerpt(
13583 &mut self,
13584 _: &MoveToEndOfExcerpt,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) {
13588 if matches!(self.mode, EditorMode::SingleLine) {
13589 cx.propagate();
13590 return;
13591 }
13592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13593 self.change_selections(Default::default(), window, cx, |s| {
13594 s.move_with(|map, selection| {
13595 selection.collapse_to(
13596 movement::end_of_excerpt(
13597 map,
13598 selection.head(),
13599 workspace::searchable::Direction::Next,
13600 ),
13601 SelectionGoal::None,
13602 )
13603 });
13604 })
13605 }
13606
13607 pub fn move_to_end_of_previous_excerpt(
13608 &mut self,
13609 _: &MoveToEndOfPreviousExcerpt,
13610 window: &mut Window,
13611 cx: &mut Context<Self>,
13612 ) {
13613 if matches!(self.mode, EditorMode::SingleLine) {
13614 cx.propagate();
13615 return;
13616 }
13617 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13618 self.change_selections(Default::default(), window, cx, |s| {
13619 s.move_with(|map, selection| {
13620 selection.collapse_to(
13621 movement::end_of_excerpt(
13622 map,
13623 selection.head(),
13624 workspace::searchable::Direction::Prev,
13625 ),
13626 SelectionGoal::None,
13627 )
13628 });
13629 })
13630 }
13631
13632 pub fn select_to_start_of_excerpt(
13633 &mut self,
13634 _: &SelectToStartOfExcerpt,
13635 window: &mut Window,
13636 cx: &mut Context<Self>,
13637 ) {
13638 if matches!(self.mode, EditorMode::SingleLine) {
13639 cx.propagate();
13640 return;
13641 }
13642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13643 self.change_selections(Default::default(), window, cx, |s| {
13644 s.move_heads_with(|map, head, _| {
13645 (
13646 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13647 SelectionGoal::None,
13648 )
13649 });
13650 })
13651 }
13652
13653 pub fn select_to_start_of_next_excerpt(
13654 &mut self,
13655 _: &SelectToStartOfNextExcerpt,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 if matches!(self.mode, EditorMode::SingleLine) {
13660 cx.propagate();
13661 return;
13662 }
13663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13664 self.change_selections(Default::default(), window, cx, |s| {
13665 s.move_heads_with(|map, head, _| {
13666 (
13667 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13668 SelectionGoal::None,
13669 )
13670 });
13671 })
13672 }
13673
13674 pub fn select_to_end_of_excerpt(
13675 &mut self,
13676 _: &SelectToEndOfExcerpt,
13677 window: &mut Window,
13678 cx: &mut Context<Self>,
13679 ) {
13680 if matches!(self.mode, EditorMode::SingleLine) {
13681 cx.propagate();
13682 return;
13683 }
13684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13685 self.change_selections(Default::default(), window, cx, |s| {
13686 s.move_heads_with(|map, head, _| {
13687 (
13688 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13689 SelectionGoal::None,
13690 )
13691 });
13692 })
13693 }
13694
13695 pub fn select_to_end_of_previous_excerpt(
13696 &mut self,
13697 _: &SelectToEndOfPreviousExcerpt,
13698 window: &mut Window,
13699 cx: &mut Context<Self>,
13700 ) {
13701 if matches!(self.mode, EditorMode::SingleLine) {
13702 cx.propagate();
13703 return;
13704 }
13705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13706 self.change_selections(Default::default(), window, cx, |s| {
13707 s.move_heads_with(|map, head, _| {
13708 (
13709 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13710 SelectionGoal::None,
13711 )
13712 });
13713 })
13714 }
13715
13716 pub fn move_to_beginning(
13717 &mut self,
13718 _: &MoveToBeginning,
13719 window: &mut Window,
13720 cx: &mut Context<Self>,
13721 ) {
13722 if matches!(self.mode, EditorMode::SingleLine) {
13723 cx.propagate();
13724 return;
13725 }
13726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13727 self.change_selections(Default::default(), window, cx, |s| {
13728 s.select_ranges(vec![0..0]);
13729 });
13730 }
13731
13732 pub fn select_to_beginning(
13733 &mut self,
13734 _: &SelectToBeginning,
13735 window: &mut Window,
13736 cx: &mut Context<Self>,
13737 ) {
13738 let mut selection = self.selections.last::<Point>(cx);
13739 selection.set_head(Point::zero(), SelectionGoal::None);
13740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13741 self.change_selections(Default::default(), window, cx, |s| {
13742 s.select(vec![selection]);
13743 });
13744 }
13745
13746 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13747 if matches!(self.mode, EditorMode::SingleLine) {
13748 cx.propagate();
13749 return;
13750 }
13751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13752 let cursor = self.buffer.read(cx).read(cx).len();
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.select_ranges(vec![cursor..cursor])
13755 });
13756 }
13757
13758 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13759 self.nav_history = nav_history;
13760 }
13761
13762 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13763 self.nav_history.as_ref()
13764 }
13765
13766 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13767 self.push_to_nav_history(
13768 self.selections.newest_anchor().head(),
13769 None,
13770 false,
13771 true,
13772 cx,
13773 );
13774 }
13775
13776 fn push_to_nav_history(
13777 &mut self,
13778 cursor_anchor: Anchor,
13779 new_position: Option<Point>,
13780 is_deactivate: bool,
13781 always: bool,
13782 cx: &mut Context<Self>,
13783 ) {
13784 if let Some(nav_history) = self.nav_history.as_mut() {
13785 let buffer = self.buffer.read(cx).read(cx);
13786 let cursor_position = cursor_anchor.to_point(&buffer);
13787 let scroll_state = self.scroll_manager.anchor();
13788 let scroll_top_row = scroll_state.top_row(&buffer);
13789 drop(buffer);
13790
13791 if let Some(new_position) = new_position {
13792 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13793 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13794 return;
13795 }
13796 }
13797
13798 nav_history.push(
13799 Some(NavigationData {
13800 cursor_anchor,
13801 cursor_position,
13802 scroll_anchor: scroll_state,
13803 scroll_top_row,
13804 }),
13805 cx,
13806 );
13807 cx.emit(EditorEvent::PushedToNavHistory {
13808 anchor: cursor_anchor,
13809 is_deactivate,
13810 })
13811 }
13812 }
13813
13814 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13816 let buffer = self.buffer.read(cx).snapshot(cx);
13817 let mut selection = self.selections.first::<usize>(cx);
13818 selection.set_head(buffer.len(), SelectionGoal::None);
13819 self.change_selections(Default::default(), window, cx, |s| {
13820 s.select(vec![selection]);
13821 });
13822 }
13823
13824 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13826 let end = self.buffer.read(cx).read(cx).len();
13827 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13828 s.select_ranges(vec![0..end]);
13829 });
13830 }
13831
13832 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13834 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13835 let mut selections = self.selections.all::<Point>(cx);
13836 let max_point = display_map.buffer_snapshot.max_point();
13837 for selection in &mut selections {
13838 let rows = selection.spanned_rows(true, &display_map);
13839 selection.start = Point::new(rows.start.0, 0);
13840 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13841 selection.reversed = false;
13842 }
13843 self.change_selections(Default::default(), window, cx, |s| {
13844 s.select(selections);
13845 });
13846 }
13847
13848 pub fn split_selection_into_lines(
13849 &mut self,
13850 action: &SplitSelectionIntoLines,
13851 window: &mut Window,
13852 cx: &mut Context<Self>,
13853 ) {
13854 let selections = self
13855 .selections
13856 .all::<Point>(cx)
13857 .into_iter()
13858 .map(|selection| selection.start..selection.end)
13859 .collect::<Vec<_>>();
13860 self.unfold_ranges(&selections, true, true, cx);
13861
13862 let mut new_selection_ranges = Vec::new();
13863 {
13864 let buffer = self.buffer.read(cx).read(cx);
13865 for selection in selections {
13866 for row in selection.start.row..selection.end.row {
13867 let line_start = Point::new(row, 0);
13868 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13869
13870 if action.keep_selections {
13871 // Keep the selection range for each line
13872 let selection_start = if row == selection.start.row {
13873 selection.start
13874 } else {
13875 line_start
13876 };
13877 new_selection_ranges.push(selection_start..line_end);
13878 } else {
13879 // Collapse to cursor at end of line
13880 new_selection_ranges.push(line_end..line_end);
13881 }
13882 }
13883
13884 let is_multiline_selection = selection.start.row != selection.end.row;
13885 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13886 // so this action feels more ergonomic when paired with other selection operations
13887 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13888 if !should_skip_last {
13889 if action.keep_selections {
13890 if is_multiline_selection {
13891 let line_start = Point::new(selection.end.row, 0);
13892 new_selection_ranges.push(line_start..selection.end);
13893 } else {
13894 new_selection_ranges.push(selection.start..selection.end);
13895 }
13896 } else {
13897 new_selection_ranges.push(selection.end..selection.end);
13898 }
13899 }
13900 }
13901 }
13902 self.change_selections(Default::default(), window, cx, |s| {
13903 s.select_ranges(new_selection_ranges);
13904 });
13905 }
13906
13907 pub fn add_selection_above(
13908 &mut self,
13909 _: &AddSelectionAbove,
13910 window: &mut Window,
13911 cx: &mut Context<Self>,
13912 ) {
13913 self.add_selection(true, window, cx);
13914 }
13915
13916 pub fn add_selection_below(
13917 &mut self,
13918 _: &AddSelectionBelow,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) {
13922 self.add_selection(false, window, cx);
13923 }
13924
13925 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13927
13928 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13929 let all_selections = self.selections.all::<Point>(cx);
13930 let text_layout_details = self.text_layout_details(window);
13931
13932 let (mut columnar_selections, new_selections_to_columnarize) = {
13933 if let Some(state) = self.add_selections_state.as_ref() {
13934 let columnar_selection_ids: HashSet<_> = state
13935 .groups
13936 .iter()
13937 .flat_map(|group| group.stack.iter())
13938 .copied()
13939 .collect();
13940
13941 all_selections
13942 .into_iter()
13943 .partition(|s| columnar_selection_ids.contains(&s.id))
13944 } else {
13945 (Vec::new(), all_selections)
13946 }
13947 };
13948
13949 let mut state = self
13950 .add_selections_state
13951 .take()
13952 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13953
13954 for selection in new_selections_to_columnarize {
13955 let range = selection.display_range(&display_map).sorted();
13956 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13957 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13958 let positions = start_x.min(end_x)..start_x.max(end_x);
13959 let mut stack = Vec::new();
13960 for row in range.start.row().0..=range.end.row().0 {
13961 if let Some(selection) = self.selections.build_columnar_selection(
13962 &display_map,
13963 DisplayRow(row),
13964 &positions,
13965 selection.reversed,
13966 &text_layout_details,
13967 ) {
13968 stack.push(selection.id);
13969 columnar_selections.push(selection);
13970 }
13971 }
13972 if !stack.is_empty() {
13973 if above {
13974 stack.reverse();
13975 }
13976 state.groups.push(AddSelectionsGroup { above, stack });
13977 }
13978 }
13979
13980 let mut final_selections = Vec::new();
13981 let end_row = if above {
13982 DisplayRow(0)
13983 } else {
13984 display_map.max_point().row()
13985 };
13986
13987 let mut last_added_item_per_group = HashMap::default();
13988 for group in state.groups.iter_mut() {
13989 if let Some(last_id) = group.stack.last() {
13990 last_added_item_per_group.insert(*last_id, group);
13991 }
13992 }
13993
13994 for selection in columnar_selections {
13995 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13996 if above == group.above {
13997 let range = selection.display_range(&display_map).sorted();
13998 debug_assert_eq!(range.start.row(), range.end.row());
13999 let mut row = range.start.row();
14000 let positions =
14001 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14002 px(start)..px(end)
14003 } else {
14004 let start_x =
14005 display_map.x_for_display_point(range.start, &text_layout_details);
14006 let end_x =
14007 display_map.x_for_display_point(range.end, &text_layout_details);
14008 start_x.min(end_x)..start_x.max(end_x)
14009 };
14010
14011 let mut maybe_new_selection = None;
14012 while row != end_row {
14013 if above {
14014 row.0 -= 1;
14015 } else {
14016 row.0 += 1;
14017 }
14018 if let Some(new_selection) = self.selections.build_columnar_selection(
14019 &display_map,
14020 row,
14021 &positions,
14022 selection.reversed,
14023 &text_layout_details,
14024 ) {
14025 maybe_new_selection = Some(new_selection);
14026 break;
14027 }
14028 }
14029
14030 if let Some(new_selection) = maybe_new_selection {
14031 group.stack.push(new_selection.id);
14032 if above {
14033 final_selections.push(new_selection);
14034 final_selections.push(selection);
14035 } else {
14036 final_selections.push(selection);
14037 final_selections.push(new_selection);
14038 }
14039 } else {
14040 final_selections.push(selection);
14041 }
14042 } else {
14043 group.stack.pop();
14044 }
14045 } else {
14046 final_selections.push(selection);
14047 }
14048 }
14049
14050 self.change_selections(Default::default(), window, cx, |s| {
14051 s.select(final_selections);
14052 });
14053
14054 let final_selection_ids: HashSet<_> = self
14055 .selections
14056 .all::<Point>(cx)
14057 .iter()
14058 .map(|s| s.id)
14059 .collect();
14060 state.groups.retain_mut(|group| {
14061 // selections might get merged above so we remove invalid items from stacks
14062 group.stack.retain(|id| final_selection_ids.contains(id));
14063
14064 // single selection in stack can be treated as initial state
14065 group.stack.len() > 1
14066 });
14067
14068 if !state.groups.is_empty() {
14069 self.add_selections_state = Some(state);
14070 }
14071 }
14072
14073 fn select_match_ranges(
14074 &mut self,
14075 range: Range<usize>,
14076 reversed: bool,
14077 replace_newest: bool,
14078 auto_scroll: Option<Autoscroll>,
14079 window: &mut Window,
14080 cx: &mut Context<Editor>,
14081 ) {
14082 self.unfold_ranges(
14083 std::slice::from_ref(&range),
14084 false,
14085 auto_scroll.is_some(),
14086 cx,
14087 );
14088 let effects = if let Some(scroll) = auto_scroll {
14089 SelectionEffects::scroll(scroll)
14090 } else {
14091 SelectionEffects::no_scroll()
14092 };
14093 self.change_selections(effects, window, cx, |s| {
14094 if replace_newest {
14095 s.delete(s.newest_anchor().id);
14096 }
14097 if reversed {
14098 s.insert_range(range.end..range.start);
14099 } else {
14100 s.insert_range(range);
14101 }
14102 });
14103 }
14104
14105 pub fn select_next_match_internal(
14106 &mut self,
14107 display_map: &DisplaySnapshot,
14108 replace_newest: bool,
14109 autoscroll: Option<Autoscroll>,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) -> Result<()> {
14113 let buffer = &display_map.buffer_snapshot;
14114 let mut selections = self.selections.all::<usize>(cx);
14115 if let Some(mut select_next_state) = self.select_next_state.take() {
14116 let query = &select_next_state.query;
14117 if !select_next_state.done {
14118 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14119 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14120 let mut next_selected_range = None;
14121
14122 let bytes_after_last_selection =
14123 buffer.bytes_in_range(last_selection.end..buffer.len());
14124 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14125 let query_matches = query
14126 .stream_find_iter(bytes_after_last_selection)
14127 .map(|result| (last_selection.end, result))
14128 .chain(
14129 query
14130 .stream_find_iter(bytes_before_first_selection)
14131 .map(|result| (0, result)),
14132 );
14133
14134 for (start_offset, query_match) in query_matches {
14135 let query_match = query_match.unwrap(); // can only fail due to I/O
14136 let offset_range =
14137 start_offset + query_match.start()..start_offset + query_match.end();
14138
14139 if !select_next_state.wordwise
14140 || (!buffer.is_inside_word(offset_range.start, false)
14141 && !buffer.is_inside_word(offset_range.end, false))
14142 {
14143 // TODO: This is n^2, because we might check all the selections
14144 if !selections
14145 .iter()
14146 .any(|selection| selection.range().overlaps(&offset_range))
14147 {
14148 next_selected_range = Some(offset_range);
14149 break;
14150 }
14151 }
14152 }
14153
14154 if let Some(next_selected_range) = next_selected_range {
14155 self.select_match_ranges(
14156 next_selected_range,
14157 last_selection.reversed,
14158 replace_newest,
14159 autoscroll,
14160 window,
14161 cx,
14162 );
14163 } else {
14164 select_next_state.done = true;
14165 }
14166 }
14167
14168 self.select_next_state = Some(select_next_state);
14169 } else {
14170 let mut only_carets = true;
14171 let mut same_text_selected = true;
14172 let mut selected_text = None;
14173
14174 let mut selections_iter = selections.iter().peekable();
14175 while let Some(selection) = selections_iter.next() {
14176 if selection.start != selection.end {
14177 only_carets = false;
14178 }
14179
14180 if same_text_selected {
14181 if selected_text.is_none() {
14182 selected_text =
14183 Some(buffer.text_for_range(selection.range()).collect::<String>());
14184 }
14185
14186 if let Some(next_selection) = selections_iter.peek() {
14187 if next_selection.range().len() == selection.range().len() {
14188 let next_selected_text = buffer
14189 .text_for_range(next_selection.range())
14190 .collect::<String>();
14191 if Some(next_selected_text) != selected_text {
14192 same_text_selected = false;
14193 selected_text = None;
14194 }
14195 } else {
14196 same_text_selected = false;
14197 selected_text = None;
14198 }
14199 }
14200 }
14201 }
14202
14203 if only_carets {
14204 for selection in &mut selections {
14205 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14206 selection.start = word_range.start;
14207 selection.end = word_range.end;
14208 selection.goal = SelectionGoal::None;
14209 selection.reversed = false;
14210 self.select_match_ranges(
14211 selection.start..selection.end,
14212 selection.reversed,
14213 replace_newest,
14214 autoscroll,
14215 window,
14216 cx,
14217 );
14218 }
14219
14220 if selections.len() == 1 {
14221 let selection = selections
14222 .last()
14223 .expect("ensured that there's only one selection");
14224 let query = buffer
14225 .text_for_range(selection.start..selection.end)
14226 .collect::<String>();
14227 let is_empty = query.is_empty();
14228 let select_state = SelectNextState {
14229 query: AhoCorasick::new(&[query])?,
14230 wordwise: true,
14231 done: is_empty,
14232 };
14233 self.select_next_state = Some(select_state);
14234 } else {
14235 self.select_next_state = None;
14236 }
14237 } else if let Some(selected_text) = selected_text {
14238 self.select_next_state = Some(SelectNextState {
14239 query: AhoCorasick::new(&[selected_text])?,
14240 wordwise: false,
14241 done: false,
14242 });
14243 self.select_next_match_internal(
14244 display_map,
14245 replace_newest,
14246 autoscroll,
14247 window,
14248 cx,
14249 )?;
14250 }
14251 }
14252 Ok(())
14253 }
14254
14255 pub fn select_all_matches(
14256 &mut self,
14257 _action: &SelectAllMatches,
14258 window: &mut Window,
14259 cx: &mut Context<Self>,
14260 ) -> Result<()> {
14261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14262
14263 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14264
14265 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14266 let Some(select_next_state) = self.select_next_state.as_mut() else {
14267 return Ok(());
14268 };
14269 if select_next_state.done {
14270 return Ok(());
14271 }
14272
14273 let mut new_selections = Vec::new();
14274
14275 let reversed = self.selections.oldest::<usize>(cx).reversed;
14276 let buffer = &display_map.buffer_snapshot;
14277 let query_matches = select_next_state
14278 .query
14279 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14280
14281 for query_match in query_matches.into_iter() {
14282 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14283 let offset_range = if reversed {
14284 query_match.end()..query_match.start()
14285 } else {
14286 query_match.start()..query_match.end()
14287 };
14288
14289 if !select_next_state.wordwise
14290 || (!buffer.is_inside_word(offset_range.start, false)
14291 && !buffer.is_inside_word(offset_range.end, false))
14292 {
14293 new_selections.push(offset_range.start..offset_range.end);
14294 }
14295 }
14296
14297 select_next_state.done = true;
14298
14299 if new_selections.is_empty() {
14300 log::error!("bug: new_selections is empty in select_all_matches");
14301 return Ok(());
14302 }
14303
14304 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14305 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14306 selections.select_ranges(new_selections)
14307 });
14308
14309 Ok(())
14310 }
14311
14312 pub fn select_next(
14313 &mut self,
14314 action: &SelectNext,
14315 window: &mut Window,
14316 cx: &mut Context<Self>,
14317 ) -> Result<()> {
14318 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14320 self.select_next_match_internal(
14321 &display_map,
14322 action.replace_newest,
14323 Some(Autoscroll::newest()),
14324 window,
14325 cx,
14326 )?;
14327 Ok(())
14328 }
14329
14330 pub fn select_previous(
14331 &mut self,
14332 action: &SelectPrevious,
14333 window: &mut Window,
14334 cx: &mut Context<Self>,
14335 ) -> Result<()> {
14336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14337 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14338 let buffer = &display_map.buffer_snapshot;
14339 let mut selections = self.selections.all::<usize>(cx);
14340 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14341 let query = &select_prev_state.query;
14342 if !select_prev_state.done {
14343 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14344 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14345 let mut next_selected_range = None;
14346 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14347 let bytes_before_last_selection =
14348 buffer.reversed_bytes_in_range(0..last_selection.start);
14349 let bytes_after_first_selection =
14350 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14351 let query_matches = query
14352 .stream_find_iter(bytes_before_last_selection)
14353 .map(|result| (last_selection.start, result))
14354 .chain(
14355 query
14356 .stream_find_iter(bytes_after_first_selection)
14357 .map(|result| (buffer.len(), result)),
14358 );
14359 for (end_offset, query_match) in query_matches {
14360 let query_match = query_match.unwrap(); // can only fail due to I/O
14361 let offset_range =
14362 end_offset - query_match.end()..end_offset - query_match.start();
14363
14364 if !select_prev_state.wordwise
14365 || (!buffer.is_inside_word(offset_range.start, false)
14366 && !buffer.is_inside_word(offset_range.end, false))
14367 {
14368 next_selected_range = Some(offset_range);
14369 break;
14370 }
14371 }
14372
14373 if let Some(next_selected_range) = next_selected_range {
14374 self.select_match_ranges(
14375 next_selected_range,
14376 last_selection.reversed,
14377 action.replace_newest,
14378 Some(Autoscroll::newest()),
14379 window,
14380 cx,
14381 );
14382 } else {
14383 select_prev_state.done = true;
14384 }
14385 }
14386
14387 self.select_prev_state = Some(select_prev_state);
14388 } else {
14389 let mut only_carets = true;
14390 let mut same_text_selected = true;
14391 let mut selected_text = None;
14392
14393 let mut selections_iter = selections.iter().peekable();
14394 while let Some(selection) = selections_iter.next() {
14395 if selection.start != selection.end {
14396 only_carets = false;
14397 }
14398
14399 if same_text_selected {
14400 if selected_text.is_none() {
14401 selected_text =
14402 Some(buffer.text_for_range(selection.range()).collect::<String>());
14403 }
14404
14405 if let Some(next_selection) = selections_iter.peek() {
14406 if next_selection.range().len() == selection.range().len() {
14407 let next_selected_text = buffer
14408 .text_for_range(next_selection.range())
14409 .collect::<String>();
14410 if Some(next_selected_text) != selected_text {
14411 same_text_selected = false;
14412 selected_text = None;
14413 }
14414 } else {
14415 same_text_selected = false;
14416 selected_text = None;
14417 }
14418 }
14419 }
14420 }
14421
14422 if only_carets {
14423 for selection in &mut selections {
14424 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14425 selection.start = word_range.start;
14426 selection.end = word_range.end;
14427 selection.goal = SelectionGoal::None;
14428 selection.reversed = false;
14429 self.select_match_ranges(
14430 selection.start..selection.end,
14431 selection.reversed,
14432 action.replace_newest,
14433 Some(Autoscroll::newest()),
14434 window,
14435 cx,
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.chars().rev().collect::<String>()])?,
14448 wordwise: true,
14449 done: is_empty,
14450 };
14451 self.select_prev_state = Some(select_state);
14452 } else {
14453 self.select_prev_state = None;
14454 }
14455 } else if let Some(selected_text) = selected_text {
14456 self.select_prev_state = Some(SelectNextState {
14457 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14458 wordwise: false,
14459 done: false,
14460 });
14461 self.select_previous(action, window, cx)?;
14462 }
14463 }
14464 Ok(())
14465 }
14466
14467 pub fn find_next_match(
14468 &mut self,
14469 _: &FindNextMatch,
14470 window: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) -> Result<()> {
14473 let selections = self.selections.disjoint_anchors();
14474 match selections.first() {
14475 Some(first) if selections.len() >= 2 => {
14476 self.change_selections(Default::default(), window, cx, |s| {
14477 s.select_ranges([first.range()]);
14478 });
14479 }
14480 _ => self.select_next(
14481 &SelectNext {
14482 replace_newest: true,
14483 },
14484 window,
14485 cx,
14486 )?,
14487 }
14488 Ok(())
14489 }
14490
14491 pub fn find_previous_match(
14492 &mut self,
14493 _: &FindPreviousMatch,
14494 window: &mut Window,
14495 cx: &mut Context<Self>,
14496 ) -> Result<()> {
14497 let selections = self.selections.disjoint_anchors();
14498 match selections.last() {
14499 Some(last) if selections.len() >= 2 => {
14500 self.change_selections(Default::default(), window, cx, |s| {
14501 s.select_ranges([last.range()]);
14502 });
14503 }
14504 _ => self.select_previous(
14505 &SelectPrevious {
14506 replace_newest: true,
14507 },
14508 window,
14509 cx,
14510 )?,
14511 }
14512 Ok(())
14513 }
14514
14515 pub fn toggle_comments(
14516 &mut self,
14517 action: &ToggleComments,
14518 window: &mut Window,
14519 cx: &mut Context<Self>,
14520 ) {
14521 if self.read_only(cx) {
14522 return;
14523 }
14524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14525 let text_layout_details = &self.text_layout_details(window);
14526 self.transact(window, cx, |this, window, cx| {
14527 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14528 let mut edits = Vec::new();
14529 let mut selection_edit_ranges = Vec::new();
14530 let mut last_toggled_row = None;
14531 let snapshot = this.buffer.read(cx).read(cx);
14532 let empty_str: Arc<str> = Arc::default();
14533 let mut suffixes_inserted = Vec::new();
14534 let ignore_indent = action.ignore_indent;
14535
14536 fn comment_prefix_range(
14537 snapshot: &MultiBufferSnapshot,
14538 row: MultiBufferRow,
14539 comment_prefix: &str,
14540 comment_prefix_whitespace: &str,
14541 ignore_indent: bool,
14542 ) -> Range<Point> {
14543 let indent_size = if ignore_indent {
14544 0
14545 } else {
14546 snapshot.indent_size_for_line(row).len
14547 };
14548
14549 let start = Point::new(row.0, indent_size);
14550
14551 let mut line_bytes = snapshot
14552 .bytes_in_range(start..snapshot.max_point())
14553 .flatten()
14554 .copied();
14555
14556 // If this line currently begins with the line comment prefix, then record
14557 // the range containing the prefix.
14558 if line_bytes
14559 .by_ref()
14560 .take(comment_prefix.len())
14561 .eq(comment_prefix.bytes())
14562 {
14563 // Include any whitespace that matches the comment prefix.
14564 let matching_whitespace_len = line_bytes
14565 .zip(comment_prefix_whitespace.bytes())
14566 .take_while(|(a, b)| a == b)
14567 .count() as u32;
14568 let end = Point::new(
14569 start.row,
14570 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14571 );
14572 start..end
14573 } else {
14574 start..start
14575 }
14576 }
14577
14578 fn comment_suffix_range(
14579 snapshot: &MultiBufferSnapshot,
14580 row: MultiBufferRow,
14581 comment_suffix: &str,
14582 comment_suffix_has_leading_space: bool,
14583 ) -> Range<Point> {
14584 let end = Point::new(row.0, snapshot.line_len(row));
14585 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14586
14587 let mut line_end_bytes = snapshot
14588 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14589 .flatten()
14590 .copied();
14591
14592 let leading_space_len = if suffix_start_column > 0
14593 && line_end_bytes.next() == Some(b' ')
14594 && comment_suffix_has_leading_space
14595 {
14596 1
14597 } else {
14598 0
14599 };
14600
14601 // If this line currently begins with the line comment prefix, then record
14602 // the range containing the prefix.
14603 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14604 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14605 start..end
14606 } else {
14607 end..end
14608 }
14609 }
14610
14611 // TODO: Handle selections that cross excerpts
14612 for selection in &mut selections {
14613 let start_column = snapshot
14614 .indent_size_for_line(MultiBufferRow(selection.start.row))
14615 .len;
14616 let language = if let Some(language) =
14617 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14618 {
14619 language
14620 } else {
14621 continue;
14622 };
14623
14624 selection_edit_ranges.clear();
14625
14626 // If multiple selections contain a given row, avoid processing that
14627 // row more than once.
14628 let mut start_row = MultiBufferRow(selection.start.row);
14629 if last_toggled_row == Some(start_row) {
14630 start_row = start_row.next_row();
14631 }
14632 let end_row =
14633 if selection.end.row > selection.start.row && selection.end.column == 0 {
14634 MultiBufferRow(selection.end.row - 1)
14635 } else {
14636 MultiBufferRow(selection.end.row)
14637 };
14638 last_toggled_row = Some(end_row);
14639
14640 if start_row > end_row {
14641 continue;
14642 }
14643
14644 // If the language has line comments, toggle those.
14645 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14646
14647 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14648 if ignore_indent {
14649 full_comment_prefixes = full_comment_prefixes
14650 .into_iter()
14651 .map(|s| Arc::from(s.trim_end()))
14652 .collect();
14653 }
14654
14655 if !full_comment_prefixes.is_empty() {
14656 let first_prefix = full_comment_prefixes
14657 .first()
14658 .expect("prefixes is non-empty");
14659 let prefix_trimmed_lengths = full_comment_prefixes
14660 .iter()
14661 .map(|p| p.trim_end_matches(' ').len())
14662 .collect::<SmallVec<[usize; 4]>>();
14663
14664 let mut all_selection_lines_are_comments = true;
14665
14666 for row in start_row.0..=end_row.0 {
14667 let row = MultiBufferRow(row);
14668 if start_row < end_row && snapshot.is_line_blank(row) {
14669 continue;
14670 }
14671
14672 let prefix_range = full_comment_prefixes
14673 .iter()
14674 .zip(prefix_trimmed_lengths.iter().copied())
14675 .map(|(prefix, trimmed_prefix_len)| {
14676 comment_prefix_range(
14677 snapshot.deref(),
14678 row,
14679 &prefix[..trimmed_prefix_len],
14680 &prefix[trimmed_prefix_len..],
14681 ignore_indent,
14682 )
14683 })
14684 .max_by_key(|range| range.end.column - range.start.column)
14685 .expect("prefixes is non-empty");
14686
14687 if prefix_range.is_empty() {
14688 all_selection_lines_are_comments = false;
14689 }
14690
14691 selection_edit_ranges.push(prefix_range);
14692 }
14693
14694 if all_selection_lines_are_comments {
14695 edits.extend(
14696 selection_edit_ranges
14697 .iter()
14698 .cloned()
14699 .map(|range| (range, empty_str.clone())),
14700 );
14701 } else {
14702 let min_column = selection_edit_ranges
14703 .iter()
14704 .map(|range| range.start.column)
14705 .min()
14706 .unwrap_or(0);
14707 edits.extend(selection_edit_ranges.iter().map(|range| {
14708 let position = Point::new(range.start.row, min_column);
14709 (position..position, first_prefix.clone())
14710 }));
14711 }
14712 } else if let Some(BlockCommentConfig {
14713 start: full_comment_prefix,
14714 end: comment_suffix,
14715 ..
14716 }) = language.block_comment()
14717 {
14718 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14719 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14720 let prefix_range = comment_prefix_range(
14721 snapshot.deref(),
14722 start_row,
14723 comment_prefix,
14724 comment_prefix_whitespace,
14725 ignore_indent,
14726 );
14727 let suffix_range = comment_suffix_range(
14728 snapshot.deref(),
14729 end_row,
14730 comment_suffix.trim_start_matches(' '),
14731 comment_suffix.starts_with(' '),
14732 );
14733
14734 if prefix_range.is_empty() || suffix_range.is_empty() {
14735 edits.push((
14736 prefix_range.start..prefix_range.start,
14737 full_comment_prefix.clone(),
14738 ));
14739 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14740 suffixes_inserted.push((end_row, comment_suffix.len()));
14741 } else {
14742 edits.push((prefix_range, empty_str.clone()));
14743 edits.push((suffix_range, empty_str.clone()));
14744 }
14745 } else {
14746 continue;
14747 }
14748 }
14749
14750 drop(snapshot);
14751 this.buffer.update(cx, |buffer, cx| {
14752 buffer.edit(edits, None, cx);
14753 });
14754
14755 // Adjust selections so that they end before any comment suffixes that
14756 // were inserted.
14757 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14758 let mut selections = this.selections.all::<Point>(cx);
14759 let snapshot = this.buffer.read(cx).read(cx);
14760 for selection in &mut selections {
14761 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14762 match row.cmp(&MultiBufferRow(selection.end.row)) {
14763 Ordering::Less => {
14764 suffixes_inserted.next();
14765 continue;
14766 }
14767 Ordering::Greater => break,
14768 Ordering::Equal => {
14769 if selection.end.column == snapshot.line_len(row) {
14770 if selection.is_empty() {
14771 selection.start.column -= suffix_len as u32;
14772 }
14773 selection.end.column -= suffix_len as u32;
14774 }
14775 break;
14776 }
14777 }
14778 }
14779 }
14780
14781 drop(snapshot);
14782 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14783
14784 let selections = this.selections.all::<Point>(cx);
14785 let selections_on_single_row = selections.windows(2).all(|selections| {
14786 selections[0].start.row == selections[1].start.row
14787 && selections[0].end.row == selections[1].end.row
14788 && selections[0].start.row == selections[0].end.row
14789 });
14790 let selections_selecting = selections
14791 .iter()
14792 .any(|selection| selection.start != selection.end);
14793 let advance_downwards = action.advance_downwards
14794 && selections_on_single_row
14795 && !selections_selecting
14796 && !matches!(this.mode, EditorMode::SingleLine);
14797
14798 if advance_downwards {
14799 let snapshot = this.buffer.read(cx).snapshot(cx);
14800
14801 this.change_selections(Default::default(), window, cx, |s| {
14802 s.move_cursors_with(|display_snapshot, display_point, _| {
14803 let mut point = display_point.to_point(display_snapshot);
14804 point.row += 1;
14805 point = snapshot.clip_point(point, Bias::Left);
14806 let display_point = point.to_display_point(display_snapshot);
14807 let goal = SelectionGoal::HorizontalPosition(
14808 display_snapshot
14809 .x_for_display_point(display_point, text_layout_details)
14810 .into(),
14811 );
14812 (display_point, goal)
14813 })
14814 });
14815 }
14816 });
14817 }
14818
14819 pub fn select_enclosing_symbol(
14820 &mut self,
14821 _: &SelectEnclosingSymbol,
14822 window: &mut Window,
14823 cx: &mut Context<Self>,
14824 ) {
14825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14826
14827 let buffer = self.buffer.read(cx).snapshot(cx);
14828 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14829
14830 fn update_selection(
14831 selection: &Selection<usize>,
14832 buffer_snap: &MultiBufferSnapshot,
14833 ) -> Option<Selection<usize>> {
14834 let cursor = selection.head();
14835 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14836 for symbol in symbols.iter().rev() {
14837 let start = symbol.range.start.to_offset(buffer_snap);
14838 let end = symbol.range.end.to_offset(buffer_snap);
14839 let new_range = start..end;
14840 if start < selection.start || end > selection.end {
14841 return Some(Selection {
14842 id: selection.id,
14843 start: new_range.start,
14844 end: new_range.end,
14845 goal: SelectionGoal::None,
14846 reversed: selection.reversed,
14847 });
14848 }
14849 }
14850 None
14851 }
14852
14853 let mut selected_larger_symbol = false;
14854 let new_selections = old_selections
14855 .iter()
14856 .map(|selection| match update_selection(selection, &buffer) {
14857 Some(new_selection) => {
14858 if new_selection.range() != selection.range() {
14859 selected_larger_symbol = true;
14860 }
14861 new_selection
14862 }
14863 None => selection.clone(),
14864 })
14865 .collect::<Vec<_>>();
14866
14867 if selected_larger_symbol {
14868 self.change_selections(Default::default(), window, cx, |s| {
14869 s.select(new_selections);
14870 });
14871 }
14872 }
14873
14874 pub fn select_larger_syntax_node(
14875 &mut self,
14876 _: &SelectLargerSyntaxNode,
14877 window: &mut Window,
14878 cx: &mut Context<Self>,
14879 ) {
14880 let Some(visible_row_count) = self.visible_row_count() else {
14881 return;
14882 };
14883 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14884 if old_selections.is_empty() {
14885 return;
14886 }
14887
14888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14889
14890 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14891 let buffer = self.buffer.read(cx).snapshot(cx);
14892
14893 let mut selected_larger_node = false;
14894 let mut new_selections = old_selections
14895 .iter()
14896 .map(|selection| {
14897 let old_range = selection.start..selection.end;
14898
14899 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14900 // manually select word at selection
14901 if ["string_content", "inline"].contains(&node.kind()) {
14902 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14903 // ignore if word is already selected
14904 if !word_range.is_empty() && old_range != word_range {
14905 let (last_word_range, _) =
14906 buffer.surrounding_word(old_range.end, false);
14907 // only select word if start and end point belongs to same word
14908 if word_range == last_word_range {
14909 selected_larger_node = true;
14910 return Selection {
14911 id: selection.id,
14912 start: word_range.start,
14913 end: word_range.end,
14914 goal: SelectionGoal::None,
14915 reversed: selection.reversed,
14916 };
14917 }
14918 }
14919 }
14920 }
14921
14922 let mut new_range = old_range.clone();
14923 while let Some((_node, containing_range)) =
14924 buffer.syntax_ancestor(new_range.clone())
14925 {
14926 new_range = match containing_range {
14927 MultiOrSingleBufferOffsetRange::Single(_) => break,
14928 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14929 };
14930 if !display_map.intersects_fold(new_range.start)
14931 && !display_map.intersects_fold(new_range.end)
14932 {
14933 break;
14934 }
14935 }
14936
14937 selected_larger_node |= new_range != old_range;
14938 Selection {
14939 id: selection.id,
14940 start: new_range.start,
14941 end: new_range.end,
14942 goal: SelectionGoal::None,
14943 reversed: selection.reversed,
14944 }
14945 })
14946 .collect::<Vec<_>>();
14947
14948 if !selected_larger_node {
14949 return; // don't put this call in the history
14950 }
14951
14952 // scroll based on transformation done to the last selection created by the user
14953 let (last_old, last_new) = old_selections
14954 .last()
14955 .zip(new_selections.last().cloned())
14956 .expect("old_selections isn't empty");
14957
14958 // revert selection
14959 let is_selection_reversed = {
14960 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14961 new_selections.last_mut().expect("checked above").reversed =
14962 should_newest_selection_be_reversed;
14963 should_newest_selection_be_reversed
14964 };
14965
14966 if selected_larger_node {
14967 self.select_syntax_node_history.disable_clearing = true;
14968 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14969 s.select(new_selections.clone());
14970 });
14971 self.select_syntax_node_history.disable_clearing = false;
14972 }
14973
14974 let start_row = last_new.start.to_display_point(&display_map).row().0;
14975 let end_row = last_new.end.to_display_point(&display_map).row().0;
14976 let selection_height = end_row - start_row + 1;
14977 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14978
14979 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14980 let scroll_behavior = if fits_on_the_screen {
14981 self.request_autoscroll(Autoscroll::fit(), cx);
14982 SelectSyntaxNodeScrollBehavior::FitSelection
14983 } else if is_selection_reversed {
14984 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14985 SelectSyntaxNodeScrollBehavior::CursorTop
14986 } else {
14987 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14988 SelectSyntaxNodeScrollBehavior::CursorBottom
14989 };
14990
14991 self.select_syntax_node_history.push((
14992 old_selections,
14993 scroll_behavior,
14994 is_selection_reversed,
14995 ));
14996 }
14997
14998 pub fn select_smaller_syntax_node(
14999 &mut self,
15000 _: &SelectSmallerSyntaxNode,
15001 window: &mut Window,
15002 cx: &mut Context<Self>,
15003 ) {
15004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15005
15006 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15007 self.select_syntax_node_history.pop()
15008 {
15009 if let Some(selection) = selections.last_mut() {
15010 selection.reversed = is_selection_reversed;
15011 }
15012
15013 self.select_syntax_node_history.disable_clearing = true;
15014 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15015 s.select(selections.to_vec());
15016 });
15017 self.select_syntax_node_history.disable_clearing = false;
15018
15019 match scroll_behavior {
15020 SelectSyntaxNodeScrollBehavior::CursorTop => {
15021 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15022 }
15023 SelectSyntaxNodeScrollBehavior::FitSelection => {
15024 self.request_autoscroll(Autoscroll::fit(), cx);
15025 }
15026 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15027 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15028 }
15029 }
15030 }
15031 }
15032
15033 pub fn unwrap_syntax_node(
15034 &mut self,
15035 _: &UnwrapSyntaxNode,
15036 window: &mut Window,
15037 cx: &mut Context<Self>,
15038 ) {
15039 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15040
15041 let buffer = self.buffer.read(cx).snapshot(cx);
15042 let selections = self
15043 .selections
15044 .all::<usize>(cx)
15045 .into_iter()
15046 // subtracting the offset requires sorting
15047 .sorted_by_key(|i| i.start);
15048
15049 let full_edits = selections
15050 .into_iter()
15051 .filter_map(|selection| {
15052 // Only requires two branches once if-let-chains stabilize (#53667)
15053 let child = if !selection.is_empty() {
15054 selection.range()
15055 } else if let Some((_, ancestor_range)) =
15056 buffer.syntax_ancestor(selection.start..selection.end)
15057 {
15058 match ancestor_range {
15059 MultiOrSingleBufferOffsetRange::Single(range) => range,
15060 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15061 }
15062 } else {
15063 selection.range()
15064 };
15065
15066 let mut parent = child.clone();
15067 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15068 parent = match ancestor_range {
15069 MultiOrSingleBufferOffsetRange::Single(range) => range,
15070 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15071 };
15072 if parent.start < child.start || parent.end > child.end {
15073 break;
15074 }
15075 }
15076
15077 if parent == child {
15078 return None;
15079 }
15080 let text = buffer.text_for_range(child).collect::<String>();
15081 Some((selection.id, parent, text))
15082 })
15083 .collect::<Vec<_>>();
15084
15085 self.transact(window, cx, |this, window, cx| {
15086 this.buffer.update(cx, |buffer, cx| {
15087 buffer.edit(
15088 full_edits
15089 .iter()
15090 .map(|(_, p, t)| (p.clone(), t.clone()))
15091 .collect::<Vec<_>>(),
15092 None,
15093 cx,
15094 );
15095 });
15096 this.change_selections(Default::default(), window, cx, |s| {
15097 let mut offset = 0;
15098 let mut selections = vec![];
15099 for (id, parent, text) in full_edits {
15100 let start = parent.start - offset;
15101 offset += parent.len() - text.len();
15102 selections.push(Selection {
15103 id,
15104 start,
15105 end: start + text.len(),
15106 reversed: false,
15107 goal: Default::default(),
15108 });
15109 }
15110 s.select(selections);
15111 });
15112 });
15113 }
15114
15115 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15116 if !EditorSettings::get_global(cx).gutter.runnables {
15117 self.clear_tasks();
15118 return Task::ready(());
15119 }
15120 let project = self.project().map(Entity::downgrade);
15121 let task_sources = self.lsp_task_sources(cx);
15122 let multi_buffer = self.buffer.downgrade();
15123 cx.spawn_in(window, async move |editor, cx| {
15124 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15125 let Some(project) = project.and_then(|p| p.upgrade()) else {
15126 return;
15127 };
15128 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15129 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15130 }) else {
15131 return;
15132 };
15133
15134 let hide_runnables = project
15135 .update(cx, |project, _| project.is_via_collab())
15136 .unwrap_or(true);
15137 if hide_runnables {
15138 return;
15139 }
15140 let new_rows =
15141 cx.background_spawn({
15142 let snapshot = display_snapshot.clone();
15143 async move {
15144 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15145 }
15146 })
15147 .await;
15148 let Ok(lsp_tasks) =
15149 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15150 else {
15151 return;
15152 };
15153 let lsp_tasks = lsp_tasks.await;
15154
15155 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15156 lsp_tasks
15157 .into_iter()
15158 .flat_map(|(kind, tasks)| {
15159 tasks.into_iter().filter_map(move |(location, task)| {
15160 Some((kind.clone(), location?, task))
15161 })
15162 })
15163 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15164 let buffer = location.target.buffer;
15165 let buffer_snapshot = buffer.read(cx).snapshot();
15166 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15167 |(excerpt_id, snapshot, _)| {
15168 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15169 display_snapshot
15170 .buffer_snapshot
15171 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15172 } else {
15173 None
15174 }
15175 },
15176 );
15177 if let Some(offset) = offset {
15178 let task_buffer_range =
15179 location.target.range.to_point(&buffer_snapshot);
15180 let context_buffer_range =
15181 task_buffer_range.to_offset(&buffer_snapshot);
15182 let context_range = BufferOffset(context_buffer_range.start)
15183 ..BufferOffset(context_buffer_range.end);
15184
15185 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15186 .or_insert_with(|| RunnableTasks {
15187 templates: Vec::new(),
15188 offset,
15189 column: task_buffer_range.start.column,
15190 extra_variables: HashMap::default(),
15191 context_range,
15192 })
15193 .templates
15194 .push((kind, task.original_task().clone()));
15195 }
15196
15197 acc
15198 })
15199 }) else {
15200 return;
15201 };
15202
15203 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15204 buffer.language_settings(cx).tasks.prefer_lsp
15205 }) else {
15206 return;
15207 };
15208
15209 let rows = Self::runnable_rows(
15210 project,
15211 display_snapshot,
15212 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15213 new_rows,
15214 cx.clone(),
15215 )
15216 .await;
15217 editor
15218 .update(cx, |editor, _| {
15219 editor.clear_tasks();
15220 for (key, mut value) in rows {
15221 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15222 value.templates.extend(lsp_tasks.templates);
15223 }
15224
15225 editor.insert_tasks(key, value);
15226 }
15227 for (key, value) in lsp_tasks_by_rows {
15228 editor.insert_tasks(key, value);
15229 }
15230 })
15231 .ok();
15232 })
15233 }
15234 fn fetch_runnable_ranges(
15235 snapshot: &DisplaySnapshot,
15236 range: Range<Anchor>,
15237 ) -> Vec<language::RunnableRange> {
15238 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15239 }
15240
15241 fn runnable_rows(
15242 project: Entity<Project>,
15243 snapshot: DisplaySnapshot,
15244 prefer_lsp: bool,
15245 runnable_ranges: Vec<RunnableRange>,
15246 cx: AsyncWindowContext,
15247 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15248 cx.spawn(async move |cx| {
15249 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15250 for mut runnable in runnable_ranges {
15251 let Some(tasks) = cx
15252 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15253 .ok()
15254 else {
15255 continue;
15256 };
15257 let mut tasks = tasks.await;
15258
15259 if prefer_lsp {
15260 tasks.retain(|(task_kind, _)| {
15261 !matches!(task_kind, TaskSourceKind::Language { .. })
15262 });
15263 }
15264 if tasks.is_empty() {
15265 continue;
15266 }
15267
15268 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15269 let Some(row) = snapshot
15270 .buffer_snapshot
15271 .buffer_line_for_row(MultiBufferRow(point.row))
15272 .map(|(_, range)| range.start.row)
15273 else {
15274 continue;
15275 };
15276
15277 let context_range =
15278 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15279 runnable_rows.push((
15280 (runnable.buffer_id, row),
15281 RunnableTasks {
15282 templates: tasks,
15283 offset: snapshot
15284 .buffer_snapshot
15285 .anchor_before(runnable.run_range.start),
15286 context_range,
15287 column: point.column,
15288 extra_variables: runnable.extra_captures,
15289 },
15290 ));
15291 }
15292 runnable_rows
15293 })
15294 }
15295
15296 fn templates_with_tags(
15297 project: &Entity<Project>,
15298 runnable: &mut Runnable,
15299 cx: &mut App,
15300 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15301 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15302 let (worktree_id, file) = project
15303 .buffer_for_id(runnable.buffer, cx)
15304 .and_then(|buffer| buffer.read(cx).file())
15305 .map(|file| (file.worktree_id(cx), file.clone()))
15306 .unzip();
15307
15308 (
15309 project.task_store().read(cx).task_inventory().cloned(),
15310 worktree_id,
15311 file,
15312 )
15313 });
15314
15315 let tags = mem::take(&mut runnable.tags);
15316 let language = runnable.language.clone();
15317 cx.spawn(async move |cx| {
15318 let mut templates_with_tags = Vec::new();
15319 if let Some(inventory) = inventory {
15320 for RunnableTag(tag) in tags {
15321 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15322 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15323 }) else {
15324 return templates_with_tags;
15325 };
15326 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15327 move |(_, template)| {
15328 template.tags.iter().any(|source_tag| source_tag == &tag)
15329 },
15330 ));
15331 }
15332 }
15333 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15334
15335 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15336 // Strongest source wins; if we have worktree tag binding, prefer that to
15337 // global and language bindings;
15338 // if we have a global binding, prefer that to language binding.
15339 let first_mismatch = templates_with_tags
15340 .iter()
15341 .position(|(tag_source, _)| tag_source != leading_tag_source);
15342 if let Some(index) = first_mismatch {
15343 templates_with_tags.truncate(index);
15344 }
15345 }
15346
15347 templates_with_tags
15348 })
15349 }
15350
15351 pub fn move_to_enclosing_bracket(
15352 &mut self,
15353 _: &MoveToEnclosingBracket,
15354 window: &mut Window,
15355 cx: &mut Context<Self>,
15356 ) {
15357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15358 self.change_selections(Default::default(), window, cx, |s| {
15359 s.move_offsets_with(|snapshot, selection| {
15360 let Some(enclosing_bracket_ranges) =
15361 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15362 else {
15363 return;
15364 };
15365
15366 let mut best_length = usize::MAX;
15367 let mut best_inside = false;
15368 let mut best_in_bracket_range = false;
15369 let mut best_destination = None;
15370 for (open, close) in enclosing_bracket_ranges {
15371 let close = close.to_inclusive();
15372 let length = close.end() - open.start;
15373 let inside = selection.start >= open.end && selection.end <= *close.start();
15374 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15375 || close.contains(&selection.head());
15376
15377 // If best is next to a bracket and current isn't, skip
15378 if !in_bracket_range && best_in_bracket_range {
15379 continue;
15380 }
15381
15382 // Prefer smaller lengths unless best is inside and current isn't
15383 if length > best_length && (best_inside || !inside) {
15384 continue;
15385 }
15386
15387 best_length = length;
15388 best_inside = inside;
15389 best_in_bracket_range = in_bracket_range;
15390 best_destination = Some(
15391 if close.contains(&selection.start) && close.contains(&selection.end) {
15392 if inside { open.end } else { open.start }
15393 } else if inside {
15394 *close.start()
15395 } else {
15396 *close.end()
15397 },
15398 );
15399 }
15400
15401 if let Some(destination) = best_destination {
15402 selection.collapse_to(destination, SelectionGoal::None);
15403 }
15404 })
15405 });
15406 }
15407
15408 pub fn undo_selection(
15409 &mut self,
15410 _: &UndoSelection,
15411 window: &mut Window,
15412 cx: &mut Context<Self>,
15413 ) {
15414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15415 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15416 self.selection_history.mode = SelectionHistoryMode::Undoing;
15417 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15418 this.end_selection(window, cx);
15419 this.change_selections(
15420 SelectionEffects::scroll(Autoscroll::newest()),
15421 window,
15422 cx,
15423 |s| s.select_anchors(entry.selections.to_vec()),
15424 );
15425 });
15426 self.selection_history.mode = SelectionHistoryMode::Normal;
15427
15428 self.select_next_state = entry.select_next_state;
15429 self.select_prev_state = entry.select_prev_state;
15430 self.add_selections_state = entry.add_selections_state;
15431 }
15432 }
15433
15434 pub fn redo_selection(
15435 &mut self,
15436 _: &RedoSelection,
15437 window: &mut Window,
15438 cx: &mut Context<Self>,
15439 ) {
15440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15441 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15442 self.selection_history.mode = SelectionHistoryMode::Redoing;
15443 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15444 this.end_selection(window, cx);
15445 this.change_selections(
15446 SelectionEffects::scroll(Autoscroll::newest()),
15447 window,
15448 cx,
15449 |s| s.select_anchors(entry.selections.to_vec()),
15450 );
15451 });
15452 self.selection_history.mode = SelectionHistoryMode::Normal;
15453
15454 self.select_next_state = entry.select_next_state;
15455 self.select_prev_state = entry.select_prev_state;
15456 self.add_selections_state = entry.add_selections_state;
15457 }
15458 }
15459
15460 pub fn expand_excerpts(
15461 &mut self,
15462 action: &ExpandExcerpts,
15463 _: &mut Window,
15464 cx: &mut Context<Self>,
15465 ) {
15466 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15467 }
15468
15469 pub fn expand_excerpts_down(
15470 &mut self,
15471 action: &ExpandExcerptsDown,
15472 _: &mut Window,
15473 cx: &mut Context<Self>,
15474 ) {
15475 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15476 }
15477
15478 pub fn expand_excerpts_up(
15479 &mut self,
15480 action: &ExpandExcerptsUp,
15481 _: &mut Window,
15482 cx: &mut Context<Self>,
15483 ) {
15484 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15485 }
15486
15487 pub fn expand_excerpts_for_direction(
15488 &mut self,
15489 lines: u32,
15490 direction: ExpandExcerptDirection,
15491
15492 cx: &mut Context<Self>,
15493 ) {
15494 let selections = self.selections.disjoint_anchors();
15495
15496 let lines = if lines == 0 {
15497 EditorSettings::get_global(cx).expand_excerpt_lines
15498 } else {
15499 lines
15500 };
15501
15502 self.buffer.update(cx, |buffer, cx| {
15503 let snapshot = buffer.snapshot(cx);
15504 let mut excerpt_ids = selections
15505 .iter()
15506 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15507 .collect::<Vec<_>>();
15508 excerpt_ids.sort();
15509 excerpt_ids.dedup();
15510 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15511 })
15512 }
15513
15514 pub fn expand_excerpt(
15515 &mut self,
15516 excerpt: ExcerptId,
15517 direction: ExpandExcerptDirection,
15518 window: &mut Window,
15519 cx: &mut Context<Self>,
15520 ) {
15521 let current_scroll_position = self.scroll_position(cx);
15522 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15523 let mut should_scroll_up = false;
15524
15525 if direction == ExpandExcerptDirection::Down {
15526 let multi_buffer = self.buffer.read(cx);
15527 let snapshot = multi_buffer.snapshot(cx);
15528 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15529 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15530 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15531 {
15532 let buffer_snapshot = buffer.read(cx).snapshot();
15533 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15534 let last_row = buffer_snapshot.max_point().row;
15535 let lines_below = last_row.saturating_sub(excerpt_end_row);
15536 should_scroll_up = lines_below >= lines_to_expand;
15537 }
15538 }
15539
15540 self.buffer.update(cx, |buffer, cx| {
15541 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15542 });
15543
15544 if should_scroll_up {
15545 let new_scroll_position =
15546 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15547 self.set_scroll_position(new_scroll_position, window, cx);
15548 }
15549 }
15550
15551 pub fn go_to_singleton_buffer_point(
15552 &mut self,
15553 point: Point,
15554 window: &mut Window,
15555 cx: &mut Context<Self>,
15556 ) {
15557 self.go_to_singleton_buffer_range(point..point, window, cx);
15558 }
15559
15560 pub fn go_to_singleton_buffer_range(
15561 &mut self,
15562 range: Range<Point>,
15563 window: &mut Window,
15564 cx: &mut Context<Self>,
15565 ) {
15566 let multibuffer = self.buffer().read(cx);
15567 let Some(buffer) = multibuffer.as_singleton() else {
15568 return;
15569 };
15570 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15571 return;
15572 };
15573 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15574 return;
15575 };
15576 self.change_selections(
15577 SelectionEffects::default().nav_history(true),
15578 window,
15579 cx,
15580 |s| s.select_anchor_ranges([start..end]),
15581 );
15582 }
15583
15584 pub fn go_to_diagnostic(
15585 &mut self,
15586 action: &GoToDiagnostic,
15587 window: &mut Window,
15588 cx: &mut Context<Self>,
15589 ) {
15590 if !self.diagnostics_enabled() {
15591 return;
15592 }
15593 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15594 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15595 }
15596
15597 pub fn go_to_prev_diagnostic(
15598 &mut self,
15599 action: &GoToPreviousDiagnostic,
15600 window: &mut Window,
15601 cx: &mut Context<Self>,
15602 ) {
15603 if !self.diagnostics_enabled() {
15604 return;
15605 }
15606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15607 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15608 }
15609
15610 pub fn go_to_diagnostic_impl(
15611 &mut self,
15612 direction: Direction,
15613 severity: GoToDiagnosticSeverityFilter,
15614 window: &mut Window,
15615 cx: &mut Context<Self>,
15616 ) {
15617 let buffer = self.buffer.read(cx).snapshot(cx);
15618 let selection = self.selections.newest::<usize>(cx);
15619
15620 let mut active_group_id = None;
15621 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15622 && active_group.active_range.start.to_offset(&buffer) == selection.start
15623 {
15624 active_group_id = Some(active_group.group_id);
15625 }
15626
15627 fn filtered(
15628 snapshot: EditorSnapshot,
15629 severity: GoToDiagnosticSeverityFilter,
15630 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15631 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15632 diagnostics
15633 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15634 .filter(|entry| entry.range.start != entry.range.end)
15635 .filter(|entry| !entry.diagnostic.is_unnecessary)
15636 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15637 }
15638
15639 let snapshot = self.snapshot(window, cx);
15640 let before = filtered(
15641 snapshot.clone(),
15642 severity,
15643 buffer
15644 .diagnostics_in_range(0..selection.start)
15645 .filter(|entry| entry.range.start <= selection.start),
15646 );
15647 let after = filtered(
15648 snapshot,
15649 severity,
15650 buffer
15651 .diagnostics_in_range(selection.start..buffer.len())
15652 .filter(|entry| entry.range.start >= selection.start),
15653 );
15654
15655 let mut found: Option<DiagnosticEntry<usize>> = None;
15656 if direction == Direction::Prev {
15657 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15658 {
15659 for diagnostic in prev_diagnostics.into_iter().rev() {
15660 if diagnostic.range.start != selection.start
15661 || active_group_id
15662 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15663 {
15664 found = Some(diagnostic);
15665 break 'outer;
15666 }
15667 }
15668 }
15669 } else {
15670 for diagnostic in after.chain(before) {
15671 if diagnostic.range.start != selection.start
15672 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15673 {
15674 found = Some(diagnostic);
15675 break;
15676 }
15677 }
15678 }
15679 let Some(next_diagnostic) = found else {
15680 return;
15681 };
15682
15683 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15684 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15685 return;
15686 };
15687 self.change_selections(Default::default(), window, cx, |s| {
15688 s.select_ranges(vec![
15689 next_diagnostic.range.start..next_diagnostic.range.start,
15690 ])
15691 });
15692 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15693 self.refresh_edit_prediction(false, true, window, cx);
15694 }
15695
15696 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15697 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15698 let snapshot = self.snapshot(window, cx);
15699 let selection = self.selections.newest::<Point>(cx);
15700 self.go_to_hunk_before_or_after_position(
15701 &snapshot,
15702 selection.head(),
15703 Direction::Next,
15704 window,
15705 cx,
15706 );
15707 }
15708
15709 pub fn go_to_hunk_before_or_after_position(
15710 &mut self,
15711 snapshot: &EditorSnapshot,
15712 position: Point,
15713 direction: Direction,
15714 window: &mut Window,
15715 cx: &mut Context<Editor>,
15716 ) {
15717 let row = if direction == Direction::Next {
15718 self.hunk_after_position(snapshot, position)
15719 .map(|hunk| hunk.row_range.start)
15720 } else {
15721 self.hunk_before_position(snapshot, position)
15722 };
15723
15724 if let Some(row) = row {
15725 let destination = Point::new(row.0, 0);
15726 let autoscroll = Autoscroll::center();
15727
15728 self.unfold_ranges(&[destination..destination], false, false, cx);
15729 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15730 s.select_ranges([destination..destination]);
15731 });
15732 }
15733 }
15734
15735 fn hunk_after_position(
15736 &mut self,
15737 snapshot: &EditorSnapshot,
15738 position: Point,
15739 ) -> Option<MultiBufferDiffHunk> {
15740 snapshot
15741 .buffer_snapshot
15742 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15743 .find(|hunk| hunk.row_range.start.0 > position.row)
15744 .or_else(|| {
15745 snapshot
15746 .buffer_snapshot
15747 .diff_hunks_in_range(Point::zero()..position)
15748 .find(|hunk| hunk.row_range.end.0 < position.row)
15749 })
15750 }
15751
15752 fn go_to_prev_hunk(
15753 &mut self,
15754 _: &GoToPreviousHunk,
15755 window: &mut Window,
15756 cx: &mut Context<Self>,
15757 ) {
15758 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15759 let snapshot = self.snapshot(window, cx);
15760 let selection = self.selections.newest::<Point>(cx);
15761 self.go_to_hunk_before_or_after_position(
15762 &snapshot,
15763 selection.head(),
15764 Direction::Prev,
15765 window,
15766 cx,
15767 );
15768 }
15769
15770 fn hunk_before_position(
15771 &mut self,
15772 snapshot: &EditorSnapshot,
15773 position: Point,
15774 ) -> Option<MultiBufferRow> {
15775 snapshot
15776 .buffer_snapshot
15777 .diff_hunk_before(position)
15778 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15779 }
15780
15781 fn go_to_next_change(
15782 &mut self,
15783 _: &GoToNextChange,
15784 window: &mut Window,
15785 cx: &mut Context<Self>,
15786 ) {
15787 if let Some(selections) = self
15788 .change_list
15789 .next_change(1, Direction::Next)
15790 .map(|s| s.to_vec())
15791 {
15792 self.change_selections(Default::default(), window, cx, |s| {
15793 let map = s.display_map();
15794 s.select_display_ranges(selections.iter().map(|a| {
15795 let point = a.to_display_point(&map);
15796 point..point
15797 }))
15798 })
15799 }
15800 }
15801
15802 fn go_to_previous_change(
15803 &mut self,
15804 _: &GoToPreviousChange,
15805 window: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 if let Some(selections) = self
15809 .change_list
15810 .next_change(1, Direction::Prev)
15811 .map(|s| s.to_vec())
15812 {
15813 self.change_selections(Default::default(), window, cx, |s| {
15814 let map = s.display_map();
15815 s.select_display_ranges(selections.iter().map(|a| {
15816 let point = a.to_display_point(&map);
15817 point..point
15818 }))
15819 })
15820 }
15821 }
15822
15823 fn go_to_line<T: 'static>(
15824 &mut self,
15825 position: Anchor,
15826 highlight_color: Option<Hsla>,
15827 window: &mut Window,
15828 cx: &mut Context<Self>,
15829 ) {
15830 let snapshot = self.snapshot(window, cx).display_snapshot;
15831 let position = position.to_point(&snapshot.buffer_snapshot);
15832 let start = snapshot
15833 .buffer_snapshot
15834 .clip_point(Point::new(position.row, 0), Bias::Left);
15835 let end = start + Point::new(1, 0);
15836 let start = snapshot.buffer_snapshot.anchor_before(start);
15837 let end = snapshot.buffer_snapshot.anchor_before(end);
15838
15839 self.highlight_rows::<T>(
15840 start..end,
15841 highlight_color
15842 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15843 Default::default(),
15844 cx,
15845 );
15846
15847 if self.buffer.read(cx).is_singleton() {
15848 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15849 }
15850 }
15851
15852 pub fn go_to_definition(
15853 &mut self,
15854 _: &GoToDefinition,
15855 window: &mut Window,
15856 cx: &mut Context<Self>,
15857 ) -> Task<Result<Navigated>> {
15858 let definition =
15859 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15860 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15861 cx.spawn_in(window, async move |editor, cx| {
15862 if definition.await? == Navigated::Yes {
15863 return Ok(Navigated::Yes);
15864 }
15865 match fallback_strategy {
15866 GoToDefinitionFallback::None => Ok(Navigated::No),
15867 GoToDefinitionFallback::FindAllReferences => {
15868 match editor.update_in(cx, |editor, window, cx| {
15869 editor.find_all_references(&FindAllReferences, window, cx)
15870 })? {
15871 Some(references) => references.await,
15872 None => Ok(Navigated::No),
15873 }
15874 }
15875 }
15876 })
15877 }
15878
15879 pub fn go_to_declaration(
15880 &mut self,
15881 _: &GoToDeclaration,
15882 window: &mut Window,
15883 cx: &mut Context<Self>,
15884 ) -> Task<Result<Navigated>> {
15885 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15886 }
15887
15888 pub fn go_to_declaration_split(
15889 &mut self,
15890 _: &GoToDeclaration,
15891 window: &mut Window,
15892 cx: &mut Context<Self>,
15893 ) -> Task<Result<Navigated>> {
15894 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15895 }
15896
15897 pub fn go_to_implementation(
15898 &mut self,
15899 _: &GoToImplementation,
15900 window: &mut Window,
15901 cx: &mut Context<Self>,
15902 ) -> Task<Result<Navigated>> {
15903 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15904 }
15905
15906 pub fn go_to_implementation_split(
15907 &mut self,
15908 _: &GoToImplementationSplit,
15909 window: &mut Window,
15910 cx: &mut Context<Self>,
15911 ) -> Task<Result<Navigated>> {
15912 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15913 }
15914
15915 pub fn go_to_type_definition(
15916 &mut self,
15917 _: &GoToTypeDefinition,
15918 window: &mut Window,
15919 cx: &mut Context<Self>,
15920 ) -> Task<Result<Navigated>> {
15921 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15922 }
15923
15924 pub fn go_to_definition_split(
15925 &mut self,
15926 _: &GoToDefinitionSplit,
15927 window: &mut Window,
15928 cx: &mut Context<Self>,
15929 ) -> Task<Result<Navigated>> {
15930 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15931 }
15932
15933 pub fn go_to_type_definition_split(
15934 &mut self,
15935 _: &GoToTypeDefinitionSplit,
15936 window: &mut Window,
15937 cx: &mut Context<Self>,
15938 ) -> Task<Result<Navigated>> {
15939 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15940 }
15941
15942 fn go_to_definition_of_kind(
15943 &mut self,
15944 kind: GotoDefinitionKind,
15945 split: bool,
15946 window: &mut Window,
15947 cx: &mut Context<Self>,
15948 ) -> Task<Result<Navigated>> {
15949 let Some(provider) = self.semantics_provider.clone() else {
15950 return Task::ready(Ok(Navigated::No));
15951 };
15952 let head = self.selections.newest::<usize>(cx).head();
15953 let buffer = self.buffer.read(cx);
15954 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15955 return Task::ready(Ok(Navigated::No));
15956 };
15957 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15958 return Task::ready(Ok(Navigated::No));
15959 };
15960
15961 cx.spawn_in(window, async move |editor, cx| {
15962 let Some(definitions) = definitions.await? else {
15963 return Ok(Navigated::No);
15964 };
15965 let navigated = editor
15966 .update_in(cx, |editor, window, cx| {
15967 editor.navigate_to_hover_links(
15968 Some(kind),
15969 definitions
15970 .into_iter()
15971 .filter(|location| {
15972 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15973 })
15974 .map(HoverLink::Text)
15975 .collect::<Vec<_>>(),
15976 split,
15977 window,
15978 cx,
15979 )
15980 })?
15981 .await?;
15982 anyhow::Ok(navigated)
15983 })
15984 }
15985
15986 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15987 let selection = self.selections.newest_anchor();
15988 let head = selection.head();
15989 let tail = selection.tail();
15990
15991 let Some((buffer, start_position)) =
15992 self.buffer.read(cx).text_anchor_for_position(head, cx)
15993 else {
15994 return;
15995 };
15996
15997 let end_position = if head != tail {
15998 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15999 return;
16000 };
16001 Some(pos)
16002 } else {
16003 None
16004 };
16005
16006 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16007 let url = if let Some(end_pos) = end_position {
16008 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16009 } else {
16010 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16011 };
16012
16013 if let Some(url) = url {
16014 editor.update(cx, |_, cx| {
16015 cx.open_url(&url);
16016 })
16017 } else {
16018 Ok(())
16019 }
16020 });
16021
16022 url_finder.detach();
16023 }
16024
16025 pub fn open_selected_filename(
16026 &mut self,
16027 _: &OpenSelectedFilename,
16028 window: &mut Window,
16029 cx: &mut Context<Self>,
16030 ) {
16031 let Some(workspace) = self.workspace() else {
16032 return;
16033 };
16034
16035 let position = self.selections.newest_anchor().head();
16036
16037 let Some((buffer, buffer_position)) =
16038 self.buffer.read(cx).text_anchor_for_position(position, cx)
16039 else {
16040 return;
16041 };
16042
16043 let project = self.project.clone();
16044
16045 cx.spawn_in(window, async move |_, cx| {
16046 let result = find_file(&buffer, project, buffer_position, cx).await;
16047
16048 if let Some((_, path)) = result {
16049 workspace
16050 .update_in(cx, |workspace, window, cx| {
16051 workspace.open_resolved_path(path, window, cx)
16052 })?
16053 .await?;
16054 }
16055 anyhow::Ok(())
16056 })
16057 .detach();
16058 }
16059
16060 pub(crate) fn navigate_to_hover_links(
16061 &mut self,
16062 kind: Option<GotoDefinitionKind>,
16063 definitions: Vec<HoverLink>,
16064 split: bool,
16065 window: &mut Window,
16066 cx: &mut Context<Editor>,
16067 ) -> Task<Result<Navigated>> {
16068 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16069 let mut first_url_or_file = None;
16070 let definitions: Vec<_> = definitions
16071 .into_iter()
16072 .filter_map(|def| match def {
16073 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16074 HoverLink::InlayHint(lsp_location, server_id) => {
16075 let computation =
16076 self.compute_target_location(lsp_location, server_id, window, cx);
16077 Some(cx.background_spawn(computation))
16078 }
16079 HoverLink::Url(url) => {
16080 first_url_or_file = Some(Either::Left(url));
16081 None
16082 }
16083 HoverLink::File(path) => {
16084 first_url_or_file = Some(Either::Right(path));
16085 None
16086 }
16087 })
16088 .collect();
16089
16090 let workspace = self.workspace();
16091
16092 cx.spawn_in(window, async move |editor, acx| {
16093 let mut locations: Vec<Location> = future::join_all(definitions)
16094 .await
16095 .into_iter()
16096 .filter_map(|location| location.transpose())
16097 .collect::<Result<_>>()
16098 .context("location tasks")?;
16099
16100 if locations.len() > 1 {
16101 let Some(workspace) = workspace else {
16102 return Ok(Navigated::No);
16103 };
16104
16105 let tab_kind = match kind {
16106 Some(GotoDefinitionKind::Implementation) => "Implementations",
16107 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16108 Some(GotoDefinitionKind::Declaration) => "Declarations",
16109 Some(GotoDefinitionKind::Type) => "Types",
16110 };
16111 let title = editor
16112 .update_in(acx, |_, _, cx| {
16113 let target = locations
16114 .iter()
16115 .map(|location| {
16116 location
16117 .buffer
16118 .read(cx)
16119 .text_for_range(location.range.clone())
16120 .collect::<String>()
16121 })
16122 .filter(|text| !text.contains('\n'))
16123 .unique()
16124 .take(3)
16125 .join(", ");
16126 if target.is_empty() {
16127 tab_kind.to_owned()
16128 } else {
16129 format!("{tab_kind} for {target}")
16130 }
16131 })
16132 .context("buffer title")?;
16133
16134 let opened = workspace
16135 .update_in(acx, |workspace, window, cx| {
16136 Self::open_locations_in_multibuffer(
16137 workspace,
16138 locations,
16139 title,
16140 split,
16141 MultibufferSelectionMode::First,
16142 window,
16143 cx,
16144 )
16145 })
16146 .is_ok();
16147
16148 anyhow::Ok(Navigated::from_bool(opened))
16149 } else if locations.is_empty() {
16150 // If there is one definition, just open it directly
16151 match first_url_or_file {
16152 Some(Either::Left(url)) => {
16153 acx.update(|_, cx| cx.open_url(&url))?;
16154 Ok(Navigated::Yes)
16155 }
16156 Some(Either::Right(path)) => {
16157 let Some(workspace) = workspace else {
16158 return Ok(Navigated::No);
16159 };
16160
16161 workspace
16162 .update_in(acx, |workspace, window, cx| {
16163 workspace.open_resolved_path(path, window, cx)
16164 })?
16165 .await?;
16166 Ok(Navigated::Yes)
16167 }
16168 None => Ok(Navigated::No),
16169 }
16170 } else {
16171 let Some(workspace) = workspace else {
16172 return Ok(Navigated::No);
16173 };
16174
16175 let target = locations.pop().unwrap();
16176 editor.update_in(acx, |editor, window, cx| {
16177 let pane = workspace.read(cx).active_pane().clone();
16178
16179 let range = target.range.to_point(target.buffer.read(cx));
16180 let range = editor.range_for_match(&range);
16181 let range = collapse_multiline_range(range);
16182
16183 if !split
16184 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16185 {
16186 editor.go_to_singleton_buffer_range(range, window, cx);
16187 } else {
16188 window.defer(cx, move |window, cx| {
16189 let target_editor: Entity<Self> =
16190 workspace.update(cx, |workspace, cx| {
16191 let pane = if split {
16192 workspace.adjacent_pane(window, cx)
16193 } else {
16194 workspace.active_pane().clone()
16195 };
16196
16197 workspace.open_project_item(
16198 pane,
16199 target.buffer.clone(),
16200 true,
16201 true,
16202 window,
16203 cx,
16204 )
16205 });
16206 target_editor.update(cx, |target_editor, cx| {
16207 // When selecting a definition in a different buffer, disable the nav history
16208 // to avoid creating a history entry at the previous cursor location.
16209 pane.update(cx, |pane, _| pane.disable_history());
16210 target_editor.go_to_singleton_buffer_range(range, window, cx);
16211 pane.update(cx, |pane, _| pane.enable_history());
16212 });
16213 });
16214 }
16215 Navigated::Yes
16216 })
16217 }
16218 })
16219 }
16220
16221 fn compute_target_location(
16222 &self,
16223 lsp_location: lsp::Location,
16224 server_id: LanguageServerId,
16225 window: &mut Window,
16226 cx: &mut Context<Self>,
16227 ) -> Task<anyhow::Result<Option<Location>>> {
16228 let Some(project) = self.project.clone() else {
16229 return Task::ready(Ok(None));
16230 };
16231
16232 cx.spawn_in(window, async move |editor, cx| {
16233 let location_task = editor.update(cx, |_, cx| {
16234 project.update(cx, |project, cx| {
16235 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16236 })
16237 })?;
16238 let location = Some({
16239 let target_buffer_handle = location_task.await.context("open local buffer")?;
16240 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16241 let target_start = target_buffer
16242 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16243 let target_end = target_buffer
16244 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16245 target_buffer.anchor_after(target_start)
16246 ..target_buffer.anchor_before(target_end)
16247 })?;
16248 Location {
16249 buffer: target_buffer_handle,
16250 range,
16251 }
16252 });
16253 Ok(location)
16254 })
16255 }
16256
16257 pub fn find_all_references(
16258 &mut self,
16259 _: &FindAllReferences,
16260 window: &mut Window,
16261 cx: &mut Context<Self>,
16262 ) -> Option<Task<Result<Navigated>>> {
16263 let selection = self.selections.newest::<usize>(cx);
16264 let multi_buffer = self.buffer.read(cx);
16265 let head = selection.head();
16266
16267 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16268 let head_anchor = multi_buffer_snapshot.anchor_at(
16269 head,
16270 if head < selection.tail() {
16271 Bias::Right
16272 } else {
16273 Bias::Left
16274 },
16275 );
16276
16277 match self
16278 .find_all_references_task_sources
16279 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16280 {
16281 Ok(_) => {
16282 log::info!(
16283 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16284 );
16285 return None;
16286 }
16287 Err(i) => {
16288 self.find_all_references_task_sources.insert(i, head_anchor);
16289 }
16290 }
16291
16292 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16293 let workspace = self.workspace()?;
16294 let project = workspace.read(cx).project().clone();
16295 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16296 Some(cx.spawn_in(window, async move |editor, cx| {
16297 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16298 if let Ok(i) = editor
16299 .find_all_references_task_sources
16300 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16301 {
16302 editor.find_all_references_task_sources.remove(i);
16303 }
16304 });
16305
16306 let Some(locations) = references.await? else {
16307 return anyhow::Ok(Navigated::No);
16308 };
16309 if locations.is_empty() {
16310 return anyhow::Ok(Navigated::No);
16311 }
16312
16313 workspace.update_in(cx, |workspace, window, cx| {
16314 let target = locations
16315 .iter()
16316 .map(|location| {
16317 location
16318 .buffer
16319 .read(cx)
16320 .text_for_range(location.range.clone())
16321 .collect::<String>()
16322 })
16323 .filter(|text| !text.contains('\n'))
16324 .unique()
16325 .take(3)
16326 .join(", ");
16327 let title = if target.is_empty() {
16328 "References".to_owned()
16329 } else {
16330 format!("References to {target}")
16331 };
16332 Self::open_locations_in_multibuffer(
16333 workspace,
16334 locations,
16335 title,
16336 false,
16337 MultibufferSelectionMode::First,
16338 window,
16339 cx,
16340 );
16341 Navigated::Yes
16342 })
16343 }))
16344 }
16345
16346 /// Opens a multibuffer with the given project locations in it
16347 pub fn open_locations_in_multibuffer(
16348 workspace: &mut Workspace,
16349 mut locations: Vec<Location>,
16350 title: String,
16351 split: bool,
16352 multibuffer_selection_mode: MultibufferSelectionMode,
16353 window: &mut Window,
16354 cx: &mut Context<Workspace>,
16355 ) {
16356 if locations.is_empty() {
16357 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16358 return;
16359 }
16360
16361 // If there are multiple definitions, open them in a multibuffer
16362 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16363 let mut locations = locations.into_iter().peekable();
16364 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16365 let capability = workspace.project().read(cx).capability();
16366
16367 let excerpt_buffer = cx.new(|cx| {
16368 let mut multibuffer = MultiBuffer::new(capability);
16369 while let Some(location) = locations.next() {
16370 let buffer = location.buffer.read(cx);
16371 let mut ranges_for_buffer = Vec::new();
16372 let range = location.range.to_point(buffer);
16373 ranges_for_buffer.push(range.clone());
16374
16375 while let Some(next_location) = locations.peek() {
16376 if next_location.buffer == location.buffer {
16377 ranges_for_buffer.push(next_location.range.to_point(buffer));
16378 locations.next();
16379 } else {
16380 break;
16381 }
16382 }
16383
16384 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16385 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16386 PathKey::for_buffer(&location.buffer, cx),
16387 location.buffer.clone(),
16388 ranges_for_buffer,
16389 multibuffer_context_lines(cx),
16390 cx,
16391 );
16392 ranges.extend(new_ranges)
16393 }
16394
16395 multibuffer.with_title(title)
16396 });
16397
16398 let editor = cx.new(|cx| {
16399 Editor::for_multibuffer(
16400 excerpt_buffer,
16401 Some(workspace.project().clone()),
16402 window,
16403 cx,
16404 )
16405 });
16406 editor.update(cx, |editor, cx| {
16407 match multibuffer_selection_mode {
16408 MultibufferSelectionMode::First => {
16409 if let Some(first_range) = ranges.first() {
16410 editor.change_selections(
16411 SelectionEffects::no_scroll(),
16412 window,
16413 cx,
16414 |selections| {
16415 selections.clear_disjoint();
16416 selections
16417 .select_anchor_ranges(std::iter::once(first_range.clone()));
16418 },
16419 );
16420 }
16421 editor.highlight_background::<Self>(
16422 &ranges,
16423 |theme| theme.colors().editor_highlighted_line_background,
16424 cx,
16425 );
16426 }
16427 MultibufferSelectionMode::All => {
16428 editor.change_selections(
16429 SelectionEffects::no_scroll(),
16430 window,
16431 cx,
16432 |selections| {
16433 selections.clear_disjoint();
16434 selections.select_anchor_ranges(ranges);
16435 },
16436 );
16437 }
16438 }
16439 editor.register_buffers_with_language_servers(cx);
16440 });
16441
16442 let item = Box::new(editor);
16443 let item_id = item.item_id();
16444
16445 if split {
16446 workspace.split_item(SplitDirection::Right, item, window, cx);
16447 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16448 let (preview_item_id, preview_item_idx) =
16449 workspace.active_pane().read_with(cx, |pane, _| {
16450 (pane.preview_item_id(), pane.preview_item_idx())
16451 });
16452
16453 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16454
16455 if let Some(preview_item_id) = preview_item_id {
16456 workspace.active_pane().update(cx, |pane, cx| {
16457 pane.remove_item(preview_item_id, false, false, window, cx);
16458 });
16459 }
16460 } else {
16461 workspace.add_item_to_active_pane(item, None, true, window, cx);
16462 }
16463 workspace.active_pane().update(cx, |pane, cx| {
16464 pane.set_preview_item_id(Some(item_id), cx);
16465 });
16466 }
16467
16468 pub fn rename(
16469 &mut self,
16470 _: &Rename,
16471 window: &mut Window,
16472 cx: &mut Context<Self>,
16473 ) -> Option<Task<Result<()>>> {
16474 use language::ToOffset as _;
16475
16476 let provider = self.semantics_provider.clone()?;
16477 let selection = self.selections.newest_anchor().clone();
16478 let (cursor_buffer, cursor_buffer_position) = self
16479 .buffer
16480 .read(cx)
16481 .text_anchor_for_position(selection.head(), cx)?;
16482 let (tail_buffer, cursor_buffer_position_end) = self
16483 .buffer
16484 .read(cx)
16485 .text_anchor_for_position(selection.tail(), cx)?;
16486 if tail_buffer != cursor_buffer {
16487 return None;
16488 }
16489
16490 let snapshot = cursor_buffer.read(cx).snapshot();
16491 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16492 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16493 let prepare_rename = provider
16494 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16495 .unwrap_or_else(|| Task::ready(Ok(None)));
16496 drop(snapshot);
16497
16498 Some(cx.spawn_in(window, async move |this, cx| {
16499 let rename_range = if let Some(range) = prepare_rename.await? {
16500 Some(range)
16501 } else {
16502 this.update(cx, |this, cx| {
16503 let buffer = this.buffer.read(cx).snapshot(cx);
16504 let mut buffer_highlights = this
16505 .document_highlights_for_position(selection.head(), &buffer)
16506 .filter(|highlight| {
16507 highlight.start.excerpt_id == selection.head().excerpt_id
16508 && highlight.end.excerpt_id == selection.head().excerpt_id
16509 });
16510 buffer_highlights
16511 .next()
16512 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16513 })?
16514 };
16515 if let Some(rename_range) = rename_range {
16516 this.update_in(cx, |this, window, cx| {
16517 let snapshot = cursor_buffer.read(cx).snapshot();
16518 let rename_buffer_range = rename_range.to_offset(&snapshot);
16519 let cursor_offset_in_rename_range =
16520 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16521 let cursor_offset_in_rename_range_end =
16522 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16523
16524 this.take_rename(false, window, cx);
16525 let buffer = this.buffer.read(cx).read(cx);
16526 let cursor_offset = selection.head().to_offset(&buffer);
16527 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16528 let rename_end = rename_start + rename_buffer_range.len();
16529 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16530 let mut old_highlight_id = None;
16531 let old_name: Arc<str> = buffer
16532 .chunks(rename_start..rename_end, true)
16533 .map(|chunk| {
16534 if old_highlight_id.is_none() {
16535 old_highlight_id = chunk.syntax_highlight_id;
16536 }
16537 chunk.text
16538 })
16539 .collect::<String>()
16540 .into();
16541
16542 drop(buffer);
16543
16544 // Position the selection in the rename editor so that it matches the current selection.
16545 this.show_local_selections = false;
16546 let rename_editor = cx.new(|cx| {
16547 let mut editor = Editor::single_line(window, cx);
16548 editor.buffer.update(cx, |buffer, cx| {
16549 buffer.edit([(0..0, old_name.clone())], None, cx)
16550 });
16551 let rename_selection_range = match cursor_offset_in_rename_range
16552 .cmp(&cursor_offset_in_rename_range_end)
16553 {
16554 Ordering::Equal => {
16555 editor.select_all(&SelectAll, window, cx);
16556 return editor;
16557 }
16558 Ordering::Less => {
16559 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16560 }
16561 Ordering::Greater => {
16562 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16563 }
16564 };
16565 if rename_selection_range.end > old_name.len() {
16566 editor.select_all(&SelectAll, window, cx);
16567 } else {
16568 editor.change_selections(Default::default(), window, cx, |s| {
16569 s.select_ranges([rename_selection_range]);
16570 });
16571 }
16572 editor
16573 });
16574 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16575 if e == &EditorEvent::Focused {
16576 cx.emit(EditorEvent::FocusedIn)
16577 }
16578 })
16579 .detach();
16580
16581 let write_highlights =
16582 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16583 let read_highlights =
16584 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16585 let ranges = write_highlights
16586 .iter()
16587 .flat_map(|(_, ranges)| ranges.iter())
16588 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16589 .cloned()
16590 .collect();
16591
16592 this.highlight_text::<Rename>(
16593 ranges,
16594 HighlightStyle {
16595 fade_out: Some(0.6),
16596 ..Default::default()
16597 },
16598 cx,
16599 );
16600 let rename_focus_handle = rename_editor.focus_handle(cx);
16601 window.focus(&rename_focus_handle);
16602 let block_id = this.insert_blocks(
16603 [BlockProperties {
16604 style: BlockStyle::Flex,
16605 placement: BlockPlacement::Below(range.start),
16606 height: Some(1),
16607 render: Arc::new({
16608 let rename_editor = rename_editor.clone();
16609 move |cx: &mut BlockContext| {
16610 let mut text_style = cx.editor_style.text.clone();
16611 if let Some(highlight_style) = old_highlight_id
16612 .and_then(|h| h.style(&cx.editor_style.syntax))
16613 {
16614 text_style = text_style.highlight(highlight_style);
16615 }
16616 div()
16617 .block_mouse_except_scroll()
16618 .pl(cx.anchor_x)
16619 .child(EditorElement::new(
16620 &rename_editor,
16621 EditorStyle {
16622 background: cx.theme().system().transparent,
16623 local_player: cx.editor_style.local_player,
16624 text: text_style,
16625 scrollbar_width: cx.editor_style.scrollbar_width,
16626 syntax: cx.editor_style.syntax.clone(),
16627 status: cx.editor_style.status.clone(),
16628 inlay_hints_style: HighlightStyle {
16629 font_weight: Some(FontWeight::BOLD),
16630 ..make_inlay_hints_style(cx.app)
16631 },
16632 edit_prediction_styles: make_suggestion_styles(
16633 cx.app,
16634 ),
16635 ..EditorStyle::default()
16636 },
16637 ))
16638 .into_any_element()
16639 }
16640 }),
16641 priority: 0,
16642 }],
16643 Some(Autoscroll::fit()),
16644 cx,
16645 )[0];
16646 this.pending_rename = Some(RenameState {
16647 range,
16648 old_name,
16649 editor: rename_editor,
16650 block_id,
16651 });
16652 })?;
16653 }
16654
16655 Ok(())
16656 }))
16657 }
16658
16659 pub fn confirm_rename(
16660 &mut self,
16661 _: &ConfirmRename,
16662 window: &mut Window,
16663 cx: &mut Context<Self>,
16664 ) -> Option<Task<Result<()>>> {
16665 let rename = self.take_rename(false, window, cx)?;
16666 let workspace = self.workspace()?.downgrade();
16667 let (buffer, start) = self
16668 .buffer
16669 .read(cx)
16670 .text_anchor_for_position(rename.range.start, cx)?;
16671 let (end_buffer, _) = self
16672 .buffer
16673 .read(cx)
16674 .text_anchor_for_position(rename.range.end, cx)?;
16675 if buffer != end_buffer {
16676 return None;
16677 }
16678
16679 let old_name = rename.old_name;
16680 let new_name = rename.editor.read(cx).text(cx);
16681
16682 let rename = self.semantics_provider.as_ref()?.perform_rename(
16683 &buffer,
16684 start,
16685 new_name.clone(),
16686 cx,
16687 )?;
16688
16689 Some(cx.spawn_in(window, async move |editor, cx| {
16690 let project_transaction = rename.await?;
16691 Self::open_project_transaction(
16692 &editor,
16693 workspace,
16694 project_transaction,
16695 format!("Rename: {} → {}", old_name, new_name),
16696 cx,
16697 )
16698 .await?;
16699
16700 editor.update(cx, |editor, cx| {
16701 editor.refresh_document_highlights(cx);
16702 })?;
16703 Ok(())
16704 }))
16705 }
16706
16707 fn take_rename(
16708 &mut self,
16709 moving_cursor: bool,
16710 window: &mut Window,
16711 cx: &mut Context<Self>,
16712 ) -> Option<RenameState> {
16713 let rename = self.pending_rename.take()?;
16714 if rename.editor.focus_handle(cx).is_focused(window) {
16715 window.focus(&self.focus_handle);
16716 }
16717
16718 self.remove_blocks(
16719 [rename.block_id].into_iter().collect(),
16720 Some(Autoscroll::fit()),
16721 cx,
16722 );
16723 self.clear_highlights::<Rename>(cx);
16724 self.show_local_selections = true;
16725
16726 if moving_cursor {
16727 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16728 editor.selections.newest::<usize>(cx).head()
16729 });
16730
16731 // Update the selection to match the position of the selection inside
16732 // the rename editor.
16733 let snapshot = self.buffer.read(cx).read(cx);
16734 let rename_range = rename.range.to_offset(&snapshot);
16735 let cursor_in_editor = snapshot
16736 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16737 .min(rename_range.end);
16738 drop(snapshot);
16739
16740 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16741 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16742 });
16743 } else {
16744 self.refresh_document_highlights(cx);
16745 }
16746
16747 Some(rename)
16748 }
16749
16750 pub fn pending_rename(&self) -> Option<&RenameState> {
16751 self.pending_rename.as_ref()
16752 }
16753
16754 fn format(
16755 &mut self,
16756 _: &Format,
16757 window: &mut Window,
16758 cx: &mut Context<Self>,
16759 ) -> Option<Task<Result<()>>> {
16760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16761
16762 let project = match &self.project {
16763 Some(project) => project.clone(),
16764 None => return None,
16765 };
16766
16767 Some(self.perform_format(
16768 project,
16769 FormatTrigger::Manual,
16770 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16771 window,
16772 cx,
16773 ))
16774 }
16775
16776 fn format_selections(
16777 &mut self,
16778 _: &FormatSelections,
16779 window: &mut Window,
16780 cx: &mut Context<Self>,
16781 ) -> Option<Task<Result<()>>> {
16782 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16783
16784 let project = match &self.project {
16785 Some(project) => project.clone(),
16786 None => return None,
16787 };
16788
16789 let ranges = self
16790 .selections
16791 .all_adjusted(cx)
16792 .into_iter()
16793 .map(|selection| selection.range())
16794 .collect_vec();
16795
16796 Some(self.perform_format(
16797 project,
16798 FormatTrigger::Manual,
16799 FormatTarget::Ranges(ranges),
16800 window,
16801 cx,
16802 ))
16803 }
16804
16805 fn perform_format(
16806 &mut self,
16807 project: Entity<Project>,
16808 trigger: FormatTrigger,
16809 target: FormatTarget,
16810 window: &mut Window,
16811 cx: &mut Context<Self>,
16812 ) -> Task<Result<()>> {
16813 let buffer = self.buffer.clone();
16814 let (buffers, target) = match target {
16815 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16816 FormatTarget::Ranges(selection_ranges) => {
16817 let multi_buffer = buffer.read(cx);
16818 let snapshot = multi_buffer.read(cx);
16819 let mut buffers = HashSet::default();
16820 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16821 BTreeMap::new();
16822 for selection_range in selection_ranges {
16823 for (buffer, buffer_range, _) in
16824 snapshot.range_to_buffer_ranges(selection_range)
16825 {
16826 let buffer_id = buffer.remote_id();
16827 let start = buffer.anchor_before(buffer_range.start);
16828 let end = buffer.anchor_after(buffer_range.end);
16829 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16830 buffer_id_to_ranges
16831 .entry(buffer_id)
16832 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16833 .or_insert_with(|| vec![start..end]);
16834 }
16835 }
16836 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16837 }
16838 };
16839
16840 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16841 let selections_prev = transaction_id_prev
16842 .and_then(|transaction_id_prev| {
16843 // default to selections as they were after the last edit, if we have them,
16844 // instead of how they are now.
16845 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16846 // will take you back to where you made the last edit, instead of staying where you scrolled
16847 self.selection_history
16848 .transaction(transaction_id_prev)
16849 .map(|t| t.0.clone())
16850 })
16851 .unwrap_or_else(|| self.selections.disjoint_anchors());
16852
16853 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16854 let format = project.update(cx, |project, cx| {
16855 project.format(buffers, target, true, trigger, cx)
16856 });
16857
16858 cx.spawn_in(window, async move |editor, cx| {
16859 let transaction = futures::select_biased! {
16860 transaction = format.log_err().fuse() => transaction,
16861 () = timeout => {
16862 log::warn!("timed out waiting for formatting");
16863 None
16864 }
16865 };
16866
16867 buffer
16868 .update(cx, |buffer, cx| {
16869 if let Some(transaction) = transaction
16870 && !buffer.is_singleton()
16871 {
16872 buffer.push_transaction(&transaction.0, cx);
16873 }
16874 cx.notify();
16875 })
16876 .ok();
16877
16878 if let Some(transaction_id_now) =
16879 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16880 {
16881 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16882 if has_new_transaction {
16883 _ = editor.update(cx, |editor, _| {
16884 editor
16885 .selection_history
16886 .insert_transaction(transaction_id_now, selections_prev);
16887 });
16888 }
16889 }
16890
16891 Ok(())
16892 })
16893 }
16894
16895 fn organize_imports(
16896 &mut self,
16897 _: &OrganizeImports,
16898 window: &mut Window,
16899 cx: &mut Context<Self>,
16900 ) -> Option<Task<Result<()>>> {
16901 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16902 let project = match &self.project {
16903 Some(project) => project.clone(),
16904 None => return None,
16905 };
16906 Some(self.perform_code_action_kind(
16907 project,
16908 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16909 window,
16910 cx,
16911 ))
16912 }
16913
16914 fn perform_code_action_kind(
16915 &mut self,
16916 project: Entity<Project>,
16917 kind: CodeActionKind,
16918 window: &mut Window,
16919 cx: &mut Context<Self>,
16920 ) -> Task<Result<()>> {
16921 let buffer = self.buffer.clone();
16922 let buffers = buffer.read(cx).all_buffers();
16923 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16924 let apply_action = project.update(cx, |project, cx| {
16925 project.apply_code_action_kind(buffers, kind, true, cx)
16926 });
16927 cx.spawn_in(window, async move |_, cx| {
16928 let transaction = futures::select_biased! {
16929 () = timeout => {
16930 log::warn!("timed out waiting for executing code action");
16931 None
16932 }
16933 transaction = apply_action.log_err().fuse() => transaction,
16934 };
16935 buffer
16936 .update(cx, |buffer, cx| {
16937 // check if we need this
16938 if let Some(transaction) = transaction
16939 && !buffer.is_singleton()
16940 {
16941 buffer.push_transaction(&transaction.0, cx);
16942 }
16943 cx.notify();
16944 })
16945 .ok();
16946 Ok(())
16947 })
16948 }
16949
16950 pub fn restart_language_server(
16951 &mut self,
16952 _: &RestartLanguageServer,
16953 _: &mut Window,
16954 cx: &mut Context<Self>,
16955 ) {
16956 if let Some(project) = self.project.clone() {
16957 self.buffer.update(cx, |multi_buffer, cx| {
16958 project.update(cx, |project, cx| {
16959 project.restart_language_servers_for_buffers(
16960 multi_buffer.all_buffers().into_iter().collect(),
16961 HashSet::default(),
16962 cx,
16963 );
16964 });
16965 })
16966 }
16967 }
16968
16969 pub fn stop_language_server(
16970 &mut self,
16971 _: &StopLanguageServer,
16972 _: &mut Window,
16973 cx: &mut Context<Self>,
16974 ) {
16975 if let Some(project) = self.project.clone() {
16976 self.buffer.update(cx, |multi_buffer, cx| {
16977 project.update(cx, |project, cx| {
16978 project.stop_language_servers_for_buffers(
16979 multi_buffer.all_buffers().into_iter().collect(),
16980 HashSet::default(),
16981 cx,
16982 );
16983 cx.emit(project::Event::RefreshInlayHints);
16984 });
16985 });
16986 }
16987 }
16988
16989 fn cancel_language_server_work(
16990 workspace: &mut Workspace,
16991 _: &actions::CancelLanguageServerWork,
16992 _: &mut Window,
16993 cx: &mut Context<Workspace>,
16994 ) {
16995 let project = workspace.project();
16996 let buffers = workspace
16997 .active_item(cx)
16998 .and_then(|item| item.act_as::<Editor>(cx))
16999 .map_or(HashSet::default(), |editor| {
17000 editor.read(cx).buffer.read(cx).all_buffers()
17001 });
17002 project.update(cx, |project, cx| {
17003 project.cancel_language_server_work_for_buffers(buffers, cx);
17004 });
17005 }
17006
17007 fn show_character_palette(
17008 &mut self,
17009 _: &ShowCharacterPalette,
17010 window: &mut Window,
17011 _: &mut Context<Self>,
17012 ) {
17013 window.show_character_palette();
17014 }
17015
17016 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17017 if !self.diagnostics_enabled() {
17018 return;
17019 }
17020
17021 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17022 let buffer = self.buffer.read(cx).snapshot(cx);
17023 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17024 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17025 let is_valid = buffer
17026 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17027 .any(|entry| {
17028 entry.diagnostic.is_primary
17029 && !entry.range.is_empty()
17030 && entry.range.start == primary_range_start
17031 && entry.diagnostic.message == active_diagnostics.active_message
17032 });
17033
17034 if !is_valid {
17035 self.dismiss_diagnostics(cx);
17036 }
17037 }
17038 }
17039
17040 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17041 match &self.active_diagnostics {
17042 ActiveDiagnostic::Group(group) => Some(group),
17043 _ => None,
17044 }
17045 }
17046
17047 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17048 if !self.diagnostics_enabled() {
17049 return;
17050 }
17051 self.dismiss_diagnostics(cx);
17052 self.active_diagnostics = ActiveDiagnostic::All;
17053 }
17054
17055 fn activate_diagnostics(
17056 &mut self,
17057 buffer_id: BufferId,
17058 diagnostic: DiagnosticEntry<usize>,
17059 window: &mut Window,
17060 cx: &mut Context<Self>,
17061 ) {
17062 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17063 return;
17064 }
17065 self.dismiss_diagnostics(cx);
17066 let snapshot = self.snapshot(window, cx);
17067 let buffer = self.buffer.read(cx).snapshot(cx);
17068 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17069 return;
17070 };
17071
17072 let diagnostic_group = buffer
17073 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17074 .collect::<Vec<_>>();
17075
17076 let blocks =
17077 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17078
17079 let blocks = self.display_map.update(cx, |display_map, cx| {
17080 display_map.insert_blocks(blocks, cx).into_iter().collect()
17081 });
17082 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17083 active_range: buffer.anchor_before(diagnostic.range.start)
17084 ..buffer.anchor_after(diagnostic.range.end),
17085 active_message: diagnostic.diagnostic.message.clone(),
17086 group_id: diagnostic.diagnostic.group_id,
17087 blocks,
17088 });
17089 cx.notify();
17090 }
17091
17092 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17093 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17094 return;
17095 };
17096
17097 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17098 if let ActiveDiagnostic::Group(group) = prev {
17099 self.display_map.update(cx, |display_map, cx| {
17100 display_map.remove_blocks(group.blocks, cx);
17101 });
17102 cx.notify();
17103 }
17104 }
17105
17106 /// Disable inline diagnostics rendering for this editor.
17107 pub fn disable_inline_diagnostics(&mut self) {
17108 self.inline_diagnostics_enabled = false;
17109 self.inline_diagnostics_update = Task::ready(());
17110 self.inline_diagnostics.clear();
17111 }
17112
17113 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17114 self.diagnostics_enabled = false;
17115 self.dismiss_diagnostics(cx);
17116 self.inline_diagnostics_update = Task::ready(());
17117 self.inline_diagnostics.clear();
17118 }
17119
17120 pub fn diagnostics_enabled(&self) -> bool {
17121 self.diagnostics_enabled && self.mode.is_full()
17122 }
17123
17124 pub fn inline_diagnostics_enabled(&self) -> bool {
17125 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17126 }
17127
17128 pub fn show_inline_diagnostics(&self) -> bool {
17129 self.show_inline_diagnostics
17130 }
17131
17132 pub fn toggle_inline_diagnostics(
17133 &mut self,
17134 _: &ToggleInlineDiagnostics,
17135 window: &mut Window,
17136 cx: &mut Context<Editor>,
17137 ) {
17138 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17139 self.refresh_inline_diagnostics(false, window, cx);
17140 }
17141
17142 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17143 self.diagnostics_max_severity = severity;
17144 self.display_map.update(cx, |display_map, _| {
17145 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17146 });
17147 }
17148
17149 pub fn toggle_diagnostics(
17150 &mut self,
17151 _: &ToggleDiagnostics,
17152 window: &mut Window,
17153 cx: &mut Context<Editor>,
17154 ) {
17155 if !self.diagnostics_enabled() {
17156 return;
17157 }
17158
17159 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17160 EditorSettings::get_global(cx)
17161 .diagnostics_max_severity
17162 .filter(|severity| severity != &DiagnosticSeverity::Off)
17163 .unwrap_or(DiagnosticSeverity::Hint)
17164 } else {
17165 DiagnosticSeverity::Off
17166 };
17167 self.set_max_diagnostics_severity(new_severity, cx);
17168 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17169 self.active_diagnostics = ActiveDiagnostic::None;
17170 self.inline_diagnostics_update = Task::ready(());
17171 self.inline_diagnostics.clear();
17172 } else {
17173 self.refresh_inline_diagnostics(false, window, cx);
17174 }
17175
17176 cx.notify();
17177 }
17178
17179 pub fn toggle_minimap(
17180 &mut self,
17181 _: &ToggleMinimap,
17182 window: &mut Window,
17183 cx: &mut Context<Editor>,
17184 ) {
17185 if self.supports_minimap(cx) {
17186 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17187 }
17188 }
17189
17190 fn refresh_inline_diagnostics(
17191 &mut self,
17192 debounce: bool,
17193 window: &mut Window,
17194 cx: &mut Context<Self>,
17195 ) {
17196 let max_severity = ProjectSettings::get_global(cx)
17197 .diagnostics
17198 .inline
17199 .max_severity
17200 .unwrap_or(self.diagnostics_max_severity);
17201
17202 if !self.inline_diagnostics_enabled()
17203 || !self.show_inline_diagnostics
17204 || max_severity == DiagnosticSeverity::Off
17205 {
17206 self.inline_diagnostics_update = Task::ready(());
17207 self.inline_diagnostics.clear();
17208 return;
17209 }
17210
17211 let debounce_ms = ProjectSettings::get_global(cx)
17212 .diagnostics
17213 .inline
17214 .update_debounce_ms;
17215 let debounce = if debounce && debounce_ms > 0 {
17216 Some(Duration::from_millis(debounce_ms))
17217 } else {
17218 None
17219 };
17220 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17221 if let Some(debounce) = debounce {
17222 cx.background_executor().timer(debounce).await;
17223 }
17224 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17225 editor
17226 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17227 .ok()
17228 }) else {
17229 return;
17230 };
17231
17232 let new_inline_diagnostics = cx
17233 .background_spawn(async move {
17234 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17235 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17236 let message = diagnostic_entry
17237 .diagnostic
17238 .message
17239 .split_once('\n')
17240 .map(|(line, _)| line)
17241 .map(SharedString::new)
17242 .unwrap_or_else(|| {
17243 SharedString::from(diagnostic_entry.diagnostic.message)
17244 });
17245 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17246 let (Ok(i) | Err(i)) = inline_diagnostics
17247 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17248 inline_diagnostics.insert(
17249 i,
17250 (
17251 start_anchor,
17252 InlineDiagnostic {
17253 message,
17254 group_id: diagnostic_entry.diagnostic.group_id,
17255 start: diagnostic_entry.range.start.to_point(&snapshot),
17256 is_primary: diagnostic_entry.diagnostic.is_primary,
17257 severity: diagnostic_entry.diagnostic.severity,
17258 },
17259 ),
17260 );
17261 }
17262 inline_diagnostics
17263 })
17264 .await;
17265
17266 editor
17267 .update(cx, |editor, cx| {
17268 editor.inline_diagnostics = new_inline_diagnostics;
17269 cx.notify();
17270 })
17271 .ok();
17272 });
17273 }
17274
17275 fn pull_diagnostics(
17276 &mut self,
17277 buffer_id: Option<BufferId>,
17278 window: &Window,
17279 cx: &mut Context<Self>,
17280 ) -> Option<()> {
17281 if !self.mode().is_full() {
17282 return None;
17283 }
17284 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17285 .diagnostics
17286 .lsp_pull_diagnostics;
17287 if !pull_diagnostics_settings.enabled {
17288 return None;
17289 }
17290 let project = self.project()?.downgrade();
17291 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17292 let mut buffers = self.buffer.read(cx).all_buffers();
17293 if let Some(buffer_id) = buffer_id {
17294 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17295 }
17296
17297 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17298 cx.background_executor().timer(debounce).await;
17299
17300 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17301 buffers
17302 .into_iter()
17303 .filter_map(|buffer| {
17304 project
17305 .update(cx, |project, cx| {
17306 project.lsp_store().update(cx, |lsp_store, cx| {
17307 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17308 })
17309 })
17310 .ok()
17311 })
17312 .collect::<FuturesUnordered<_>>()
17313 }) else {
17314 return;
17315 };
17316
17317 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17318 match pull_task {
17319 Ok(()) => {
17320 if editor
17321 .update_in(cx, |editor, window, cx| {
17322 editor.update_diagnostics_state(window, cx);
17323 })
17324 .is_err()
17325 {
17326 return;
17327 }
17328 }
17329 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17330 }
17331 }
17332 });
17333
17334 Some(())
17335 }
17336
17337 pub fn set_selections_from_remote(
17338 &mut self,
17339 selections: Vec<Selection<Anchor>>,
17340 pending_selection: Option<Selection<Anchor>>,
17341 window: &mut Window,
17342 cx: &mut Context<Self>,
17343 ) {
17344 let old_cursor_position = self.selections.newest_anchor().head();
17345 self.selections.change_with(cx, |s| {
17346 s.select_anchors(selections);
17347 if let Some(pending_selection) = pending_selection {
17348 s.set_pending(pending_selection, SelectMode::Character);
17349 } else {
17350 s.clear_pending();
17351 }
17352 });
17353 self.selections_did_change(
17354 false,
17355 &old_cursor_position,
17356 SelectionEffects::default(),
17357 window,
17358 cx,
17359 );
17360 }
17361
17362 pub fn transact(
17363 &mut self,
17364 window: &mut Window,
17365 cx: &mut Context<Self>,
17366 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17367 ) -> Option<TransactionId> {
17368 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17369 this.start_transaction_at(Instant::now(), window, cx);
17370 update(this, window, cx);
17371 this.end_transaction_at(Instant::now(), cx)
17372 })
17373 }
17374
17375 pub fn start_transaction_at(
17376 &mut self,
17377 now: Instant,
17378 window: &mut Window,
17379 cx: &mut Context<Self>,
17380 ) -> Option<TransactionId> {
17381 self.end_selection(window, cx);
17382 if let Some(tx_id) = self
17383 .buffer
17384 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17385 {
17386 self.selection_history
17387 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17388 cx.emit(EditorEvent::TransactionBegun {
17389 transaction_id: tx_id,
17390 });
17391 Some(tx_id)
17392 } else {
17393 None
17394 }
17395 }
17396
17397 pub fn end_transaction_at(
17398 &mut self,
17399 now: Instant,
17400 cx: &mut Context<Self>,
17401 ) -> Option<TransactionId> {
17402 if let Some(transaction_id) = self
17403 .buffer
17404 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17405 {
17406 if let Some((_, end_selections)) =
17407 self.selection_history.transaction_mut(transaction_id)
17408 {
17409 *end_selections = Some(self.selections.disjoint_anchors());
17410 } else {
17411 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17412 }
17413
17414 cx.emit(EditorEvent::Edited { transaction_id });
17415 Some(transaction_id)
17416 } else {
17417 None
17418 }
17419 }
17420
17421 pub fn modify_transaction_selection_history(
17422 &mut self,
17423 transaction_id: TransactionId,
17424 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17425 ) -> bool {
17426 self.selection_history
17427 .transaction_mut(transaction_id)
17428 .map(modify)
17429 .is_some()
17430 }
17431
17432 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17433 if self.selection_mark_mode {
17434 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17435 s.move_with(|_, sel| {
17436 sel.collapse_to(sel.head(), SelectionGoal::None);
17437 });
17438 })
17439 }
17440 self.selection_mark_mode = true;
17441 cx.notify();
17442 }
17443
17444 pub fn swap_selection_ends(
17445 &mut self,
17446 _: &actions::SwapSelectionEnds,
17447 window: &mut Window,
17448 cx: &mut Context<Self>,
17449 ) {
17450 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17451 s.move_with(|_, sel| {
17452 if sel.start != sel.end {
17453 sel.reversed = !sel.reversed
17454 }
17455 });
17456 });
17457 self.request_autoscroll(Autoscroll::newest(), cx);
17458 cx.notify();
17459 }
17460
17461 pub fn toggle_focus(
17462 workspace: &mut Workspace,
17463 _: &actions::ToggleFocus,
17464 window: &mut Window,
17465 cx: &mut Context<Workspace>,
17466 ) {
17467 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17468 return;
17469 };
17470 workspace.activate_item(&item, true, true, window, cx);
17471 }
17472
17473 pub fn toggle_fold(
17474 &mut self,
17475 _: &actions::ToggleFold,
17476 window: &mut Window,
17477 cx: &mut Context<Self>,
17478 ) {
17479 if self.is_singleton(cx) {
17480 let selection = self.selections.newest::<Point>(cx);
17481
17482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17483 let range = if selection.is_empty() {
17484 let point = selection.head().to_display_point(&display_map);
17485 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17486 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17487 .to_point(&display_map);
17488 start..end
17489 } else {
17490 selection.range()
17491 };
17492 if display_map.folds_in_range(range).next().is_some() {
17493 self.unfold_lines(&Default::default(), window, cx)
17494 } else {
17495 self.fold(&Default::default(), window, cx)
17496 }
17497 } else {
17498 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17499 let buffer_ids: HashSet<_> = self
17500 .selections
17501 .disjoint_anchor_ranges()
17502 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17503 .collect();
17504
17505 let should_unfold = buffer_ids
17506 .iter()
17507 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17508
17509 for buffer_id in buffer_ids {
17510 if should_unfold {
17511 self.unfold_buffer(buffer_id, cx);
17512 } else {
17513 self.fold_buffer(buffer_id, cx);
17514 }
17515 }
17516 }
17517 }
17518
17519 pub fn toggle_fold_recursive(
17520 &mut self,
17521 _: &actions::ToggleFoldRecursive,
17522 window: &mut Window,
17523 cx: &mut Context<Self>,
17524 ) {
17525 let selection = self.selections.newest::<Point>(cx);
17526
17527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17528 let range = if selection.is_empty() {
17529 let point = selection.head().to_display_point(&display_map);
17530 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17531 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17532 .to_point(&display_map);
17533 start..end
17534 } else {
17535 selection.range()
17536 };
17537 if display_map.folds_in_range(range).next().is_some() {
17538 self.unfold_recursive(&Default::default(), window, cx)
17539 } else {
17540 self.fold_recursive(&Default::default(), window, cx)
17541 }
17542 }
17543
17544 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17545 if self.is_singleton(cx) {
17546 let mut to_fold = Vec::new();
17547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17548 let selections = self.selections.all_adjusted(cx);
17549
17550 for selection in selections {
17551 let range = selection.range().sorted();
17552 let buffer_start_row = range.start.row;
17553
17554 if range.start.row != range.end.row {
17555 let mut found = false;
17556 let mut row = range.start.row;
17557 while row <= range.end.row {
17558 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17559 {
17560 found = true;
17561 row = crease.range().end.row + 1;
17562 to_fold.push(crease);
17563 } else {
17564 row += 1
17565 }
17566 }
17567 if found {
17568 continue;
17569 }
17570 }
17571
17572 for row in (0..=range.start.row).rev() {
17573 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17574 && crease.range().end.row >= buffer_start_row
17575 {
17576 to_fold.push(crease);
17577 if row <= range.start.row {
17578 break;
17579 }
17580 }
17581 }
17582 }
17583
17584 self.fold_creases(to_fold, true, window, cx);
17585 } else {
17586 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17587 let buffer_ids = self
17588 .selections
17589 .disjoint_anchor_ranges()
17590 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17591 .collect::<HashSet<_>>();
17592 for buffer_id in buffer_ids {
17593 self.fold_buffer(buffer_id, cx);
17594 }
17595 }
17596 }
17597
17598 pub fn toggle_fold_all(
17599 &mut self,
17600 _: &actions::ToggleFoldAll,
17601 window: &mut Window,
17602 cx: &mut Context<Self>,
17603 ) {
17604 if self.buffer.read(cx).is_singleton() {
17605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17606 let has_folds = display_map
17607 .folds_in_range(0..display_map.buffer_snapshot.len())
17608 .next()
17609 .is_some();
17610
17611 if has_folds {
17612 self.unfold_all(&actions::UnfoldAll, window, cx);
17613 } else {
17614 self.fold_all(&actions::FoldAll, window, cx);
17615 }
17616 } else {
17617 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17618 let should_unfold = buffer_ids
17619 .iter()
17620 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17621
17622 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17623 editor
17624 .update_in(cx, |editor, _, cx| {
17625 for buffer_id in buffer_ids {
17626 if should_unfold {
17627 editor.unfold_buffer(buffer_id, cx);
17628 } else {
17629 editor.fold_buffer(buffer_id, cx);
17630 }
17631 }
17632 })
17633 .ok();
17634 });
17635 }
17636 }
17637
17638 fn fold_at_level(
17639 &mut self,
17640 fold_at: &FoldAtLevel,
17641 window: &mut Window,
17642 cx: &mut Context<Self>,
17643 ) {
17644 if !self.buffer.read(cx).is_singleton() {
17645 return;
17646 }
17647
17648 let fold_at_level = fold_at.0;
17649 let snapshot = self.buffer.read(cx).snapshot(cx);
17650 let mut to_fold = Vec::new();
17651 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17652
17653 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17654 while start_row < end_row {
17655 match self
17656 .snapshot(window, cx)
17657 .crease_for_buffer_row(MultiBufferRow(start_row))
17658 {
17659 Some(crease) => {
17660 let nested_start_row = crease.range().start.row + 1;
17661 let nested_end_row = crease.range().end.row;
17662
17663 if current_level < fold_at_level {
17664 stack.push((nested_start_row, nested_end_row, current_level + 1));
17665 } else if current_level == fold_at_level {
17666 to_fold.push(crease);
17667 }
17668
17669 start_row = nested_end_row + 1;
17670 }
17671 None => start_row += 1,
17672 }
17673 }
17674 }
17675
17676 self.fold_creases(to_fold, true, window, cx);
17677 }
17678
17679 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17680 if self.buffer.read(cx).is_singleton() {
17681 let mut fold_ranges = Vec::new();
17682 let snapshot = self.buffer.read(cx).snapshot(cx);
17683
17684 for row in 0..snapshot.max_row().0 {
17685 if let Some(foldable_range) = self
17686 .snapshot(window, cx)
17687 .crease_for_buffer_row(MultiBufferRow(row))
17688 {
17689 fold_ranges.push(foldable_range);
17690 }
17691 }
17692
17693 self.fold_creases(fold_ranges, true, window, cx);
17694 } else {
17695 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17696 editor
17697 .update_in(cx, |editor, _, cx| {
17698 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17699 editor.fold_buffer(buffer_id, cx);
17700 }
17701 })
17702 .ok();
17703 });
17704 }
17705 }
17706
17707 pub fn fold_function_bodies(
17708 &mut self,
17709 _: &actions::FoldFunctionBodies,
17710 window: &mut Window,
17711 cx: &mut Context<Self>,
17712 ) {
17713 let snapshot = self.buffer.read(cx).snapshot(cx);
17714
17715 let ranges = snapshot
17716 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17717 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17718 .collect::<Vec<_>>();
17719
17720 let creases = ranges
17721 .into_iter()
17722 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17723 .collect();
17724
17725 self.fold_creases(creases, true, window, cx);
17726 }
17727
17728 pub fn fold_recursive(
17729 &mut self,
17730 _: &actions::FoldRecursive,
17731 window: &mut Window,
17732 cx: &mut Context<Self>,
17733 ) {
17734 let mut to_fold = Vec::new();
17735 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17736 let selections = self.selections.all_adjusted(cx);
17737
17738 for selection in selections {
17739 let range = selection.range().sorted();
17740 let buffer_start_row = range.start.row;
17741
17742 if range.start.row != range.end.row {
17743 let mut found = false;
17744 for row in range.start.row..=range.end.row {
17745 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17746 found = true;
17747 to_fold.push(crease);
17748 }
17749 }
17750 if found {
17751 continue;
17752 }
17753 }
17754
17755 for row in (0..=range.start.row).rev() {
17756 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17757 if crease.range().end.row >= buffer_start_row {
17758 to_fold.push(crease);
17759 } else {
17760 break;
17761 }
17762 }
17763 }
17764 }
17765
17766 self.fold_creases(to_fold, true, window, cx);
17767 }
17768
17769 pub fn fold_at(
17770 &mut self,
17771 buffer_row: MultiBufferRow,
17772 window: &mut Window,
17773 cx: &mut Context<Self>,
17774 ) {
17775 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17776
17777 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17778 let autoscroll = self
17779 .selections
17780 .all::<Point>(cx)
17781 .iter()
17782 .any(|selection| crease.range().overlaps(&selection.range()));
17783
17784 self.fold_creases(vec![crease], autoscroll, window, cx);
17785 }
17786 }
17787
17788 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17789 if self.is_singleton(cx) {
17790 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17791 let buffer = &display_map.buffer_snapshot;
17792 let selections = self.selections.all::<Point>(cx);
17793 let ranges = selections
17794 .iter()
17795 .map(|s| {
17796 let range = s.display_range(&display_map).sorted();
17797 let mut start = range.start.to_point(&display_map);
17798 let mut end = range.end.to_point(&display_map);
17799 start.column = 0;
17800 end.column = buffer.line_len(MultiBufferRow(end.row));
17801 start..end
17802 })
17803 .collect::<Vec<_>>();
17804
17805 self.unfold_ranges(&ranges, true, true, cx);
17806 } else {
17807 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17808 let buffer_ids = self
17809 .selections
17810 .disjoint_anchor_ranges()
17811 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17812 .collect::<HashSet<_>>();
17813 for buffer_id in buffer_ids {
17814 self.unfold_buffer(buffer_id, cx);
17815 }
17816 }
17817 }
17818
17819 pub fn unfold_recursive(
17820 &mut self,
17821 _: &UnfoldRecursive,
17822 _window: &mut Window,
17823 cx: &mut Context<Self>,
17824 ) {
17825 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17826 let selections = self.selections.all::<Point>(cx);
17827 let ranges = selections
17828 .iter()
17829 .map(|s| {
17830 let mut range = s.display_range(&display_map).sorted();
17831 *range.start.column_mut() = 0;
17832 *range.end.column_mut() = display_map.line_len(range.end.row());
17833 let start = range.start.to_point(&display_map);
17834 let end = range.end.to_point(&display_map);
17835 start..end
17836 })
17837 .collect::<Vec<_>>();
17838
17839 self.unfold_ranges(&ranges, true, true, cx);
17840 }
17841
17842 pub fn unfold_at(
17843 &mut self,
17844 buffer_row: MultiBufferRow,
17845 _window: &mut Window,
17846 cx: &mut Context<Self>,
17847 ) {
17848 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17849
17850 let intersection_range = Point::new(buffer_row.0, 0)
17851 ..Point::new(
17852 buffer_row.0,
17853 display_map.buffer_snapshot.line_len(buffer_row),
17854 );
17855
17856 let autoscroll = self
17857 .selections
17858 .all::<Point>(cx)
17859 .iter()
17860 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17861
17862 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17863 }
17864
17865 pub fn unfold_all(
17866 &mut self,
17867 _: &actions::UnfoldAll,
17868 _window: &mut Window,
17869 cx: &mut Context<Self>,
17870 ) {
17871 if self.buffer.read(cx).is_singleton() {
17872 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17873 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17874 } else {
17875 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17876 editor
17877 .update(cx, |editor, cx| {
17878 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17879 editor.unfold_buffer(buffer_id, cx);
17880 }
17881 })
17882 .ok();
17883 });
17884 }
17885 }
17886
17887 pub fn fold_selected_ranges(
17888 &mut self,
17889 _: &FoldSelectedRanges,
17890 window: &mut Window,
17891 cx: &mut Context<Self>,
17892 ) {
17893 let selections = self.selections.all_adjusted(cx);
17894 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17895 let ranges = selections
17896 .into_iter()
17897 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17898 .collect::<Vec<_>>();
17899 self.fold_creases(ranges, true, window, cx);
17900 }
17901
17902 pub fn fold_ranges<T: ToOffset + Clone>(
17903 &mut self,
17904 ranges: Vec<Range<T>>,
17905 auto_scroll: bool,
17906 window: &mut Window,
17907 cx: &mut Context<Self>,
17908 ) {
17909 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17910 let ranges = ranges
17911 .into_iter()
17912 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17913 .collect::<Vec<_>>();
17914 self.fold_creases(ranges, auto_scroll, window, cx);
17915 }
17916
17917 pub fn fold_creases<T: ToOffset + Clone>(
17918 &mut self,
17919 creases: Vec<Crease<T>>,
17920 auto_scroll: bool,
17921 _window: &mut Window,
17922 cx: &mut Context<Self>,
17923 ) {
17924 if creases.is_empty() {
17925 return;
17926 }
17927
17928 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17929
17930 if auto_scroll {
17931 self.request_autoscroll(Autoscroll::fit(), cx);
17932 }
17933
17934 cx.notify();
17935
17936 self.scrollbar_marker_state.dirty = true;
17937 self.folds_did_change(cx);
17938 }
17939
17940 /// Removes any folds whose ranges intersect any of the given ranges.
17941 pub fn unfold_ranges<T: ToOffset + Clone>(
17942 &mut self,
17943 ranges: &[Range<T>],
17944 inclusive: bool,
17945 auto_scroll: bool,
17946 cx: &mut Context<Self>,
17947 ) {
17948 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17949 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17950 });
17951 self.folds_did_change(cx);
17952 }
17953
17954 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17955 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17956 return;
17957 }
17958 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17959 self.display_map.update(cx, |display_map, cx| {
17960 display_map.fold_buffers([buffer_id], cx)
17961 });
17962 cx.emit(EditorEvent::BufferFoldToggled {
17963 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17964 folded: true,
17965 });
17966 cx.notify();
17967 }
17968
17969 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17970 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17971 return;
17972 }
17973 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17974 self.display_map.update(cx, |display_map, cx| {
17975 display_map.unfold_buffers([buffer_id], cx);
17976 });
17977 cx.emit(EditorEvent::BufferFoldToggled {
17978 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17979 folded: false,
17980 });
17981 cx.notify();
17982 }
17983
17984 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17985 self.display_map.read(cx).is_buffer_folded(buffer)
17986 }
17987
17988 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17989 self.display_map.read(cx).folded_buffers()
17990 }
17991
17992 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17993 self.display_map.update(cx, |display_map, cx| {
17994 display_map.disable_header_for_buffer(buffer_id, cx);
17995 });
17996 cx.notify();
17997 }
17998
17999 /// Removes any folds with the given ranges.
18000 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18001 &mut self,
18002 ranges: &[Range<T>],
18003 type_id: TypeId,
18004 auto_scroll: bool,
18005 cx: &mut Context<Self>,
18006 ) {
18007 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18008 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18009 });
18010 self.folds_did_change(cx);
18011 }
18012
18013 fn remove_folds_with<T: ToOffset + Clone>(
18014 &mut self,
18015 ranges: &[Range<T>],
18016 auto_scroll: bool,
18017 cx: &mut Context<Self>,
18018 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18019 ) {
18020 if ranges.is_empty() {
18021 return;
18022 }
18023
18024 let mut buffers_affected = HashSet::default();
18025 let multi_buffer = self.buffer().read(cx);
18026 for range in ranges {
18027 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18028 buffers_affected.insert(buffer.read(cx).remote_id());
18029 };
18030 }
18031
18032 self.display_map.update(cx, update);
18033
18034 if auto_scroll {
18035 self.request_autoscroll(Autoscroll::fit(), cx);
18036 }
18037
18038 cx.notify();
18039 self.scrollbar_marker_state.dirty = true;
18040 self.active_indent_guides_state.dirty = true;
18041 }
18042
18043 pub fn update_renderer_widths(
18044 &mut self,
18045 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18046 cx: &mut Context<Self>,
18047 ) -> bool {
18048 self.display_map
18049 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18050 }
18051
18052 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18053 self.display_map.read(cx).fold_placeholder.clone()
18054 }
18055
18056 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18057 self.buffer.update(cx, |buffer, cx| {
18058 buffer.set_all_diff_hunks_expanded(cx);
18059 });
18060 }
18061
18062 pub fn expand_all_diff_hunks(
18063 &mut self,
18064 _: &ExpandAllDiffHunks,
18065 _window: &mut Window,
18066 cx: &mut Context<Self>,
18067 ) {
18068 self.buffer.update(cx, |buffer, cx| {
18069 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18070 });
18071 }
18072
18073 pub fn toggle_selected_diff_hunks(
18074 &mut self,
18075 _: &ToggleSelectedDiffHunks,
18076 _window: &mut Window,
18077 cx: &mut Context<Self>,
18078 ) {
18079 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18080 self.toggle_diff_hunks_in_ranges(ranges, cx);
18081 }
18082
18083 pub fn diff_hunks_in_ranges<'a>(
18084 &'a self,
18085 ranges: &'a [Range<Anchor>],
18086 buffer: &'a MultiBufferSnapshot,
18087 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18088 ranges.iter().flat_map(move |range| {
18089 let end_excerpt_id = range.end.excerpt_id;
18090 let range = range.to_point(buffer);
18091 let mut peek_end = range.end;
18092 if range.end.row < buffer.max_row().0 {
18093 peek_end = Point::new(range.end.row + 1, 0);
18094 }
18095 buffer
18096 .diff_hunks_in_range(range.start..peek_end)
18097 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18098 })
18099 }
18100
18101 pub fn has_stageable_diff_hunks_in_ranges(
18102 &self,
18103 ranges: &[Range<Anchor>],
18104 snapshot: &MultiBufferSnapshot,
18105 ) -> bool {
18106 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18107 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18108 }
18109
18110 pub fn toggle_staged_selected_diff_hunks(
18111 &mut self,
18112 _: &::git::ToggleStaged,
18113 _: &mut Window,
18114 cx: &mut Context<Self>,
18115 ) {
18116 let snapshot = self.buffer.read(cx).snapshot(cx);
18117 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18118 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18119 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18120 }
18121
18122 pub fn set_render_diff_hunk_controls(
18123 &mut self,
18124 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18125 cx: &mut Context<Self>,
18126 ) {
18127 self.render_diff_hunk_controls = render_diff_hunk_controls;
18128 cx.notify();
18129 }
18130
18131 pub fn stage_and_next(
18132 &mut self,
18133 _: &::git::StageAndNext,
18134 window: &mut Window,
18135 cx: &mut Context<Self>,
18136 ) {
18137 self.do_stage_or_unstage_and_next(true, window, cx);
18138 }
18139
18140 pub fn unstage_and_next(
18141 &mut self,
18142 _: &::git::UnstageAndNext,
18143 window: &mut Window,
18144 cx: &mut Context<Self>,
18145 ) {
18146 self.do_stage_or_unstage_and_next(false, window, cx);
18147 }
18148
18149 pub fn stage_or_unstage_diff_hunks(
18150 &mut self,
18151 stage: bool,
18152 ranges: Vec<Range<Anchor>>,
18153 cx: &mut Context<Self>,
18154 ) {
18155 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18156 cx.spawn(async move |this, cx| {
18157 task.await?;
18158 this.update(cx, |this, cx| {
18159 let snapshot = this.buffer.read(cx).snapshot(cx);
18160 let chunk_by = this
18161 .diff_hunks_in_ranges(&ranges, &snapshot)
18162 .chunk_by(|hunk| hunk.buffer_id);
18163 for (buffer_id, hunks) in &chunk_by {
18164 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18165 }
18166 })
18167 })
18168 .detach_and_log_err(cx);
18169 }
18170
18171 fn save_buffers_for_ranges_if_needed(
18172 &mut self,
18173 ranges: &[Range<Anchor>],
18174 cx: &mut Context<Editor>,
18175 ) -> Task<Result<()>> {
18176 let multibuffer = self.buffer.read(cx);
18177 let snapshot = multibuffer.read(cx);
18178 let buffer_ids: HashSet<_> = ranges
18179 .iter()
18180 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18181 .collect();
18182 drop(snapshot);
18183
18184 let mut buffers = HashSet::default();
18185 for buffer_id in buffer_ids {
18186 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18187 let buffer = buffer_entity.read(cx);
18188 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18189 {
18190 buffers.insert(buffer_entity);
18191 }
18192 }
18193 }
18194
18195 if let Some(project) = &self.project {
18196 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18197 } else {
18198 Task::ready(Ok(()))
18199 }
18200 }
18201
18202 fn do_stage_or_unstage_and_next(
18203 &mut self,
18204 stage: bool,
18205 window: &mut Window,
18206 cx: &mut Context<Self>,
18207 ) {
18208 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18209
18210 if ranges.iter().any(|range| range.start != range.end) {
18211 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18212 return;
18213 }
18214
18215 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18216 let snapshot = self.snapshot(window, cx);
18217 let position = self.selections.newest::<Point>(cx).head();
18218 let mut row = snapshot
18219 .buffer_snapshot
18220 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18221 .find(|hunk| hunk.row_range.start.0 > position.row)
18222 .map(|hunk| hunk.row_range.start);
18223
18224 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18225 // Outside of the project diff editor, wrap around to the beginning.
18226 if !all_diff_hunks_expanded {
18227 row = row.or_else(|| {
18228 snapshot
18229 .buffer_snapshot
18230 .diff_hunks_in_range(Point::zero()..position)
18231 .find(|hunk| hunk.row_range.end.0 < position.row)
18232 .map(|hunk| hunk.row_range.start)
18233 });
18234 }
18235
18236 if let Some(row) = row {
18237 let destination = Point::new(row.0, 0);
18238 let autoscroll = Autoscroll::center();
18239
18240 self.unfold_ranges(&[destination..destination], false, false, cx);
18241 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18242 s.select_ranges([destination..destination]);
18243 });
18244 }
18245 }
18246
18247 fn do_stage_or_unstage(
18248 &self,
18249 stage: bool,
18250 buffer_id: BufferId,
18251 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18252 cx: &mut App,
18253 ) -> Option<()> {
18254 let project = self.project()?;
18255 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18256 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18257 let buffer_snapshot = buffer.read(cx).snapshot();
18258 let file_exists = buffer_snapshot
18259 .file()
18260 .is_some_and(|file| file.disk_state().exists());
18261 diff.update(cx, |diff, cx| {
18262 diff.stage_or_unstage_hunks(
18263 stage,
18264 &hunks
18265 .map(|hunk| buffer_diff::DiffHunk {
18266 buffer_range: hunk.buffer_range,
18267 diff_base_byte_range: hunk.diff_base_byte_range,
18268 secondary_status: hunk.secondary_status,
18269 range: Point::zero()..Point::zero(), // unused
18270 })
18271 .collect::<Vec<_>>(),
18272 &buffer_snapshot,
18273 file_exists,
18274 cx,
18275 )
18276 });
18277 None
18278 }
18279
18280 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18281 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18282 self.buffer
18283 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18284 }
18285
18286 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18287 self.buffer.update(cx, |buffer, cx| {
18288 let ranges = vec![Anchor::min()..Anchor::max()];
18289 if !buffer.all_diff_hunks_expanded()
18290 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18291 {
18292 buffer.collapse_diff_hunks(ranges, cx);
18293 true
18294 } else {
18295 false
18296 }
18297 })
18298 }
18299
18300 fn toggle_diff_hunks_in_ranges(
18301 &mut self,
18302 ranges: Vec<Range<Anchor>>,
18303 cx: &mut Context<Editor>,
18304 ) {
18305 self.buffer.update(cx, |buffer, cx| {
18306 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18307 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18308 })
18309 }
18310
18311 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18312 self.buffer.update(cx, |buffer, cx| {
18313 let snapshot = buffer.snapshot(cx);
18314 let excerpt_id = range.end.excerpt_id;
18315 let point_range = range.to_point(&snapshot);
18316 let expand = !buffer.single_hunk_is_expanded(range, cx);
18317 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18318 })
18319 }
18320
18321 pub(crate) fn apply_all_diff_hunks(
18322 &mut self,
18323 _: &ApplyAllDiffHunks,
18324 window: &mut Window,
18325 cx: &mut Context<Self>,
18326 ) {
18327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18328
18329 let buffers = self.buffer.read(cx).all_buffers();
18330 for branch_buffer in buffers {
18331 branch_buffer.update(cx, |branch_buffer, cx| {
18332 branch_buffer.merge_into_base(Vec::new(), cx);
18333 });
18334 }
18335
18336 if let Some(project) = self.project.clone() {
18337 self.save(
18338 SaveOptions {
18339 format: true,
18340 autosave: false,
18341 },
18342 project,
18343 window,
18344 cx,
18345 )
18346 .detach_and_log_err(cx);
18347 }
18348 }
18349
18350 pub(crate) fn apply_selected_diff_hunks(
18351 &mut self,
18352 _: &ApplyDiffHunk,
18353 window: &mut Window,
18354 cx: &mut Context<Self>,
18355 ) {
18356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18357 let snapshot = self.snapshot(window, cx);
18358 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18359 let mut ranges_by_buffer = HashMap::default();
18360 self.transact(window, cx, |editor, _window, cx| {
18361 for hunk in hunks {
18362 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18363 ranges_by_buffer
18364 .entry(buffer.clone())
18365 .or_insert_with(Vec::new)
18366 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18367 }
18368 }
18369
18370 for (buffer, ranges) in ranges_by_buffer {
18371 buffer.update(cx, |buffer, cx| {
18372 buffer.merge_into_base(ranges, cx);
18373 });
18374 }
18375 });
18376
18377 if let Some(project) = self.project.clone() {
18378 self.save(
18379 SaveOptions {
18380 format: true,
18381 autosave: false,
18382 },
18383 project,
18384 window,
18385 cx,
18386 )
18387 .detach_and_log_err(cx);
18388 }
18389 }
18390
18391 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18392 if hovered != self.gutter_hovered {
18393 self.gutter_hovered = hovered;
18394 cx.notify();
18395 }
18396 }
18397
18398 pub fn insert_blocks(
18399 &mut self,
18400 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18401 autoscroll: Option<Autoscroll>,
18402 cx: &mut Context<Self>,
18403 ) -> Vec<CustomBlockId> {
18404 let blocks = self
18405 .display_map
18406 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18407 if let Some(autoscroll) = autoscroll {
18408 self.request_autoscroll(autoscroll, cx);
18409 }
18410 cx.notify();
18411 blocks
18412 }
18413
18414 pub fn resize_blocks(
18415 &mut self,
18416 heights: HashMap<CustomBlockId, u32>,
18417 autoscroll: Option<Autoscroll>,
18418 cx: &mut Context<Self>,
18419 ) {
18420 self.display_map
18421 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18422 if let Some(autoscroll) = autoscroll {
18423 self.request_autoscroll(autoscroll, cx);
18424 }
18425 cx.notify();
18426 }
18427
18428 pub fn replace_blocks(
18429 &mut self,
18430 renderers: HashMap<CustomBlockId, RenderBlock>,
18431 autoscroll: Option<Autoscroll>,
18432 cx: &mut Context<Self>,
18433 ) {
18434 self.display_map
18435 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18436 if let Some(autoscroll) = autoscroll {
18437 self.request_autoscroll(autoscroll, cx);
18438 }
18439 cx.notify();
18440 }
18441
18442 pub fn remove_blocks(
18443 &mut self,
18444 block_ids: HashSet<CustomBlockId>,
18445 autoscroll: Option<Autoscroll>,
18446 cx: &mut Context<Self>,
18447 ) {
18448 self.display_map.update(cx, |display_map, cx| {
18449 display_map.remove_blocks(block_ids, cx)
18450 });
18451 if let Some(autoscroll) = autoscroll {
18452 self.request_autoscroll(autoscroll, cx);
18453 }
18454 cx.notify();
18455 }
18456
18457 pub fn row_for_block(
18458 &self,
18459 block_id: CustomBlockId,
18460 cx: &mut Context<Self>,
18461 ) -> Option<DisplayRow> {
18462 self.display_map
18463 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18464 }
18465
18466 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18467 self.focused_block = Some(focused_block);
18468 }
18469
18470 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18471 self.focused_block.take()
18472 }
18473
18474 pub fn insert_creases(
18475 &mut self,
18476 creases: impl IntoIterator<Item = Crease<Anchor>>,
18477 cx: &mut Context<Self>,
18478 ) -> Vec<CreaseId> {
18479 self.display_map
18480 .update(cx, |map, cx| map.insert_creases(creases, cx))
18481 }
18482
18483 pub fn remove_creases(
18484 &mut self,
18485 ids: impl IntoIterator<Item = CreaseId>,
18486 cx: &mut Context<Self>,
18487 ) -> Vec<(CreaseId, Range<Anchor>)> {
18488 self.display_map
18489 .update(cx, |map, cx| map.remove_creases(ids, cx))
18490 }
18491
18492 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18493 self.display_map
18494 .update(cx, |map, cx| map.snapshot(cx))
18495 .longest_row()
18496 }
18497
18498 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18499 self.display_map
18500 .update(cx, |map, cx| map.snapshot(cx))
18501 .max_point()
18502 }
18503
18504 pub fn text(&self, cx: &App) -> String {
18505 self.buffer.read(cx).read(cx).text()
18506 }
18507
18508 pub fn is_empty(&self, cx: &App) -> bool {
18509 self.buffer.read(cx).read(cx).is_empty()
18510 }
18511
18512 pub fn text_option(&self, cx: &App) -> Option<String> {
18513 let text = self.text(cx);
18514 let text = text.trim();
18515
18516 if text.is_empty() {
18517 return None;
18518 }
18519
18520 Some(text.to_string())
18521 }
18522
18523 pub fn set_text(
18524 &mut self,
18525 text: impl Into<Arc<str>>,
18526 window: &mut Window,
18527 cx: &mut Context<Self>,
18528 ) {
18529 self.transact(window, cx, |this, _, cx| {
18530 this.buffer
18531 .read(cx)
18532 .as_singleton()
18533 .expect("you can only call set_text on editors for singleton buffers")
18534 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18535 });
18536 }
18537
18538 pub fn display_text(&self, cx: &mut App) -> String {
18539 self.display_map
18540 .update(cx, |map, cx| map.snapshot(cx))
18541 .text()
18542 }
18543
18544 fn create_minimap(
18545 &self,
18546 minimap_settings: MinimapSettings,
18547 window: &mut Window,
18548 cx: &mut Context<Self>,
18549 ) -> Option<Entity<Self>> {
18550 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18551 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18552 }
18553
18554 fn initialize_new_minimap(
18555 &self,
18556 minimap_settings: MinimapSettings,
18557 window: &mut Window,
18558 cx: &mut Context<Self>,
18559 ) -> Entity<Self> {
18560 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18561
18562 let mut minimap = Editor::new_internal(
18563 EditorMode::Minimap {
18564 parent: cx.weak_entity(),
18565 },
18566 self.buffer.clone(),
18567 None,
18568 Some(self.display_map.clone()),
18569 window,
18570 cx,
18571 );
18572 minimap.scroll_manager.clone_state(&self.scroll_manager);
18573 minimap.set_text_style_refinement(TextStyleRefinement {
18574 font_size: Some(MINIMAP_FONT_SIZE),
18575 font_weight: Some(MINIMAP_FONT_WEIGHT),
18576 ..Default::default()
18577 });
18578 minimap.update_minimap_configuration(minimap_settings, cx);
18579 cx.new(|_| minimap)
18580 }
18581
18582 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18583 let current_line_highlight = minimap_settings
18584 .current_line_highlight
18585 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18586 self.set_current_line_highlight(Some(current_line_highlight));
18587 }
18588
18589 pub fn minimap(&self) -> Option<&Entity<Self>> {
18590 self.minimap
18591 .as_ref()
18592 .filter(|_| self.minimap_visibility.visible())
18593 }
18594
18595 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18596 let mut wrap_guides = smallvec![];
18597
18598 if self.show_wrap_guides == Some(false) {
18599 return wrap_guides;
18600 }
18601
18602 let settings = self.buffer.read(cx).language_settings(cx);
18603 if settings.show_wrap_guides {
18604 match self.soft_wrap_mode(cx) {
18605 SoftWrap::Column(soft_wrap) => {
18606 wrap_guides.push((soft_wrap as usize, true));
18607 }
18608 SoftWrap::Bounded(soft_wrap) => {
18609 wrap_guides.push((soft_wrap as usize, true));
18610 }
18611 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18612 }
18613 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18614 }
18615
18616 wrap_guides
18617 }
18618
18619 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18620 let settings = self.buffer.read(cx).language_settings(cx);
18621 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18622 match mode {
18623 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18624 SoftWrap::None
18625 }
18626 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18627 language_settings::SoftWrap::PreferredLineLength => {
18628 SoftWrap::Column(settings.preferred_line_length)
18629 }
18630 language_settings::SoftWrap::Bounded => {
18631 SoftWrap::Bounded(settings.preferred_line_length)
18632 }
18633 }
18634 }
18635
18636 pub fn set_soft_wrap_mode(
18637 &mut self,
18638 mode: language_settings::SoftWrap,
18639
18640 cx: &mut Context<Self>,
18641 ) {
18642 self.soft_wrap_mode_override = Some(mode);
18643 cx.notify();
18644 }
18645
18646 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18647 self.hard_wrap = hard_wrap;
18648 cx.notify();
18649 }
18650
18651 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18652 self.text_style_refinement = Some(style);
18653 }
18654
18655 /// called by the Element so we know what style we were most recently rendered with.
18656 pub(crate) fn set_style(
18657 &mut self,
18658 style: EditorStyle,
18659 window: &mut Window,
18660 cx: &mut Context<Self>,
18661 ) {
18662 // We intentionally do not inform the display map about the minimap style
18663 // so that wrapping is not recalculated and stays consistent for the editor
18664 // and its linked minimap.
18665 if !self.mode.is_minimap() {
18666 let rem_size = window.rem_size();
18667 self.display_map.update(cx, |map, cx| {
18668 map.set_font(
18669 style.text.font(),
18670 style.text.font_size.to_pixels(rem_size),
18671 cx,
18672 )
18673 });
18674 }
18675 self.style = Some(style);
18676 }
18677
18678 pub fn style(&self) -> Option<&EditorStyle> {
18679 self.style.as_ref()
18680 }
18681
18682 // Called by the element. This method is not designed to be called outside of the editor
18683 // element's layout code because it does not notify when rewrapping is computed synchronously.
18684 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18685 self.display_map
18686 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18687 }
18688
18689 pub fn set_soft_wrap(&mut self) {
18690 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18691 }
18692
18693 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18694 if self.soft_wrap_mode_override.is_some() {
18695 self.soft_wrap_mode_override.take();
18696 } else {
18697 let soft_wrap = match self.soft_wrap_mode(cx) {
18698 SoftWrap::GitDiff => return,
18699 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18700 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18701 language_settings::SoftWrap::None
18702 }
18703 };
18704 self.soft_wrap_mode_override = Some(soft_wrap);
18705 }
18706 cx.notify();
18707 }
18708
18709 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18710 let Some(workspace) = self.workspace() else {
18711 return;
18712 };
18713 let fs = workspace.read(cx).app_state().fs.clone();
18714 let current_show = TabBarSettings::get_global(cx).show;
18715 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18716 setting.show = Some(!current_show);
18717 });
18718 }
18719
18720 pub fn toggle_indent_guides(
18721 &mut self,
18722 _: &ToggleIndentGuides,
18723 _: &mut Window,
18724 cx: &mut Context<Self>,
18725 ) {
18726 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18727 self.buffer
18728 .read(cx)
18729 .language_settings(cx)
18730 .indent_guides
18731 .enabled
18732 });
18733 self.show_indent_guides = Some(!currently_enabled);
18734 cx.notify();
18735 }
18736
18737 fn should_show_indent_guides(&self) -> Option<bool> {
18738 self.show_indent_guides
18739 }
18740
18741 pub fn toggle_line_numbers(
18742 &mut self,
18743 _: &ToggleLineNumbers,
18744 _: &mut Window,
18745 cx: &mut Context<Self>,
18746 ) {
18747 let mut editor_settings = EditorSettings::get_global(cx).clone();
18748 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18749 EditorSettings::override_global(editor_settings, cx);
18750 }
18751
18752 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18753 if let Some(show_line_numbers) = self.show_line_numbers {
18754 return show_line_numbers;
18755 }
18756 EditorSettings::get_global(cx).gutter.line_numbers
18757 }
18758
18759 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18760 self.use_relative_line_numbers
18761 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18762 }
18763
18764 pub fn toggle_relative_line_numbers(
18765 &mut self,
18766 _: &ToggleRelativeLineNumbers,
18767 _: &mut Window,
18768 cx: &mut Context<Self>,
18769 ) {
18770 let is_relative = self.should_use_relative_line_numbers(cx);
18771 self.set_relative_line_number(Some(!is_relative), cx)
18772 }
18773
18774 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18775 self.use_relative_line_numbers = is_relative;
18776 cx.notify();
18777 }
18778
18779 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18780 self.show_gutter = show_gutter;
18781 cx.notify();
18782 }
18783
18784 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18785 self.show_scrollbars = ScrollbarAxes {
18786 horizontal: show,
18787 vertical: show,
18788 };
18789 cx.notify();
18790 }
18791
18792 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18793 self.show_scrollbars.vertical = show;
18794 cx.notify();
18795 }
18796
18797 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18798 self.show_scrollbars.horizontal = show;
18799 cx.notify();
18800 }
18801
18802 pub fn set_minimap_visibility(
18803 &mut self,
18804 minimap_visibility: MinimapVisibility,
18805 window: &mut Window,
18806 cx: &mut Context<Self>,
18807 ) {
18808 if self.minimap_visibility != minimap_visibility {
18809 if minimap_visibility.visible() && self.minimap.is_none() {
18810 let minimap_settings = EditorSettings::get_global(cx).minimap;
18811 self.minimap =
18812 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18813 }
18814 self.minimap_visibility = minimap_visibility;
18815 cx.notify();
18816 }
18817 }
18818
18819 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18820 self.set_show_scrollbars(false, cx);
18821 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18822 }
18823
18824 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18825 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18826 }
18827
18828 /// Normally the text in full mode and auto height editors is padded on the
18829 /// left side by roughly half a character width for improved hit testing.
18830 ///
18831 /// Use this method to disable this for cases where this is not wanted (e.g.
18832 /// if you want to align the editor text with some other text above or below)
18833 /// or if you want to add this padding to single-line editors.
18834 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18835 self.offset_content = offset_content;
18836 cx.notify();
18837 }
18838
18839 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18840 self.show_line_numbers = Some(show_line_numbers);
18841 cx.notify();
18842 }
18843
18844 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18845 self.disable_expand_excerpt_buttons = true;
18846 cx.notify();
18847 }
18848
18849 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18850 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18851 cx.notify();
18852 }
18853
18854 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18855 self.show_code_actions = Some(show_code_actions);
18856 cx.notify();
18857 }
18858
18859 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18860 self.show_runnables = Some(show_runnables);
18861 cx.notify();
18862 }
18863
18864 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18865 self.show_breakpoints = Some(show_breakpoints);
18866 cx.notify();
18867 }
18868
18869 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18870 if self.display_map.read(cx).masked != masked {
18871 self.display_map.update(cx, |map, _| map.masked = masked);
18872 }
18873 cx.notify()
18874 }
18875
18876 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18877 self.show_wrap_guides = Some(show_wrap_guides);
18878 cx.notify();
18879 }
18880
18881 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18882 self.show_indent_guides = Some(show_indent_guides);
18883 cx.notify();
18884 }
18885
18886 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18887 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18888 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18889 && let Some(dir) = file.abs_path(cx).parent()
18890 {
18891 return Some(dir.to_owned());
18892 }
18893
18894 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18895 return Some(project_path.path.to_path_buf());
18896 }
18897 }
18898
18899 None
18900 }
18901
18902 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18903 self.active_excerpt(cx)?
18904 .1
18905 .read(cx)
18906 .file()
18907 .and_then(|f| f.as_local())
18908 }
18909
18910 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18911 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18912 let buffer = buffer.read(cx);
18913 if let Some(project_path) = buffer.project_path(cx) {
18914 let project = self.project()?.read(cx);
18915 project.absolute_path(&project_path, cx)
18916 } else {
18917 buffer
18918 .file()
18919 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18920 }
18921 })
18922 }
18923
18924 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18925 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18926 let project_path = buffer.read(cx).project_path(cx)?;
18927 let project = self.project()?.read(cx);
18928 let entry = project.entry_for_path(&project_path, cx)?;
18929 let path = entry.path.to_path_buf();
18930 Some(path)
18931 })
18932 }
18933
18934 pub fn reveal_in_finder(
18935 &mut self,
18936 _: &RevealInFileManager,
18937 _window: &mut Window,
18938 cx: &mut Context<Self>,
18939 ) {
18940 if let Some(target) = self.target_file(cx) {
18941 cx.reveal_path(&target.abs_path(cx));
18942 }
18943 }
18944
18945 pub fn copy_path(
18946 &mut self,
18947 _: &zed_actions::workspace::CopyPath,
18948 _window: &mut Window,
18949 cx: &mut Context<Self>,
18950 ) {
18951 if let Some(path) = self.target_file_abs_path(cx)
18952 && let Some(path) = path.to_str()
18953 {
18954 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18955 }
18956 }
18957
18958 pub fn copy_relative_path(
18959 &mut self,
18960 _: &zed_actions::workspace::CopyRelativePath,
18961 _window: &mut Window,
18962 cx: &mut Context<Self>,
18963 ) {
18964 if let Some(path) = self.target_file_path(cx)
18965 && let Some(path) = path.to_str()
18966 {
18967 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18968 }
18969 }
18970
18971 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18972 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18973 buffer.read(cx).project_path(cx)
18974 } else {
18975 None
18976 }
18977 }
18978
18979 // Returns true if the editor handled a go-to-line request
18980 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18981 maybe!({
18982 let breakpoint_store = self.breakpoint_store.as_ref()?;
18983
18984 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18985 else {
18986 self.clear_row_highlights::<ActiveDebugLine>();
18987 return None;
18988 };
18989
18990 let position = active_stack_frame.position;
18991 let buffer_id = position.buffer_id?;
18992 let snapshot = self
18993 .project
18994 .as_ref()?
18995 .read(cx)
18996 .buffer_for_id(buffer_id, cx)?
18997 .read(cx)
18998 .snapshot();
18999
19000 let mut handled = false;
19001 for (id, ExcerptRange { context, .. }) in
19002 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19003 {
19004 if context.start.cmp(&position, &snapshot).is_ge()
19005 || context.end.cmp(&position, &snapshot).is_lt()
19006 {
19007 continue;
19008 }
19009 let snapshot = self.buffer.read(cx).snapshot(cx);
19010 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19011
19012 handled = true;
19013 self.clear_row_highlights::<ActiveDebugLine>();
19014
19015 self.go_to_line::<ActiveDebugLine>(
19016 multibuffer_anchor,
19017 Some(cx.theme().colors().editor_debugger_active_line_background),
19018 window,
19019 cx,
19020 );
19021
19022 cx.notify();
19023 }
19024
19025 handled.then_some(())
19026 })
19027 .is_some()
19028 }
19029
19030 pub fn copy_file_name_without_extension(
19031 &mut self,
19032 _: &CopyFileNameWithoutExtension,
19033 _: &mut Window,
19034 cx: &mut Context<Self>,
19035 ) {
19036 if let Some(file) = self.target_file(cx)
19037 && let Some(file_stem) = file.path().file_stem()
19038 && let Some(name) = file_stem.to_str()
19039 {
19040 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19041 }
19042 }
19043
19044 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19045 if let Some(file) = self.target_file(cx)
19046 && let Some(file_name) = file.path().file_name()
19047 && let Some(name) = file_name.to_str()
19048 {
19049 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19050 }
19051 }
19052
19053 pub fn toggle_git_blame(
19054 &mut self,
19055 _: &::git::Blame,
19056 window: &mut Window,
19057 cx: &mut Context<Self>,
19058 ) {
19059 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19060
19061 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19062 self.start_git_blame(true, window, cx);
19063 }
19064
19065 cx.notify();
19066 }
19067
19068 pub fn toggle_git_blame_inline(
19069 &mut self,
19070 _: &ToggleGitBlameInline,
19071 window: &mut Window,
19072 cx: &mut Context<Self>,
19073 ) {
19074 self.toggle_git_blame_inline_internal(true, window, cx);
19075 cx.notify();
19076 }
19077
19078 pub fn open_git_blame_commit(
19079 &mut self,
19080 _: &OpenGitBlameCommit,
19081 window: &mut Window,
19082 cx: &mut Context<Self>,
19083 ) {
19084 self.open_git_blame_commit_internal(window, cx);
19085 }
19086
19087 fn open_git_blame_commit_internal(
19088 &mut self,
19089 window: &mut Window,
19090 cx: &mut Context<Self>,
19091 ) -> Option<()> {
19092 let blame = self.blame.as_ref()?;
19093 let snapshot = self.snapshot(window, cx);
19094 let cursor = self.selections.newest::<Point>(cx).head();
19095 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19096 let (_, blame_entry) = blame
19097 .update(cx, |blame, cx| {
19098 blame
19099 .blame_for_rows(
19100 &[RowInfo {
19101 buffer_id: Some(buffer.remote_id()),
19102 buffer_row: Some(point.row),
19103 ..Default::default()
19104 }],
19105 cx,
19106 )
19107 .next()
19108 })
19109 .flatten()?;
19110 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19111 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19112 let workspace = self.workspace()?.downgrade();
19113 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19114 None
19115 }
19116
19117 pub fn git_blame_inline_enabled(&self) -> bool {
19118 self.git_blame_inline_enabled
19119 }
19120
19121 pub fn toggle_selection_menu(
19122 &mut self,
19123 _: &ToggleSelectionMenu,
19124 _: &mut Window,
19125 cx: &mut Context<Self>,
19126 ) {
19127 self.show_selection_menu = self
19128 .show_selection_menu
19129 .map(|show_selections_menu| !show_selections_menu)
19130 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19131
19132 cx.notify();
19133 }
19134
19135 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19136 self.show_selection_menu
19137 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19138 }
19139
19140 fn start_git_blame(
19141 &mut self,
19142 user_triggered: bool,
19143 window: &mut Window,
19144 cx: &mut Context<Self>,
19145 ) {
19146 if let Some(project) = self.project() {
19147 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19148 && buffer.read(cx).file().is_none()
19149 {
19150 return;
19151 }
19152
19153 let focused = self.focus_handle(cx).contains_focused(window, cx);
19154
19155 let project = project.clone();
19156 let blame = cx
19157 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19158 self.blame_subscription =
19159 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19160 self.blame = Some(blame);
19161 }
19162 }
19163
19164 fn toggle_git_blame_inline_internal(
19165 &mut self,
19166 user_triggered: bool,
19167 window: &mut Window,
19168 cx: &mut Context<Self>,
19169 ) {
19170 if self.git_blame_inline_enabled {
19171 self.git_blame_inline_enabled = false;
19172 self.show_git_blame_inline = false;
19173 self.show_git_blame_inline_delay_task.take();
19174 } else {
19175 self.git_blame_inline_enabled = true;
19176 self.start_git_blame_inline(user_triggered, window, cx);
19177 }
19178
19179 cx.notify();
19180 }
19181
19182 fn start_git_blame_inline(
19183 &mut self,
19184 user_triggered: bool,
19185 window: &mut Window,
19186 cx: &mut Context<Self>,
19187 ) {
19188 self.start_git_blame(user_triggered, window, cx);
19189
19190 if ProjectSettings::get_global(cx)
19191 .git
19192 .inline_blame_delay()
19193 .is_some()
19194 {
19195 self.start_inline_blame_timer(window, cx);
19196 } else {
19197 self.show_git_blame_inline = true
19198 }
19199 }
19200
19201 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19202 self.blame.as_ref()
19203 }
19204
19205 pub fn show_git_blame_gutter(&self) -> bool {
19206 self.show_git_blame_gutter
19207 }
19208
19209 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19210 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19211 }
19212
19213 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19214 self.show_git_blame_inline
19215 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19216 && !self.newest_selection_head_on_empty_line(cx)
19217 && self.has_blame_entries(cx)
19218 }
19219
19220 fn has_blame_entries(&self, cx: &App) -> bool {
19221 self.blame()
19222 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19223 }
19224
19225 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19226 let cursor_anchor = self.selections.newest_anchor().head();
19227
19228 let snapshot = self.buffer.read(cx).snapshot(cx);
19229 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19230
19231 snapshot.line_len(buffer_row) == 0
19232 }
19233
19234 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19235 let buffer_and_selection = maybe!({
19236 let selection = self.selections.newest::<Point>(cx);
19237 let selection_range = selection.range();
19238
19239 let multi_buffer = self.buffer().read(cx);
19240 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19241 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19242
19243 let (buffer, range, _) = if selection.reversed {
19244 buffer_ranges.first()
19245 } else {
19246 buffer_ranges.last()
19247 }?;
19248
19249 let selection = text::ToPoint::to_point(&range.start, buffer).row
19250 ..text::ToPoint::to_point(&range.end, buffer).row;
19251 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19252 });
19253
19254 let Some((buffer, selection)) = buffer_and_selection else {
19255 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19256 };
19257
19258 let Some(project) = self.project() else {
19259 return Task::ready(Err(anyhow!("editor does not have project")));
19260 };
19261
19262 project.update(cx, |project, cx| {
19263 project.get_permalink_to_line(&buffer, selection, cx)
19264 })
19265 }
19266
19267 pub fn copy_permalink_to_line(
19268 &mut self,
19269 _: &CopyPermalinkToLine,
19270 window: &mut Window,
19271 cx: &mut Context<Self>,
19272 ) {
19273 let permalink_task = self.get_permalink_to_line(cx);
19274 let workspace = self.workspace();
19275
19276 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19277 Ok(permalink) => {
19278 cx.update(|_, cx| {
19279 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19280 })
19281 .ok();
19282 }
19283 Err(err) => {
19284 let message = format!("Failed to copy permalink: {err}");
19285
19286 anyhow::Result::<()>::Err(err).log_err();
19287
19288 if let Some(workspace) = workspace {
19289 workspace
19290 .update_in(cx, |workspace, _, cx| {
19291 struct CopyPermalinkToLine;
19292
19293 workspace.show_toast(
19294 Toast::new(
19295 NotificationId::unique::<CopyPermalinkToLine>(),
19296 message,
19297 ),
19298 cx,
19299 )
19300 })
19301 .ok();
19302 }
19303 }
19304 })
19305 .detach();
19306 }
19307
19308 pub fn copy_file_location(
19309 &mut self,
19310 _: &CopyFileLocation,
19311 _: &mut Window,
19312 cx: &mut Context<Self>,
19313 ) {
19314 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19315 if let Some(file) = self.target_file(cx)
19316 && let Some(path) = file.path().to_str()
19317 {
19318 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19319 }
19320 }
19321
19322 pub fn open_permalink_to_line(
19323 &mut self,
19324 _: &OpenPermalinkToLine,
19325 window: &mut Window,
19326 cx: &mut Context<Self>,
19327 ) {
19328 let permalink_task = self.get_permalink_to_line(cx);
19329 let workspace = self.workspace();
19330
19331 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19332 Ok(permalink) => {
19333 cx.update(|_, cx| {
19334 cx.open_url(permalink.as_ref());
19335 })
19336 .ok();
19337 }
19338 Err(err) => {
19339 let message = format!("Failed to open permalink: {err}");
19340
19341 anyhow::Result::<()>::Err(err).log_err();
19342
19343 if let Some(workspace) = workspace {
19344 workspace
19345 .update(cx, |workspace, cx| {
19346 struct OpenPermalinkToLine;
19347
19348 workspace.show_toast(
19349 Toast::new(
19350 NotificationId::unique::<OpenPermalinkToLine>(),
19351 message,
19352 ),
19353 cx,
19354 )
19355 })
19356 .ok();
19357 }
19358 }
19359 })
19360 .detach();
19361 }
19362
19363 pub fn insert_uuid_v4(
19364 &mut self,
19365 _: &InsertUuidV4,
19366 window: &mut Window,
19367 cx: &mut Context<Self>,
19368 ) {
19369 self.insert_uuid(UuidVersion::V4, window, cx);
19370 }
19371
19372 pub fn insert_uuid_v7(
19373 &mut self,
19374 _: &InsertUuidV7,
19375 window: &mut Window,
19376 cx: &mut Context<Self>,
19377 ) {
19378 self.insert_uuid(UuidVersion::V7, window, cx);
19379 }
19380
19381 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19382 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19383 self.transact(window, cx, |this, window, cx| {
19384 let edits = this
19385 .selections
19386 .all::<Point>(cx)
19387 .into_iter()
19388 .map(|selection| {
19389 let uuid = match version {
19390 UuidVersion::V4 => uuid::Uuid::new_v4(),
19391 UuidVersion::V7 => uuid::Uuid::now_v7(),
19392 };
19393
19394 (selection.range(), uuid.to_string())
19395 });
19396 this.edit(edits, cx);
19397 this.refresh_edit_prediction(true, false, window, cx);
19398 });
19399 }
19400
19401 pub fn open_selections_in_multibuffer(
19402 &mut self,
19403 _: &OpenSelectionsInMultibuffer,
19404 window: &mut Window,
19405 cx: &mut Context<Self>,
19406 ) {
19407 let multibuffer = self.buffer.read(cx);
19408
19409 let Some(buffer) = multibuffer.as_singleton() else {
19410 return;
19411 };
19412
19413 let Some(workspace) = self.workspace() else {
19414 return;
19415 };
19416
19417 let title = multibuffer.title(cx).to_string();
19418
19419 let locations = self
19420 .selections
19421 .all_anchors(cx)
19422 .iter()
19423 .map(|selection| Location {
19424 buffer: buffer.clone(),
19425 range: selection.start.text_anchor..selection.end.text_anchor,
19426 })
19427 .collect::<Vec<_>>();
19428
19429 cx.spawn_in(window, async move |_, cx| {
19430 workspace.update_in(cx, |workspace, window, cx| {
19431 Self::open_locations_in_multibuffer(
19432 workspace,
19433 locations,
19434 format!("Selections for '{title}'"),
19435 false,
19436 MultibufferSelectionMode::All,
19437 window,
19438 cx,
19439 );
19440 })
19441 })
19442 .detach();
19443 }
19444
19445 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19446 /// last highlight added will be used.
19447 ///
19448 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19449 pub fn highlight_rows<T: 'static>(
19450 &mut self,
19451 range: Range<Anchor>,
19452 color: Hsla,
19453 options: RowHighlightOptions,
19454 cx: &mut Context<Self>,
19455 ) {
19456 let snapshot = self.buffer().read(cx).snapshot(cx);
19457 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19458 let ix = row_highlights.binary_search_by(|highlight| {
19459 Ordering::Equal
19460 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19461 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19462 });
19463
19464 if let Err(mut ix) = ix {
19465 let index = post_inc(&mut self.highlight_order);
19466
19467 // If this range intersects with the preceding highlight, then merge it with
19468 // the preceding highlight. Otherwise insert a new highlight.
19469 let mut merged = false;
19470 if ix > 0 {
19471 let prev_highlight = &mut row_highlights[ix - 1];
19472 if prev_highlight
19473 .range
19474 .end
19475 .cmp(&range.start, &snapshot)
19476 .is_ge()
19477 {
19478 ix -= 1;
19479 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19480 prev_highlight.range.end = range.end;
19481 }
19482 merged = true;
19483 prev_highlight.index = index;
19484 prev_highlight.color = color;
19485 prev_highlight.options = options;
19486 }
19487 }
19488
19489 if !merged {
19490 row_highlights.insert(
19491 ix,
19492 RowHighlight {
19493 range,
19494 index,
19495 color,
19496 options,
19497 type_id: TypeId::of::<T>(),
19498 },
19499 );
19500 }
19501
19502 // If any of the following highlights intersect with this one, merge them.
19503 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19504 let highlight = &row_highlights[ix];
19505 if next_highlight
19506 .range
19507 .start
19508 .cmp(&highlight.range.end, &snapshot)
19509 .is_le()
19510 {
19511 if next_highlight
19512 .range
19513 .end
19514 .cmp(&highlight.range.end, &snapshot)
19515 .is_gt()
19516 {
19517 row_highlights[ix].range.end = next_highlight.range.end;
19518 }
19519 row_highlights.remove(ix + 1);
19520 } else {
19521 break;
19522 }
19523 }
19524 }
19525 }
19526
19527 /// Remove any highlighted row ranges of the given type that intersect the
19528 /// given ranges.
19529 pub fn remove_highlighted_rows<T: 'static>(
19530 &mut self,
19531 ranges_to_remove: Vec<Range<Anchor>>,
19532 cx: &mut Context<Self>,
19533 ) {
19534 let snapshot = self.buffer().read(cx).snapshot(cx);
19535 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19536 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19537 row_highlights.retain(|highlight| {
19538 while let Some(range_to_remove) = ranges_to_remove.peek() {
19539 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19540 Ordering::Less | Ordering::Equal => {
19541 ranges_to_remove.next();
19542 }
19543 Ordering::Greater => {
19544 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19545 Ordering::Less | Ordering::Equal => {
19546 return false;
19547 }
19548 Ordering::Greater => break,
19549 }
19550 }
19551 }
19552 }
19553
19554 true
19555 })
19556 }
19557
19558 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19559 pub fn clear_row_highlights<T: 'static>(&mut self) {
19560 self.highlighted_rows.remove(&TypeId::of::<T>());
19561 }
19562
19563 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19564 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19565 self.highlighted_rows
19566 .get(&TypeId::of::<T>())
19567 .map_or(&[] as &[_], |vec| vec.as_slice())
19568 .iter()
19569 .map(|highlight| (highlight.range.clone(), highlight.color))
19570 }
19571
19572 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19573 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19574 /// Allows to ignore certain kinds of highlights.
19575 pub fn highlighted_display_rows(
19576 &self,
19577 window: &mut Window,
19578 cx: &mut App,
19579 ) -> BTreeMap<DisplayRow, LineHighlight> {
19580 let snapshot = self.snapshot(window, cx);
19581 let mut used_highlight_orders = HashMap::default();
19582 self.highlighted_rows
19583 .iter()
19584 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19585 .fold(
19586 BTreeMap::<DisplayRow, LineHighlight>::new(),
19587 |mut unique_rows, highlight| {
19588 let start = highlight.range.start.to_display_point(&snapshot);
19589 let end = highlight.range.end.to_display_point(&snapshot);
19590 let start_row = start.row().0;
19591 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19592 && end.column() == 0
19593 {
19594 end.row().0.saturating_sub(1)
19595 } else {
19596 end.row().0
19597 };
19598 for row in start_row..=end_row {
19599 let used_index =
19600 used_highlight_orders.entry(row).or_insert(highlight.index);
19601 if highlight.index >= *used_index {
19602 *used_index = highlight.index;
19603 unique_rows.insert(
19604 DisplayRow(row),
19605 LineHighlight {
19606 include_gutter: highlight.options.include_gutter,
19607 border: None,
19608 background: highlight.color.into(),
19609 type_id: Some(highlight.type_id),
19610 },
19611 );
19612 }
19613 }
19614 unique_rows
19615 },
19616 )
19617 }
19618
19619 pub fn highlighted_display_row_for_autoscroll(
19620 &self,
19621 snapshot: &DisplaySnapshot,
19622 ) -> Option<DisplayRow> {
19623 self.highlighted_rows
19624 .values()
19625 .flat_map(|highlighted_rows| highlighted_rows.iter())
19626 .filter_map(|highlight| {
19627 if highlight.options.autoscroll {
19628 Some(highlight.range.start.to_display_point(snapshot).row())
19629 } else {
19630 None
19631 }
19632 })
19633 .min()
19634 }
19635
19636 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19637 self.highlight_background::<SearchWithinRange>(
19638 ranges,
19639 |colors| colors.colors().editor_document_highlight_read_background,
19640 cx,
19641 )
19642 }
19643
19644 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19645 self.breadcrumb_header = Some(new_header);
19646 }
19647
19648 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19649 self.clear_background_highlights::<SearchWithinRange>(cx);
19650 }
19651
19652 pub fn highlight_background<T: 'static>(
19653 &mut self,
19654 ranges: &[Range<Anchor>],
19655 color_fetcher: fn(&Theme) -> Hsla,
19656 cx: &mut Context<Self>,
19657 ) {
19658 self.background_highlights.insert(
19659 HighlightKey::Type(TypeId::of::<T>()),
19660 (color_fetcher, Arc::from(ranges)),
19661 );
19662 self.scrollbar_marker_state.dirty = true;
19663 cx.notify();
19664 }
19665
19666 pub fn highlight_background_key<T: 'static>(
19667 &mut self,
19668 key: usize,
19669 ranges: &[Range<Anchor>],
19670 color_fetcher: fn(&Theme) -> Hsla,
19671 cx: &mut Context<Self>,
19672 ) {
19673 self.background_highlights.insert(
19674 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19675 (color_fetcher, Arc::from(ranges)),
19676 );
19677 self.scrollbar_marker_state.dirty = true;
19678 cx.notify();
19679 }
19680
19681 pub fn clear_background_highlights<T: 'static>(
19682 &mut self,
19683 cx: &mut Context<Self>,
19684 ) -> Option<BackgroundHighlight> {
19685 let text_highlights = self
19686 .background_highlights
19687 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19688 if !text_highlights.1.is_empty() {
19689 self.scrollbar_marker_state.dirty = true;
19690 cx.notify();
19691 }
19692 Some(text_highlights)
19693 }
19694
19695 pub fn highlight_gutter<T: 'static>(
19696 &mut self,
19697 ranges: impl Into<Vec<Range<Anchor>>>,
19698 color_fetcher: fn(&App) -> Hsla,
19699 cx: &mut Context<Self>,
19700 ) {
19701 self.gutter_highlights
19702 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19703 cx.notify();
19704 }
19705
19706 pub fn clear_gutter_highlights<T: 'static>(
19707 &mut self,
19708 cx: &mut Context<Self>,
19709 ) -> Option<GutterHighlight> {
19710 cx.notify();
19711 self.gutter_highlights.remove(&TypeId::of::<T>())
19712 }
19713
19714 pub fn insert_gutter_highlight<T: 'static>(
19715 &mut self,
19716 range: Range<Anchor>,
19717 color_fetcher: fn(&App) -> Hsla,
19718 cx: &mut Context<Self>,
19719 ) {
19720 let snapshot = self.buffer().read(cx).snapshot(cx);
19721 let mut highlights = self
19722 .gutter_highlights
19723 .remove(&TypeId::of::<T>())
19724 .map(|(_, highlights)| highlights)
19725 .unwrap_or_default();
19726 let ix = highlights.binary_search_by(|highlight| {
19727 Ordering::Equal
19728 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19729 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19730 });
19731 if let Err(ix) = ix {
19732 highlights.insert(ix, range);
19733 }
19734 self.gutter_highlights
19735 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19736 }
19737
19738 pub fn remove_gutter_highlights<T: 'static>(
19739 &mut self,
19740 ranges_to_remove: Vec<Range<Anchor>>,
19741 cx: &mut Context<Self>,
19742 ) {
19743 let snapshot = self.buffer().read(cx).snapshot(cx);
19744 let Some((color_fetcher, mut gutter_highlights)) =
19745 self.gutter_highlights.remove(&TypeId::of::<T>())
19746 else {
19747 return;
19748 };
19749 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19750 gutter_highlights.retain(|highlight| {
19751 while let Some(range_to_remove) = ranges_to_remove.peek() {
19752 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19753 Ordering::Less | Ordering::Equal => {
19754 ranges_to_remove.next();
19755 }
19756 Ordering::Greater => {
19757 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19758 Ordering::Less | Ordering::Equal => {
19759 return false;
19760 }
19761 Ordering::Greater => break,
19762 }
19763 }
19764 }
19765 }
19766
19767 true
19768 });
19769 self.gutter_highlights
19770 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19771 }
19772
19773 #[cfg(feature = "test-support")]
19774 pub fn all_text_highlights(
19775 &self,
19776 window: &mut Window,
19777 cx: &mut Context<Self>,
19778 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19779 let snapshot = self.snapshot(window, cx);
19780 self.display_map.update(cx, |display_map, _| {
19781 display_map
19782 .all_text_highlights()
19783 .map(|highlight| {
19784 let (style, ranges) = highlight.as_ref();
19785 (
19786 *style,
19787 ranges
19788 .iter()
19789 .map(|range| range.clone().to_display_points(&snapshot))
19790 .collect(),
19791 )
19792 })
19793 .collect()
19794 })
19795 }
19796
19797 #[cfg(feature = "test-support")]
19798 pub fn all_text_background_highlights(
19799 &self,
19800 window: &mut Window,
19801 cx: &mut Context<Self>,
19802 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19803 let snapshot = self.snapshot(window, cx);
19804 let buffer = &snapshot.buffer_snapshot;
19805 let start = buffer.anchor_before(0);
19806 let end = buffer.anchor_after(buffer.len());
19807 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
19808 }
19809
19810 #[cfg(any(test, feature = "test-support"))]
19811 pub fn sorted_background_highlights_in_range(
19812 &self,
19813 search_range: Range<Anchor>,
19814 display_snapshot: &DisplaySnapshot,
19815 theme: &Theme,
19816 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19817 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
19818 res.sort_by(|a, b| {
19819 a.0.start
19820 .cmp(&b.0.start)
19821 .then_with(|| a.0.end.cmp(&b.0.end))
19822 .then_with(|| a.1.cmp(&b.1))
19823 });
19824 res
19825 }
19826
19827 #[cfg(feature = "test-support")]
19828 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19829 let snapshot = self.buffer().read(cx).snapshot(cx);
19830
19831 let highlights = self
19832 .background_highlights
19833 .get(&HighlightKey::Type(TypeId::of::<
19834 items::BufferSearchHighlights,
19835 >()));
19836
19837 if let Some((_color, ranges)) = highlights {
19838 ranges
19839 .iter()
19840 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19841 .collect_vec()
19842 } else {
19843 vec![]
19844 }
19845 }
19846
19847 fn document_highlights_for_position<'a>(
19848 &'a self,
19849 position: Anchor,
19850 buffer: &'a MultiBufferSnapshot,
19851 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19852 let read_highlights = self
19853 .background_highlights
19854 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19855 .map(|h| &h.1);
19856 let write_highlights = self
19857 .background_highlights
19858 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19859 .map(|h| &h.1);
19860 let left_position = position.bias_left(buffer);
19861 let right_position = position.bias_right(buffer);
19862 read_highlights
19863 .into_iter()
19864 .chain(write_highlights)
19865 .flat_map(move |ranges| {
19866 let start_ix = match ranges.binary_search_by(|probe| {
19867 let cmp = probe.end.cmp(&left_position, buffer);
19868 if cmp.is_ge() {
19869 Ordering::Greater
19870 } else {
19871 Ordering::Less
19872 }
19873 }) {
19874 Ok(i) | Err(i) => i,
19875 };
19876
19877 ranges[start_ix..]
19878 .iter()
19879 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19880 })
19881 }
19882
19883 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19884 self.background_highlights
19885 .get(&HighlightKey::Type(TypeId::of::<T>()))
19886 .is_some_and(|(_, highlights)| !highlights.is_empty())
19887 }
19888
19889 /// Returns all background highlights for a given range.
19890 ///
19891 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
19892 pub fn background_highlights_in_range(
19893 &self,
19894 search_range: Range<Anchor>,
19895 display_snapshot: &DisplaySnapshot,
19896 theme: &Theme,
19897 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19898 let mut results = Vec::new();
19899 for (color_fetcher, ranges) in self.background_highlights.values() {
19900 let color = color_fetcher(theme);
19901 let start_ix = match ranges.binary_search_by(|probe| {
19902 let cmp = probe
19903 .end
19904 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19905 if cmp.is_gt() {
19906 Ordering::Greater
19907 } else {
19908 Ordering::Less
19909 }
19910 }) {
19911 Ok(i) | Err(i) => i,
19912 };
19913 for range in &ranges[start_ix..] {
19914 if range
19915 .start
19916 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19917 .is_ge()
19918 {
19919 break;
19920 }
19921
19922 let start = range.start.to_display_point(display_snapshot);
19923 let end = range.end.to_display_point(display_snapshot);
19924 results.push((start..end, color))
19925 }
19926 }
19927 results
19928 }
19929
19930 pub fn gutter_highlights_in_range(
19931 &self,
19932 search_range: Range<Anchor>,
19933 display_snapshot: &DisplaySnapshot,
19934 cx: &App,
19935 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19936 let mut results = Vec::new();
19937 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19938 let color = color_fetcher(cx);
19939 let start_ix = match ranges.binary_search_by(|probe| {
19940 let cmp = probe
19941 .end
19942 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19943 if cmp.is_gt() {
19944 Ordering::Greater
19945 } else {
19946 Ordering::Less
19947 }
19948 }) {
19949 Ok(i) | Err(i) => i,
19950 };
19951 for range in &ranges[start_ix..] {
19952 if range
19953 .start
19954 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19955 .is_ge()
19956 {
19957 break;
19958 }
19959
19960 let start = range.start.to_display_point(display_snapshot);
19961 let end = range.end.to_display_point(display_snapshot);
19962 results.push((start..end, color))
19963 }
19964 }
19965 results
19966 }
19967
19968 /// Get the text ranges corresponding to the redaction query
19969 pub fn redacted_ranges(
19970 &self,
19971 search_range: Range<Anchor>,
19972 display_snapshot: &DisplaySnapshot,
19973 cx: &App,
19974 ) -> Vec<Range<DisplayPoint>> {
19975 display_snapshot
19976 .buffer_snapshot
19977 .redacted_ranges(search_range, |file| {
19978 if let Some(file) = file {
19979 file.is_private()
19980 && EditorSettings::get(
19981 Some(SettingsLocation {
19982 worktree_id: file.worktree_id(cx),
19983 path: file.path().as_ref(),
19984 }),
19985 cx,
19986 )
19987 .redact_private_values
19988 } else {
19989 false
19990 }
19991 })
19992 .map(|range| {
19993 range.start.to_display_point(display_snapshot)
19994 ..range.end.to_display_point(display_snapshot)
19995 })
19996 .collect()
19997 }
19998
19999 pub fn highlight_text_key<T: 'static>(
20000 &mut self,
20001 key: usize,
20002 ranges: Vec<Range<Anchor>>,
20003 style: HighlightStyle,
20004 cx: &mut Context<Self>,
20005 ) {
20006 self.display_map.update(cx, |map, _| {
20007 map.highlight_text(
20008 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20009 ranges,
20010 style,
20011 );
20012 });
20013 cx.notify();
20014 }
20015
20016 pub fn highlight_text<T: 'static>(
20017 &mut self,
20018 ranges: Vec<Range<Anchor>>,
20019 style: HighlightStyle,
20020 cx: &mut Context<Self>,
20021 ) {
20022 self.display_map.update(cx, |map, _| {
20023 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20024 });
20025 cx.notify();
20026 }
20027
20028 pub(crate) fn highlight_inlays<T: 'static>(
20029 &mut self,
20030 highlights: Vec<InlayHighlight>,
20031 style: HighlightStyle,
20032 cx: &mut Context<Self>,
20033 ) {
20034 self.display_map.update(cx, |map, _| {
20035 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20036 });
20037 cx.notify();
20038 }
20039
20040 pub fn text_highlights<'a, T: 'static>(
20041 &'a self,
20042 cx: &'a App,
20043 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20044 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20045 }
20046
20047 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20048 let cleared = self
20049 .display_map
20050 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20051 if cleared {
20052 cx.notify();
20053 }
20054 }
20055
20056 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20057 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20058 && self.focus_handle.is_focused(window)
20059 }
20060
20061 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20062 self.show_cursor_when_unfocused = is_enabled;
20063 cx.notify();
20064 }
20065
20066 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20067 cx.notify();
20068 }
20069
20070 fn on_debug_session_event(
20071 &mut self,
20072 _session: Entity<Session>,
20073 event: &SessionEvent,
20074 cx: &mut Context<Self>,
20075 ) {
20076 if let SessionEvent::InvalidateInlineValue = event {
20077 self.refresh_inline_values(cx);
20078 }
20079 }
20080
20081 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20082 let Some(project) = self.project.clone() else {
20083 return;
20084 };
20085
20086 if !self.inline_value_cache.enabled {
20087 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20088 self.splice_inlays(&inlays, Vec::new(), cx);
20089 return;
20090 }
20091
20092 let current_execution_position = self
20093 .highlighted_rows
20094 .get(&TypeId::of::<ActiveDebugLine>())
20095 .and_then(|lines| lines.last().map(|line| line.range.end));
20096
20097 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20098 let inline_values = editor
20099 .update(cx, |editor, cx| {
20100 let Some(current_execution_position) = current_execution_position else {
20101 return Some(Task::ready(Ok(Vec::new())));
20102 };
20103
20104 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20105 let snapshot = buffer.snapshot(cx);
20106
20107 let excerpt = snapshot.excerpt_containing(
20108 current_execution_position..current_execution_position,
20109 )?;
20110
20111 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20112 })?;
20113
20114 let range =
20115 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20116
20117 project.inline_values(buffer, range, cx)
20118 })
20119 .ok()
20120 .flatten()?
20121 .await
20122 .context("refreshing debugger inlays")
20123 .log_err()?;
20124
20125 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20126
20127 for (buffer_id, inline_value) in inline_values
20128 .into_iter()
20129 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20130 {
20131 buffer_inline_values
20132 .entry(buffer_id)
20133 .or_default()
20134 .push(inline_value);
20135 }
20136
20137 editor
20138 .update(cx, |editor, cx| {
20139 let snapshot = editor.buffer.read(cx).snapshot(cx);
20140 let mut new_inlays = Vec::default();
20141
20142 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20143 let buffer_id = buffer_snapshot.remote_id();
20144 buffer_inline_values
20145 .get(&buffer_id)
20146 .into_iter()
20147 .flatten()
20148 .for_each(|hint| {
20149 let inlay = Inlay::debugger(
20150 post_inc(&mut editor.next_inlay_id),
20151 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20152 hint.text(),
20153 );
20154 if !inlay.text.chars().contains(&'\n') {
20155 new_inlays.push(inlay);
20156 }
20157 });
20158 }
20159
20160 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20161 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20162
20163 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20164 })
20165 .ok()?;
20166 Some(())
20167 });
20168 }
20169
20170 fn on_buffer_event(
20171 &mut self,
20172 multibuffer: &Entity<MultiBuffer>,
20173 event: &multi_buffer::Event,
20174 window: &mut Window,
20175 cx: &mut Context<Self>,
20176 ) {
20177 match event {
20178 multi_buffer::Event::Edited {
20179 singleton_buffer_edited,
20180 edited_buffer,
20181 } => {
20182 self.scrollbar_marker_state.dirty = true;
20183 self.active_indent_guides_state.dirty = true;
20184 self.refresh_active_diagnostics(cx);
20185 self.refresh_code_actions(window, cx);
20186 self.refresh_selected_text_highlights(true, window, cx);
20187 self.refresh_single_line_folds(window, cx);
20188 refresh_matching_bracket_highlights(self, window, cx);
20189 if self.has_active_edit_prediction() {
20190 self.update_visible_edit_prediction(window, cx);
20191 }
20192 if let Some(project) = self.project.as_ref()
20193 && let Some(edited_buffer) = edited_buffer
20194 {
20195 project.update(cx, |project, cx| {
20196 self.registered_buffers
20197 .entry(edited_buffer.read(cx).remote_id())
20198 .or_insert_with(|| {
20199 project.register_buffer_with_language_servers(edited_buffer, cx)
20200 });
20201 });
20202 }
20203 cx.emit(EditorEvent::BufferEdited);
20204 cx.emit(SearchEvent::MatchesInvalidated);
20205
20206 if let Some(buffer) = edited_buffer {
20207 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20208 }
20209
20210 if *singleton_buffer_edited {
20211 if let Some(buffer) = edited_buffer
20212 && buffer.read(cx).file().is_none()
20213 {
20214 cx.emit(EditorEvent::TitleChanged);
20215 }
20216 if let Some(project) = &self.project {
20217 #[allow(clippy::mutable_key_type)]
20218 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20219 multibuffer
20220 .all_buffers()
20221 .into_iter()
20222 .filter_map(|buffer| {
20223 buffer.update(cx, |buffer, cx| {
20224 let language = buffer.language()?;
20225 let should_discard = project.update(cx, |project, cx| {
20226 project.is_local()
20227 && !project.has_language_servers_for(buffer, cx)
20228 });
20229 should_discard.not().then_some(language.clone())
20230 })
20231 })
20232 .collect::<HashSet<_>>()
20233 });
20234 if !languages_affected.is_empty() {
20235 self.refresh_inlay_hints(
20236 InlayHintRefreshReason::BufferEdited(languages_affected),
20237 cx,
20238 );
20239 }
20240 }
20241 }
20242
20243 let Some(project) = &self.project else { return };
20244 let (telemetry, is_via_ssh) = {
20245 let project = project.read(cx);
20246 let telemetry = project.client().telemetry().clone();
20247 let is_via_ssh = project.is_via_remote_server();
20248 (telemetry, is_via_ssh)
20249 };
20250 refresh_linked_ranges(self, window, cx);
20251 telemetry.log_edit_event("editor", is_via_ssh);
20252 }
20253 multi_buffer::Event::ExcerptsAdded {
20254 buffer,
20255 predecessor,
20256 excerpts,
20257 } => {
20258 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20259 let buffer_id = buffer.read(cx).remote_id();
20260 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20261 && let Some(project) = &self.project
20262 {
20263 update_uncommitted_diff_for_buffer(
20264 cx.entity(),
20265 project,
20266 [buffer.clone()],
20267 self.buffer.clone(),
20268 cx,
20269 )
20270 .detach();
20271 }
20272 self.update_lsp_data(false, Some(buffer_id), window, cx);
20273 cx.emit(EditorEvent::ExcerptsAdded {
20274 buffer: buffer.clone(),
20275 predecessor: *predecessor,
20276 excerpts: excerpts.clone(),
20277 });
20278 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20279 }
20280 multi_buffer::Event::ExcerptsRemoved {
20281 ids,
20282 removed_buffer_ids,
20283 } => {
20284 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20285 let buffer = self.buffer.read(cx);
20286 self.registered_buffers
20287 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20288 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20289 cx.emit(EditorEvent::ExcerptsRemoved {
20290 ids: ids.clone(),
20291 removed_buffer_ids: removed_buffer_ids.clone(),
20292 });
20293 }
20294 multi_buffer::Event::ExcerptsEdited {
20295 excerpt_ids,
20296 buffer_ids,
20297 } => {
20298 self.display_map.update(cx, |map, cx| {
20299 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20300 });
20301 cx.emit(EditorEvent::ExcerptsEdited {
20302 ids: excerpt_ids.clone(),
20303 });
20304 }
20305 multi_buffer::Event::ExcerptsExpanded { ids } => {
20306 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20307 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20308 }
20309 multi_buffer::Event::Reparsed(buffer_id) => {
20310 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20311 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20312
20313 cx.emit(EditorEvent::Reparsed(*buffer_id));
20314 }
20315 multi_buffer::Event::DiffHunksToggled => {
20316 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20317 }
20318 multi_buffer::Event::LanguageChanged(buffer_id) => {
20319 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20320 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20321 cx.emit(EditorEvent::Reparsed(*buffer_id));
20322 cx.notify();
20323 }
20324 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20325 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20326 multi_buffer::Event::FileHandleChanged
20327 | multi_buffer::Event::Reloaded
20328 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20329 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20330 multi_buffer::Event::DiagnosticsUpdated => {
20331 self.update_diagnostics_state(window, cx);
20332 }
20333 _ => {}
20334 };
20335 }
20336
20337 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20338 if !self.diagnostics_enabled() {
20339 return;
20340 }
20341 self.refresh_active_diagnostics(cx);
20342 self.refresh_inline_diagnostics(true, window, cx);
20343 self.scrollbar_marker_state.dirty = true;
20344 cx.notify();
20345 }
20346
20347 pub fn start_temporary_diff_override(&mut self) {
20348 self.load_diff_task.take();
20349 self.temporary_diff_override = true;
20350 }
20351
20352 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20353 self.temporary_diff_override = false;
20354 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20355 self.buffer.update(cx, |buffer, cx| {
20356 buffer.set_all_diff_hunks_collapsed(cx);
20357 });
20358
20359 if let Some(project) = self.project.clone() {
20360 self.load_diff_task = Some(
20361 update_uncommitted_diff_for_buffer(
20362 cx.entity(),
20363 &project,
20364 self.buffer.read(cx).all_buffers(),
20365 self.buffer.clone(),
20366 cx,
20367 )
20368 .shared(),
20369 );
20370 }
20371 }
20372
20373 fn on_display_map_changed(
20374 &mut self,
20375 _: Entity<DisplayMap>,
20376 _: &mut Window,
20377 cx: &mut Context<Self>,
20378 ) {
20379 cx.notify();
20380 }
20381
20382 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20383 if self.diagnostics_enabled() {
20384 let new_severity = EditorSettings::get_global(cx)
20385 .diagnostics_max_severity
20386 .unwrap_or(DiagnosticSeverity::Hint);
20387 self.set_max_diagnostics_severity(new_severity, cx);
20388 }
20389 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20390 self.update_edit_prediction_settings(cx);
20391 self.refresh_edit_prediction(true, false, window, cx);
20392 self.refresh_inline_values(cx);
20393 self.refresh_inlay_hints(
20394 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20395 self.selections.newest_anchor().head(),
20396 &self.buffer.read(cx).snapshot(cx),
20397 cx,
20398 )),
20399 cx,
20400 );
20401
20402 let old_cursor_shape = self.cursor_shape;
20403 let old_show_breadcrumbs = self.show_breadcrumbs;
20404
20405 {
20406 let editor_settings = EditorSettings::get_global(cx);
20407 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20408 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20409 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20410 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20411 }
20412
20413 if old_cursor_shape != self.cursor_shape {
20414 cx.emit(EditorEvent::CursorShapeChanged);
20415 }
20416
20417 if old_show_breadcrumbs != self.show_breadcrumbs {
20418 cx.emit(EditorEvent::BreadcrumbsChanged);
20419 }
20420
20421 let project_settings = ProjectSettings::get_global(cx);
20422 self.serialize_dirty_buffers =
20423 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20424
20425 if self.mode.is_full() {
20426 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20427 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20428 if self.show_inline_diagnostics != show_inline_diagnostics {
20429 self.show_inline_diagnostics = show_inline_diagnostics;
20430 self.refresh_inline_diagnostics(false, window, cx);
20431 }
20432
20433 if self.git_blame_inline_enabled != inline_blame_enabled {
20434 self.toggle_git_blame_inline_internal(false, window, cx);
20435 }
20436
20437 let minimap_settings = EditorSettings::get_global(cx).minimap;
20438 if self.minimap_visibility != MinimapVisibility::Disabled {
20439 if self.minimap_visibility.settings_visibility()
20440 != minimap_settings.minimap_enabled()
20441 {
20442 self.set_minimap_visibility(
20443 MinimapVisibility::for_mode(self.mode(), cx),
20444 window,
20445 cx,
20446 );
20447 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20448 minimap_entity.update(cx, |minimap_editor, cx| {
20449 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20450 })
20451 }
20452 }
20453 }
20454
20455 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20456 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20457 }) {
20458 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20459 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20460 }
20461 self.refresh_colors(false, None, window, cx);
20462 }
20463
20464 cx.notify();
20465 }
20466
20467 pub fn set_searchable(&mut self, searchable: bool) {
20468 self.searchable = searchable;
20469 }
20470
20471 pub fn searchable(&self) -> bool {
20472 self.searchable
20473 }
20474
20475 fn open_proposed_changes_editor(
20476 &mut self,
20477 _: &OpenProposedChangesEditor,
20478 window: &mut Window,
20479 cx: &mut Context<Self>,
20480 ) {
20481 let Some(workspace) = self.workspace() else {
20482 cx.propagate();
20483 return;
20484 };
20485
20486 let selections = self.selections.all::<usize>(cx);
20487 let multi_buffer = self.buffer.read(cx);
20488 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20489 let mut new_selections_by_buffer = HashMap::default();
20490 for selection in selections {
20491 for (buffer, range, _) in
20492 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20493 {
20494 let mut range = range.to_point(buffer);
20495 range.start.column = 0;
20496 range.end.column = buffer.line_len(range.end.row);
20497 new_selections_by_buffer
20498 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20499 .or_insert(Vec::new())
20500 .push(range)
20501 }
20502 }
20503
20504 let proposed_changes_buffers = new_selections_by_buffer
20505 .into_iter()
20506 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20507 .collect::<Vec<_>>();
20508 let proposed_changes_editor = cx.new(|cx| {
20509 ProposedChangesEditor::new(
20510 "Proposed changes",
20511 proposed_changes_buffers,
20512 self.project.clone(),
20513 window,
20514 cx,
20515 )
20516 });
20517
20518 window.defer(cx, move |window, cx| {
20519 workspace.update(cx, |workspace, cx| {
20520 workspace.active_pane().update(cx, |pane, cx| {
20521 pane.add_item(
20522 Box::new(proposed_changes_editor),
20523 true,
20524 true,
20525 None,
20526 window,
20527 cx,
20528 );
20529 });
20530 });
20531 });
20532 }
20533
20534 pub fn open_excerpts_in_split(
20535 &mut self,
20536 _: &OpenExcerptsSplit,
20537 window: &mut Window,
20538 cx: &mut Context<Self>,
20539 ) {
20540 self.open_excerpts_common(None, true, window, cx)
20541 }
20542
20543 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20544 self.open_excerpts_common(None, false, window, cx)
20545 }
20546
20547 fn open_excerpts_common(
20548 &mut self,
20549 jump_data: Option<JumpData>,
20550 split: bool,
20551 window: &mut Window,
20552 cx: &mut Context<Self>,
20553 ) {
20554 let Some(workspace) = self.workspace() else {
20555 cx.propagate();
20556 return;
20557 };
20558
20559 if self.buffer.read(cx).is_singleton() {
20560 cx.propagate();
20561 return;
20562 }
20563
20564 let mut new_selections_by_buffer = HashMap::default();
20565 match &jump_data {
20566 Some(JumpData::MultiBufferPoint {
20567 excerpt_id,
20568 position,
20569 anchor,
20570 line_offset_from_top,
20571 }) => {
20572 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20573 if let Some(buffer) = multi_buffer_snapshot
20574 .buffer_id_for_excerpt(*excerpt_id)
20575 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20576 {
20577 let buffer_snapshot = buffer.read(cx).snapshot();
20578 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20579 language::ToPoint::to_point(anchor, &buffer_snapshot)
20580 } else {
20581 buffer_snapshot.clip_point(*position, Bias::Left)
20582 };
20583 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20584 new_selections_by_buffer.insert(
20585 buffer,
20586 (
20587 vec![jump_to_offset..jump_to_offset],
20588 Some(*line_offset_from_top),
20589 ),
20590 );
20591 }
20592 }
20593 Some(JumpData::MultiBufferRow {
20594 row,
20595 line_offset_from_top,
20596 }) => {
20597 let point = MultiBufferPoint::new(row.0, 0);
20598 if let Some((buffer, buffer_point, _)) =
20599 self.buffer.read(cx).point_to_buffer_point(point, cx)
20600 {
20601 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20602 new_selections_by_buffer
20603 .entry(buffer)
20604 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20605 .0
20606 .push(buffer_offset..buffer_offset)
20607 }
20608 }
20609 None => {
20610 let selections = self.selections.all::<usize>(cx);
20611 let multi_buffer = self.buffer.read(cx);
20612 for selection in selections {
20613 for (snapshot, range, _, anchor) in multi_buffer
20614 .snapshot(cx)
20615 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20616 {
20617 if let Some(anchor) = anchor {
20618 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20619 else {
20620 continue;
20621 };
20622 let offset = text::ToOffset::to_offset(
20623 &anchor.text_anchor,
20624 &buffer_handle.read(cx).snapshot(),
20625 );
20626 let range = offset..offset;
20627 new_selections_by_buffer
20628 .entry(buffer_handle)
20629 .or_insert((Vec::new(), None))
20630 .0
20631 .push(range)
20632 } else {
20633 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20634 else {
20635 continue;
20636 };
20637 new_selections_by_buffer
20638 .entry(buffer_handle)
20639 .or_insert((Vec::new(), None))
20640 .0
20641 .push(range)
20642 }
20643 }
20644 }
20645 }
20646 }
20647
20648 new_selections_by_buffer
20649 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20650
20651 if new_selections_by_buffer.is_empty() {
20652 return;
20653 }
20654
20655 // We defer the pane interaction because we ourselves are a workspace item
20656 // and activating a new item causes the pane to call a method on us reentrantly,
20657 // which panics if we're on the stack.
20658 window.defer(cx, move |window, cx| {
20659 workspace.update(cx, |workspace, cx| {
20660 let pane = if split {
20661 workspace.adjacent_pane(window, cx)
20662 } else {
20663 workspace.active_pane().clone()
20664 };
20665
20666 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20667 let editor = buffer
20668 .read(cx)
20669 .file()
20670 .is_none()
20671 .then(|| {
20672 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20673 // so `workspace.open_project_item` will never find them, always opening a new editor.
20674 // Instead, we try to activate the existing editor in the pane first.
20675 let (editor, pane_item_index) =
20676 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20677 let editor = item.downcast::<Editor>()?;
20678 let singleton_buffer =
20679 editor.read(cx).buffer().read(cx).as_singleton()?;
20680 if singleton_buffer == buffer {
20681 Some((editor, i))
20682 } else {
20683 None
20684 }
20685 })?;
20686 pane.update(cx, |pane, cx| {
20687 pane.activate_item(pane_item_index, true, true, window, cx)
20688 });
20689 Some(editor)
20690 })
20691 .flatten()
20692 .unwrap_or_else(|| {
20693 workspace.open_project_item::<Self>(
20694 pane.clone(),
20695 buffer,
20696 true,
20697 true,
20698 window,
20699 cx,
20700 )
20701 });
20702
20703 editor.update(cx, |editor, cx| {
20704 let autoscroll = match scroll_offset {
20705 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20706 None => Autoscroll::newest(),
20707 };
20708 let nav_history = editor.nav_history.take();
20709 editor.change_selections(
20710 SelectionEffects::scroll(autoscroll),
20711 window,
20712 cx,
20713 |s| {
20714 s.select_ranges(ranges);
20715 },
20716 );
20717 editor.nav_history = nav_history;
20718 });
20719 }
20720 })
20721 });
20722 }
20723
20724 // For now, don't allow opening excerpts in buffers that aren't backed by
20725 // regular project files.
20726 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20727 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20728 }
20729
20730 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20731 let snapshot = self.buffer.read(cx).read(cx);
20732 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20733 Some(
20734 ranges
20735 .iter()
20736 .map(move |range| {
20737 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20738 })
20739 .collect(),
20740 )
20741 }
20742
20743 fn selection_replacement_ranges(
20744 &self,
20745 range: Range<OffsetUtf16>,
20746 cx: &mut App,
20747 ) -> Vec<Range<OffsetUtf16>> {
20748 let selections = self.selections.all::<OffsetUtf16>(cx);
20749 let newest_selection = selections
20750 .iter()
20751 .max_by_key(|selection| selection.id)
20752 .unwrap();
20753 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20754 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20755 let snapshot = self.buffer.read(cx).read(cx);
20756 selections
20757 .into_iter()
20758 .map(|mut selection| {
20759 selection.start.0 =
20760 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20761 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20762 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20763 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20764 })
20765 .collect()
20766 }
20767
20768 fn report_editor_event(
20769 &self,
20770 reported_event: ReportEditorEvent,
20771 file_extension: Option<String>,
20772 cx: &App,
20773 ) {
20774 if cfg!(any(test, feature = "test-support")) {
20775 return;
20776 }
20777
20778 let Some(project) = &self.project else { return };
20779
20780 // If None, we are in a file without an extension
20781 let file = self
20782 .buffer
20783 .read(cx)
20784 .as_singleton()
20785 .and_then(|b| b.read(cx).file());
20786 let file_extension = file_extension.or(file
20787 .as_ref()
20788 .and_then(|file| Path::new(file.file_name(cx)).extension())
20789 .and_then(|e| e.to_str())
20790 .map(|a| a.to_string()));
20791
20792 let vim_mode = vim_enabled(cx);
20793
20794 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20795 let copilot_enabled = edit_predictions_provider
20796 == language::language_settings::EditPredictionProvider::Copilot;
20797 let copilot_enabled_for_language = self
20798 .buffer
20799 .read(cx)
20800 .language_settings(cx)
20801 .show_edit_predictions;
20802
20803 let project = project.read(cx);
20804 let event_type = reported_event.event_type();
20805
20806 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20807 telemetry::event!(
20808 event_type,
20809 type = if auto_saved {"autosave"} else {"manual"},
20810 file_extension,
20811 vim_mode,
20812 copilot_enabled,
20813 copilot_enabled_for_language,
20814 edit_predictions_provider,
20815 is_via_ssh = project.is_via_remote_server(),
20816 );
20817 } else {
20818 telemetry::event!(
20819 event_type,
20820 file_extension,
20821 vim_mode,
20822 copilot_enabled,
20823 copilot_enabled_for_language,
20824 edit_predictions_provider,
20825 is_via_ssh = project.is_via_remote_server(),
20826 );
20827 };
20828 }
20829
20830 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20831 /// with each line being an array of {text, highlight} objects.
20832 fn copy_highlight_json(
20833 &mut self,
20834 _: &CopyHighlightJson,
20835 window: &mut Window,
20836 cx: &mut Context<Self>,
20837 ) {
20838 #[derive(Serialize)]
20839 struct Chunk<'a> {
20840 text: String,
20841 highlight: Option<&'a str>,
20842 }
20843
20844 let snapshot = self.buffer.read(cx).snapshot(cx);
20845 let range = self
20846 .selected_text_range(false, window, cx)
20847 .and_then(|selection| {
20848 if selection.range.is_empty() {
20849 None
20850 } else {
20851 Some(selection.range)
20852 }
20853 })
20854 .unwrap_or_else(|| 0..snapshot.len());
20855
20856 let chunks = snapshot.chunks(range, true);
20857 let mut lines = Vec::new();
20858 let mut line: VecDeque<Chunk> = VecDeque::new();
20859
20860 let Some(style) = self.style.as_ref() else {
20861 return;
20862 };
20863
20864 for chunk in chunks {
20865 let highlight = chunk
20866 .syntax_highlight_id
20867 .and_then(|id| id.name(&style.syntax));
20868 let mut chunk_lines = chunk.text.split('\n').peekable();
20869 while let Some(text) = chunk_lines.next() {
20870 let mut merged_with_last_token = false;
20871 if let Some(last_token) = line.back_mut()
20872 && last_token.highlight == highlight
20873 {
20874 last_token.text.push_str(text);
20875 merged_with_last_token = true;
20876 }
20877
20878 if !merged_with_last_token {
20879 line.push_back(Chunk {
20880 text: text.into(),
20881 highlight,
20882 });
20883 }
20884
20885 if chunk_lines.peek().is_some() {
20886 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20887 line.pop_front();
20888 }
20889 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20890 line.pop_back();
20891 }
20892
20893 lines.push(mem::take(&mut line));
20894 }
20895 }
20896 }
20897
20898 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20899 return;
20900 };
20901 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20902 }
20903
20904 pub fn open_context_menu(
20905 &mut self,
20906 _: &OpenContextMenu,
20907 window: &mut Window,
20908 cx: &mut Context<Self>,
20909 ) {
20910 self.request_autoscroll(Autoscroll::newest(), cx);
20911 let position = self.selections.newest_display(cx).start;
20912 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20913 }
20914
20915 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20916 &self.inlay_hint_cache
20917 }
20918
20919 pub fn replay_insert_event(
20920 &mut self,
20921 text: &str,
20922 relative_utf16_range: Option<Range<isize>>,
20923 window: &mut Window,
20924 cx: &mut Context<Self>,
20925 ) {
20926 if !self.input_enabled {
20927 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20928 return;
20929 }
20930 if let Some(relative_utf16_range) = relative_utf16_range {
20931 let selections = self.selections.all::<OffsetUtf16>(cx);
20932 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20933 let new_ranges = selections.into_iter().map(|range| {
20934 let start = OffsetUtf16(
20935 range
20936 .head()
20937 .0
20938 .saturating_add_signed(relative_utf16_range.start),
20939 );
20940 let end = OffsetUtf16(
20941 range
20942 .head()
20943 .0
20944 .saturating_add_signed(relative_utf16_range.end),
20945 );
20946 start..end
20947 });
20948 s.select_ranges(new_ranges);
20949 });
20950 }
20951
20952 self.handle_input(text, window, cx);
20953 }
20954
20955 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20956 let Some(provider) = self.semantics_provider.as_ref() else {
20957 return false;
20958 };
20959
20960 let mut supports = false;
20961 self.buffer().update(cx, |this, cx| {
20962 this.for_each_buffer(|buffer| {
20963 supports |= provider.supports_inlay_hints(buffer, cx);
20964 });
20965 });
20966
20967 supports
20968 }
20969
20970 pub fn is_focused(&self, window: &Window) -> bool {
20971 self.focus_handle.is_focused(window)
20972 }
20973
20974 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20975 cx.emit(EditorEvent::Focused);
20976
20977 if let Some(descendant) = self
20978 .last_focused_descendant
20979 .take()
20980 .and_then(|descendant| descendant.upgrade())
20981 {
20982 window.focus(&descendant);
20983 } else {
20984 if let Some(blame) = self.blame.as_ref() {
20985 blame.update(cx, GitBlame::focus)
20986 }
20987
20988 self.blink_manager.update(cx, BlinkManager::enable);
20989 self.show_cursor_names(window, cx);
20990 self.buffer.update(cx, |buffer, cx| {
20991 buffer.finalize_last_transaction(cx);
20992 if self.leader_id.is_none() {
20993 buffer.set_active_selections(
20994 &self.selections.disjoint_anchors(),
20995 self.selections.line_mode,
20996 self.cursor_shape,
20997 cx,
20998 );
20999 }
21000 });
21001 }
21002 }
21003
21004 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21005 cx.emit(EditorEvent::FocusedIn)
21006 }
21007
21008 fn handle_focus_out(
21009 &mut self,
21010 event: FocusOutEvent,
21011 _window: &mut Window,
21012 cx: &mut Context<Self>,
21013 ) {
21014 if event.blurred != self.focus_handle {
21015 self.last_focused_descendant = Some(event.blurred);
21016 }
21017 self.selection_drag_state = SelectionDragState::None;
21018 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21019 }
21020
21021 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21022 self.blink_manager.update(cx, BlinkManager::disable);
21023 self.buffer
21024 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21025
21026 if let Some(blame) = self.blame.as_ref() {
21027 blame.update(cx, GitBlame::blur)
21028 }
21029 if !self.hover_state.focused(window, cx) {
21030 hide_hover(self, cx);
21031 }
21032 if !self
21033 .context_menu
21034 .borrow()
21035 .as_ref()
21036 .is_some_and(|context_menu| context_menu.focused(window, cx))
21037 {
21038 self.hide_context_menu(window, cx);
21039 }
21040 self.discard_edit_prediction(false, cx);
21041 cx.emit(EditorEvent::Blurred);
21042 cx.notify();
21043 }
21044
21045 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21046 let mut pending: String = window
21047 .pending_input_keystrokes()
21048 .into_iter()
21049 .flatten()
21050 .filter_map(|keystroke| {
21051 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21052 keystroke.key_char.clone()
21053 } else {
21054 None
21055 }
21056 })
21057 .collect();
21058
21059 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21060 pending = "".to_string();
21061 }
21062
21063 let existing_pending = self
21064 .text_highlights::<PendingInput>(cx)
21065 .map(|(_, ranges)| ranges.to_vec());
21066 if existing_pending.is_none() && pending.is_empty() {
21067 return;
21068 }
21069 let transaction =
21070 self.transact(window, cx, |this, window, cx| {
21071 let selections = this.selections.all::<usize>(cx);
21072 let edits = selections
21073 .iter()
21074 .map(|selection| (selection.end..selection.end, pending.clone()));
21075 this.edit(edits, cx);
21076 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21077 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21078 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21079 }));
21080 });
21081 if let Some(existing_ranges) = existing_pending {
21082 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21083 this.edit(edits, cx);
21084 }
21085 });
21086
21087 let snapshot = self.snapshot(window, cx);
21088 let ranges = self
21089 .selections
21090 .all::<usize>(cx)
21091 .into_iter()
21092 .map(|selection| {
21093 snapshot.buffer_snapshot.anchor_after(selection.end)
21094 ..snapshot
21095 .buffer_snapshot
21096 .anchor_before(selection.end + pending.len())
21097 })
21098 .collect();
21099
21100 if pending.is_empty() {
21101 self.clear_highlights::<PendingInput>(cx);
21102 } else {
21103 self.highlight_text::<PendingInput>(
21104 ranges,
21105 HighlightStyle {
21106 underline: Some(UnderlineStyle {
21107 thickness: px(1.),
21108 color: None,
21109 wavy: false,
21110 }),
21111 ..Default::default()
21112 },
21113 cx,
21114 );
21115 }
21116
21117 self.ime_transaction = self.ime_transaction.or(transaction);
21118 if let Some(transaction) = self.ime_transaction {
21119 self.buffer.update(cx, |buffer, cx| {
21120 buffer.group_until_transaction(transaction, cx);
21121 });
21122 }
21123
21124 if self.text_highlights::<PendingInput>(cx).is_none() {
21125 self.ime_transaction.take();
21126 }
21127 }
21128
21129 pub fn register_action_renderer(
21130 &mut self,
21131 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21132 ) -> Subscription {
21133 let id = self.next_editor_action_id.post_inc();
21134 self.editor_actions
21135 .borrow_mut()
21136 .insert(id, Box::new(listener));
21137
21138 let editor_actions = self.editor_actions.clone();
21139 Subscription::new(move || {
21140 editor_actions.borrow_mut().remove(&id);
21141 })
21142 }
21143
21144 pub fn register_action<A: Action>(
21145 &mut self,
21146 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21147 ) -> Subscription {
21148 let id = self.next_editor_action_id.post_inc();
21149 let listener = Arc::new(listener);
21150 self.editor_actions.borrow_mut().insert(
21151 id,
21152 Box::new(move |_, window, _| {
21153 let listener = listener.clone();
21154 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21155 let action = action.downcast_ref().unwrap();
21156 if phase == DispatchPhase::Bubble {
21157 listener(action, window, cx)
21158 }
21159 })
21160 }),
21161 );
21162
21163 let editor_actions = self.editor_actions.clone();
21164 Subscription::new(move || {
21165 editor_actions.borrow_mut().remove(&id);
21166 })
21167 }
21168
21169 pub fn file_header_size(&self) -> u32 {
21170 FILE_HEADER_HEIGHT
21171 }
21172
21173 pub fn restore(
21174 &mut self,
21175 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21176 window: &mut Window,
21177 cx: &mut Context<Self>,
21178 ) {
21179 let workspace = self.workspace();
21180 let project = self.project();
21181 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21182 let mut tasks = Vec::new();
21183 for (buffer_id, changes) in revert_changes {
21184 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21185 buffer.update(cx, |buffer, cx| {
21186 buffer.edit(
21187 changes
21188 .into_iter()
21189 .map(|(range, text)| (range, text.to_string())),
21190 None,
21191 cx,
21192 );
21193 });
21194
21195 if let Some(project) =
21196 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21197 {
21198 project.update(cx, |project, cx| {
21199 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21200 })
21201 }
21202 }
21203 }
21204 tasks
21205 });
21206 cx.spawn_in(window, async move |_, cx| {
21207 for (buffer, task) in save_tasks {
21208 let result = task.await;
21209 if result.is_err() {
21210 let Some(path) = buffer
21211 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21212 .ok()
21213 else {
21214 continue;
21215 };
21216 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21217 let Some(task) = cx
21218 .update_window_entity(workspace, |workspace, window, cx| {
21219 workspace
21220 .open_path_preview(path, None, false, false, false, window, cx)
21221 })
21222 .ok()
21223 else {
21224 continue;
21225 };
21226 task.await.log_err();
21227 }
21228 }
21229 }
21230 })
21231 .detach();
21232 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21233 selections.refresh()
21234 });
21235 }
21236
21237 pub fn to_pixel_point(
21238 &self,
21239 source: multi_buffer::Anchor,
21240 editor_snapshot: &EditorSnapshot,
21241 window: &mut Window,
21242 ) -> Option<gpui::Point<Pixels>> {
21243 let source_point = source.to_display_point(editor_snapshot);
21244 self.display_to_pixel_point(source_point, editor_snapshot, window)
21245 }
21246
21247 pub fn display_to_pixel_point(
21248 &self,
21249 source: DisplayPoint,
21250 editor_snapshot: &EditorSnapshot,
21251 window: &mut Window,
21252 ) -> Option<gpui::Point<Pixels>> {
21253 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21254 let text_layout_details = self.text_layout_details(window);
21255 let scroll_top = text_layout_details
21256 .scroll_anchor
21257 .scroll_position(editor_snapshot)
21258 .y;
21259
21260 if source.row().as_f32() < scroll_top.floor() {
21261 return None;
21262 }
21263 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21264 let source_y = line_height * (source.row().as_f32() - scroll_top);
21265 Some(gpui::Point::new(source_x, source_y))
21266 }
21267
21268 pub fn has_visible_completions_menu(&self) -> bool {
21269 !self.edit_prediction_preview_is_active()
21270 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21271 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21272 })
21273 }
21274
21275 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21276 if self.mode.is_minimap() {
21277 return;
21278 }
21279 self.addons
21280 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21281 }
21282
21283 pub fn unregister_addon<T: Addon>(&mut self) {
21284 self.addons.remove(&std::any::TypeId::of::<T>());
21285 }
21286
21287 pub fn addon<T: Addon>(&self) -> Option<&T> {
21288 let type_id = std::any::TypeId::of::<T>();
21289 self.addons
21290 .get(&type_id)
21291 .and_then(|item| item.to_any().downcast_ref::<T>())
21292 }
21293
21294 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21295 let type_id = std::any::TypeId::of::<T>();
21296 self.addons
21297 .get_mut(&type_id)
21298 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21299 }
21300
21301 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21302 let text_layout_details = self.text_layout_details(window);
21303 let style = &text_layout_details.editor_style;
21304 let font_id = window.text_system().resolve_font(&style.text.font());
21305 let font_size = style.text.font_size.to_pixels(window.rem_size());
21306 let line_height = style.text.line_height_in_pixels(window.rem_size());
21307 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21308 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21309
21310 CharacterDimensions {
21311 em_width,
21312 em_advance,
21313 line_height,
21314 }
21315 }
21316
21317 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21318 self.load_diff_task.clone()
21319 }
21320
21321 fn read_metadata_from_db(
21322 &mut self,
21323 item_id: u64,
21324 workspace_id: WorkspaceId,
21325 window: &mut Window,
21326 cx: &mut Context<Editor>,
21327 ) {
21328 if self.is_singleton(cx)
21329 && !self.mode.is_minimap()
21330 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21331 {
21332 let buffer_snapshot = OnceCell::new();
21333
21334 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21335 && !folds.is_empty()
21336 {
21337 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21338 self.fold_ranges(
21339 folds
21340 .into_iter()
21341 .map(|(start, end)| {
21342 snapshot.clip_offset(start, Bias::Left)
21343 ..snapshot.clip_offset(end, Bias::Right)
21344 })
21345 .collect(),
21346 false,
21347 window,
21348 cx,
21349 );
21350 }
21351
21352 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21353 && !selections.is_empty()
21354 {
21355 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21356 // skip adding the initial selection to selection history
21357 self.selection_history.mode = SelectionHistoryMode::Skipping;
21358 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21359 s.select_ranges(selections.into_iter().map(|(start, end)| {
21360 snapshot.clip_offset(start, Bias::Left)
21361 ..snapshot.clip_offset(end, Bias::Right)
21362 }));
21363 });
21364 self.selection_history.mode = SelectionHistoryMode::Normal;
21365 };
21366 }
21367
21368 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21369 }
21370
21371 fn update_lsp_data(
21372 &mut self,
21373 ignore_cache: bool,
21374 for_buffer: Option<BufferId>,
21375 window: &mut Window,
21376 cx: &mut Context<'_, Self>,
21377 ) {
21378 self.pull_diagnostics(for_buffer, window, cx);
21379 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21380 }
21381}
21382
21383fn vim_enabled(cx: &App) -> bool {
21384 cx.global::<SettingsStore>()
21385 .raw_user_settings()
21386 .get("vim_mode")
21387 == Some(&serde_json::Value::Bool(true))
21388}
21389
21390fn process_completion_for_edit(
21391 completion: &Completion,
21392 intent: CompletionIntent,
21393 buffer: &Entity<Buffer>,
21394 cursor_position: &text::Anchor,
21395 cx: &mut Context<Editor>,
21396) -> CompletionEdit {
21397 let buffer = buffer.read(cx);
21398 let buffer_snapshot = buffer.snapshot();
21399 let (snippet, new_text) = if completion.is_snippet() {
21400 // Workaround for typescript language server issues so that methods don't expand within
21401 // strings and functions with type expressions. The previous point is used because the query
21402 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21403 let mut snippet_source = completion.new_text.clone();
21404 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21405 previous_point.column = previous_point.column.saturating_sub(1);
21406 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21407 && scope.prefers_label_for_snippet_in_completion()
21408 && let Some(label) = completion.label()
21409 && matches!(
21410 completion.kind(),
21411 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21412 )
21413 {
21414 snippet_source = label;
21415 }
21416 match Snippet::parse(&snippet_source).log_err() {
21417 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21418 None => (None, completion.new_text.clone()),
21419 }
21420 } else {
21421 (None, completion.new_text.clone())
21422 };
21423
21424 let mut range_to_replace = {
21425 let replace_range = &completion.replace_range;
21426 if let CompletionSource::Lsp {
21427 insert_range: Some(insert_range),
21428 ..
21429 } = &completion.source
21430 {
21431 debug_assert_eq!(
21432 insert_range.start, replace_range.start,
21433 "insert_range and replace_range should start at the same position"
21434 );
21435 debug_assert!(
21436 insert_range
21437 .start
21438 .cmp(cursor_position, &buffer_snapshot)
21439 .is_le(),
21440 "insert_range should start before or at cursor position"
21441 );
21442 debug_assert!(
21443 replace_range
21444 .start
21445 .cmp(cursor_position, &buffer_snapshot)
21446 .is_le(),
21447 "replace_range should start before or at cursor position"
21448 );
21449
21450 let should_replace = match intent {
21451 CompletionIntent::CompleteWithInsert => false,
21452 CompletionIntent::CompleteWithReplace => true,
21453 CompletionIntent::Complete | CompletionIntent::Compose => {
21454 let insert_mode =
21455 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21456 .completions
21457 .lsp_insert_mode;
21458 match insert_mode {
21459 LspInsertMode::Insert => false,
21460 LspInsertMode::Replace => true,
21461 LspInsertMode::ReplaceSubsequence => {
21462 let mut text_to_replace = buffer.chars_for_range(
21463 buffer.anchor_before(replace_range.start)
21464 ..buffer.anchor_after(replace_range.end),
21465 );
21466 let mut current_needle = text_to_replace.next();
21467 for haystack_ch in completion.label.text.chars() {
21468 if let Some(needle_ch) = current_needle
21469 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21470 {
21471 current_needle = text_to_replace.next();
21472 }
21473 }
21474 current_needle.is_none()
21475 }
21476 LspInsertMode::ReplaceSuffix => {
21477 if replace_range
21478 .end
21479 .cmp(cursor_position, &buffer_snapshot)
21480 .is_gt()
21481 {
21482 let range_after_cursor = *cursor_position..replace_range.end;
21483 let text_after_cursor = buffer
21484 .text_for_range(
21485 buffer.anchor_before(range_after_cursor.start)
21486 ..buffer.anchor_after(range_after_cursor.end),
21487 )
21488 .collect::<String>()
21489 .to_ascii_lowercase();
21490 completion
21491 .label
21492 .text
21493 .to_ascii_lowercase()
21494 .ends_with(&text_after_cursor)
21495 } else {
21496 true
21497 }
21498 }
21499 }
21500 }
21501 };
21502
21503 if should_replace {
21504 replace_range.clone()
21505 } else {
21506 insert_range.clone()
21507 }
21508 } else {
21509 replace_range.clone()
21510 }
21511 };
21512
21513 if range_to_replace
21514 .end
21515 .cmp(cursor_position, &buffer_snapshot)
21516 .is_lt()
21517 {
21518 range_to_replace.end = *cursor_position;
21519 }
21520
21521 CompletionEdit {
21522 new_text,
21523 replace_range: range_to_replace.to_offset(buffer),
21524 snippet,
21525 }
21526}
21527
21528struct CompletionEdit {
21529 new_text: String,
21530 replace_range: Range<usize>,
21531 snippet: Option<Snippet>,
21532}
21533
21534fn insert_extra_newline_brackets(
21535 buffer: &MultiBufferSnapshot,
21536 range: Range<usize>,
21537 language: &language::LanguageScope,
21538) -> bool {
21539 let leading_whitespace_len = buffer
21540 .reversed_chars_at(range.start)
21541 .take_while(|c| c.is_whitespace() && *c != '\n')
21542 .map(|c| c.len_utf8())
21543 .sum::<usize>();
21544 let trailing_whitespace_len = buffer
21545 .chars_at(range.end)
21546 .take_while(|c| c.is_whitespace() && *c != '\n')
21547 .map(|c| c.len_utf8())
21548 .sum::<usize>();
21549 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21550
21551 language.brackets().any(|(pair, enabled)| {
21552 let pair_start = pair.start.trim_end();
21553 let pair_end = pair.end.trim_start();
21554
21555 enabled
21556 && pair.newline
21557 && buffer.contains_str_at(range.end, pair_end)
21558 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21559 })
21560}
21561
21562fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21563 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21564 [(buffer, range, _)] => (*buffer, range.clone()),
21565 _ => return false,
21566 };
21567 let pair = {
21568 let mut result: Option<BracketMatch> = None;
21569
21570 for pair in buffer
21571 .all_bracket_ranges(range.clone())
21572 .filter(move |pair| {
21573 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21574 })
21575 {
21576 let len = pair.close_range.end - pair.open_range.start;
21577
21578 if let Some(existing) = &result {
21579 let existing_len = existing.close_range.end - existing.open_range.start;
21580 if len > existing_len {
21581 continue;
21582 }
21583 }
21584
21585 result = Some(pair);
21586 }
21587
21588 result
21589 };
21590 let Some(pair) = pair else {
21591 return false;
21592 };
21593 pair.newline_only
21594 && buffer
21595 .chars_for_range(pair.open_range.end..range.start)
21596 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21597 .all(|c| c.is_whitespace() && c != '\n')
21598}
21599
21600fn update_uncommitted_diff_for_buffer(
21601 editor: Entity<Editor>,
21602 project: &Entity<Project>,
21603 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21604 buffer: Entity<MultiBuffer>,
21605 cx: &mut App,
21606) -> Task<()> {
21607 let mut tasks = Vec::new();
21608 project.update(cx, |project, cx| {
21609 for buffer in buffers {
21610 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21611 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21612 }
21613 }
21614 });
21615 cx.spawn(async move |cx| {
21616 let diffs = future::join_all(tasks).await;
21617 if editor
21618 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21619 .unwrap_or(false)
21620 {
21621 return;
21622 }
21623
21624 buffer
21625 .update(cx, |buffer, cx| {
21626 for diff in diffs.into_iter().flatten() {
21627 buffer.add_diff(diff, cx);
21628 }
21629 })
21630 .ok();
21631 })
21632}
21633
21634fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21635 let tab_size = tab_size.get() as usize;
21636 let mut width = offset;
21637
21638 for ch in text.chars() {
21639 width += if ch == '\t' {
21640 tab_size - (width % tab_size)
21641 } else {
21642 1
21643 };
21644 }
21645
21646 width - offset
21647}
21648
21649#[cfg(test)]
21650mod tests {
21651 use super::*;
21652
21653 #[test]
21654 fn test_string_size_with_expanded_tabs() {
21655 let nz = |val| NonZeroU32::new(val).unwrap();
21656 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21657 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21658 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21659 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21660 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21661 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21662 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21663 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21664 }
21665}
21666
21667/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21668struct WordBreakingTokenizer<'a> {
21669 input: &'a str,
21670}
21671
21672impl<'a> WordBreakingTokenizer<'a> {
21673 fn new(input: &'a str) -> Self {
21674 Self { input }
21675 }
21676}
21677
21678fn is_char_ideographic(ch: char) -> bool {
21679 use unicode_script::Script::*;
21680 use unicode_script::UnicodeScript;
21681 matches!(ch.script(), Han | Tangut | Yi)
21682}
21683
21684fn is_grapheme_ideographic(text: &str) -> bool {
21685 text.chars().any(is_char_ideographic)
21686}
21687
21688fn is_grapheme_whitespace(text: &str) -> bool {
21689 text.chars().any(|x| x.is_whitespace())
21690}
21691
21692fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21693 text.chars()
21694 .next()
21695 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21696}
21697
21698#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21699enum WordBreakToken<'a> {
21700 Word { token: &'a str, grapheme_len: usize },
21701 InlineWhitespace { token: &'a str, grapheme_len: usize },
21702 Newline,
21703}
21704
21705impl<'a> Iterator for WordBreakingTokenizer<'a> {
21706 /// Yields a span, the count of graphemes in the token, and whether it was
21707 /// whitespace. Note that it also breaks at word boundaries.
21708 type Item = WordBreakToken<'a>;
21709
21710 fn next(&mut self) -> Option<Self::Item> {
21711 use unicode_segmentation::UnicodeSegmentation;
21712 if self.input.is_empty() {
21713 return None;
21714 }
21715
21716 let mut iter = self.input.graphemes(true).peekable();
21717 let mut offset = 0;
21718 let mut grapheme_len = 0;
21719 if let Some(first_grapheme) = iter.next() {
21720 let is_newline = first_grapheme == "\n";
21721 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21722 offset += first_grapheme.len();
21723 grapheme_len += 1;
21724 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21725 if let Some(grapheme) = iter.peek().copied()
21726 && should_stay_with_preceding_ideograph(grapheme)
21727 {
21728 offset += grapheme.len();
21729 grapheme_len += 1;
21730 }
21731 } else {
21732 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21733 let mut next_word_bound = words.peek().copied();
21734 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21735 next_word_bound = words.next();
21736 }
21737 while let Some(grapheme) = iter.peek().copied() {
21738 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21739 break;
21740 };
21741 if is_grapheme_whitespace(grapheme) != is_whitespace
21742 || (grapheme == "\n") != is_newline
21743 {
21744 break;
21745 };
21746 offset += grapheme.len();
21747 grapheme_len += 1;
21748 iter.next();
21749 }
21750 }
21751 let token = &self.input[..offset];
21752 self.input = &self.input[offset..];
21753 if token == "\n" {
21754 Some(WordBreakToken::Newline)
21755 } else if is_whitespace {
21756 Some(WordBreakToken::InlineWhitespace {
21757 token,
21758 grapheme_len,
21759 })
21760 } else {
21761 Some(WordBreakToken::Word {
21762 token,
21763 grapheme_len,
21764 })
21765 }
21766 } else {
21767 None
21768 }
21769 }
21770}
21771
21772#[test]
21773fn test_word_breaking_tokenizer() {
21774 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21775 ("", &[]),
21776 (" ", &[whitespace(" ", 2)]),
21777 ("Ʒ", &[word("Ʒ", 1)]),
21778 ("Ǽ", &[word("Ǽ", 1)]),
21779 ("⋑", &[word("⋑", 1)]),
21780 ("⋑⋑", &[word("⋑⋑", 2)]),
21781 (
21782 "原理,进而",
21783 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21784 ),
21785 (
21786 "hello world",
21787 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21788 ),
21789 (
21790 "hello, world",
21791 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21792 ),
21793 (
21794 " hello world",
21795 &[
21796 whitespace(" ", 2),
21797 word("hello", 5),
21798 whitespace(" ", 1),
21799 word("world", 5),
21800 ],
21801 ),
21802 (
21803 "这是什么 \n 钢笔",
21804 &[
21805 word("这", 1),
21806 word("是", 1),
21807 word("什", 1),
21808 word("么", 1),
21809 whitespace(" ", 1),
21810 newline(),
21811 whitespace(" ", 1),
21812 word("钢", 1),
21813 word("笔", 1),
21814 ],
21815 ),
21816 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21817 ];
21818
21819 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21820 WordBreakToken::Word {
21821 token,
21822 grapheme_len,
21823 }
21824 }
21825
21826 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21827 WordBreakToken::InlineWhitespace {
21828 token,
21829 grapheme_len,
21830 }
21831 }
21832
21833 fn newline() -> WordBreakToken<'static> {
21834 WordBreakToken::Newline
21835 }
21836
21837 for (input, result) in tests {
21838 assert_eq!(
21839 WordBreakingTokenizer::new(input)
21840 .collect::<Vec<_>>()
21841 .as_slice(),
21842 *result,
21843 );
21844 }
21845}
21846
21847fn wrap_with_prefix(
21848 first_line_prefix: String,
21849 subsequent_lines_prefix: String,
21850 unwrapped_text: String,
21851 wrap_column: usize,
21852 tab_size: NonZeroU32,
21853 preserve_existing_whitespace: bool,
21854) -> String {
21855 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21856 let subsequent_lines_prefix_len =
21857 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21858 let mut wrapped_text = String::new();
21859 let mut current_line = first_line_prefix;
21860 let mut is_first_line = true;
21861
21862 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21863 let mut current_line_len = first_line_prefix_len;
21864 let mut in_whitespace = false;
21865 for token in tokenizer {
21866 let have_preceding_whitespace = in_whitespace;
21867 match token {
21868 WordBreakToken::Word {
21869 token,
21870 grapheme_len,
21871 } => {
21872 in_whitespace = false;
21873 let current_prefix_len = if is_first_line {
21874 first_line_prefix_len
21875 } else {
21876 subsequent_lines_prefix_len
21877 };
21878 if current_line_len + grapheme_len > wrap_column
21879 && current_line_len != current_prefix_len
21880 {
21881 wrapped_text.push_str(current_line.trim_end());
21882 wrapped_text.push('\n');
21883 is_first_line = false;
21884 current_line = subsequent_lines_prefix.clone();
21885 current_line_len = subsequent_lines_prefix_len;
21886 }
21887 current_line.push_str(token);
21888 current_line_len += grapheme_len;
21889 }
21890 WordBreakToken::InlineWhitespace {
21891 mut token,
21892 mut grapheme_len,
21893 } => {
21894 in_whitespace = true;
21895 if have_preceding_whitespace && !preserve_existing_whitespace {
21896 continue;
21897 }
21898 if !preserve_existing_whitespace {
21899 token = " ";
21900 grapheme_len = 1;
21901 }
21902 let current_prefix_len = if is_first_line {
21903 first_line_prefix_len
21904 } else {
21905 subsequent_lines_prefix_len
21906 };
21907 if current_line_len + grapheme_len > wrap_column {
21908 wrapped_text.push_str(current_line.trim_end());
21909 wrapped_text.push('\n');
21910 is_first_line = false;
21911 current_line = subsequent_lines_prefix.clone();
21912 current_line_len = subsequent_lines_prefix_len;
21913 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21914 current_line.push_str(token);
21915 current_line_len += grapheme_len;
21916 }
21917 }
21918 WordBreakToken::Newline => {
21919 in_whitespace = true;
21920 let current_prefix_len = if is_first_line {
21921 first_line_prefix_len
21922 } else {
21923 subsequent_lines_prefix_len
21924 };
21925 if preserve_existing_whitespace {
21926 wrapped_text.push_str(current_line.trim_end());
21927 wrapped_text.push('\n');
21928 is_first_line = false;
21929 current_line = subsequent_lines_prefix.clone();
21930 current_line_len = subsequent_lines_prefix_len;
21931 } else if have_preceding_whitespace {
21932 continue;
21933 } else if current_line_len + 1 > wrap_column
21934 && current_line_len != current_prefix_len
21935 {
21936 wrapped_text.push_str(current_line.trim_end());
21937 wrapped_text.push('\n');
21938 is_first_line = false;
21939 current_line = subsequent_lines_prefix.clone();
21940 current_line_len = subsequent_lines_prefix_len;
21941 } else if current_line_len != current_prefix_len {
21942 current_line.push(' ');
21943 current_line_len += 1;
21944 }
21945 }
21946 }
21947 }
21948
21949 if !current_line.is_empty() {
21950 wrapped_text.push_str(¤t_line);
21951 }
21952 wrapped_text
21953}
21954
21955#[test]
21956fn test_wrap_with_prefix() {
21957 assert_eq!(
21958 wrap_with_prefix(
21959 "# ".to_string(),
21960 "# ".to_string(),
21961 "abcdefg".to_string(),
21962 4,
21963 NonZeroU32::new(4).unwrap(),
21964 false,
21965 ),
21966 "# abcdefg"
21967 );
21968 assert_eq!(
21969 wrap_with_prefix(
21970 "".to_string(),
21971 "".to_string(),
21972 "\thello world".to_string(),
21973 8,
21974 NonZeroU32::new(4).unwrap(),
21975 false,
21976 ),
21977 "hello\nworld"
21978 );
21979 assert_eq!(
21980 wrap_with_prefix(
21981 "// ".to_string(),
21982 "// ".to_string(),
21983 "xx \nyy zz aa bb cc".to_string(),
21984 12,
21985 NonZeroU32::new(4).unwrap(),
21986 false,
21987 ),
21988 "// xx yy zz\n// aa bb cc"
21989 );
21990 assert_eq!(
21991 wrap_with_prefix(
21992 String::new(),
21993 String::new(),
21994 "这是什么 \n 钢笔".to_string(),
21995 3,
21996 NonZeroU32::new(4).unwrap(),
21997 false,
21998 ),
21999 "这是什\n么 钢\n笔"
22000 );
22001}
22002
22003pub trait CollaborationHub {
22004 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22005 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22006 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22007}
22008
22009impl CollaborationHub for Entity<Project> {
22010 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22011 self.read(cx).collaborators()
22012 }
22013
22014 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22015 self.read(cx).user_store().read(cx).participant_indices()
22016 }
22017
22018 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22019 let this = self.read(cx);
22020 let user_ids = this.collaborators().values().map(|c| c.user_id);
22021 this.user_store().read(cx).participant_names(user_ids, cx)
22022 }
22023}
22024
22025pub trait SemanticsProvider {
22026 fn hover(
22027 &self,
22028 buffer: &Entity<Buffer>,
22029 position: text::Anchor,
22030 cx: &mut App,
22031 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22032
22033 fn inline_values(
22034 &self,
22035 buffer_handle: Entity<Buffer>,
22036 range: Range<text::Anchor>,
22037 cx: &mut App,
22038 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22039
22040 fn inlay_hints(
22041 &self,
22042 buffer_handle: Entity<Buffer>,
22043 range: Range<text::Anchor>,
22044 cx: &mut App,
22045 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22046
22047 fn resolve_inlay_hint(
22048 &self,
22049 hint: InlayHint,
22050 buffer_handle: Entity<Buffer>,
22051 server_id: LanguageServerId,
22052 cx: &mut App,
22053 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22054
22055 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22056
22057 fn document_highlights(
22058 &self,
22059 buffer: &Entity<Buffer>,
22060 position: text::Anchor,
22061 cx: &mut App,
22062 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22063
22064 fn definitions(
22065 &self,
22066 buffer: &Entity<Buffer>,
22067 position: text::Anchor,
22068 kind: GotoDefinitionKind,
22069 cx: &mut App,
22070 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22071
22072 fn range_for_rename(
22073 &self,
22074 buffer: &Entity<Buffer>,
22075 position: text::Anchor,
22076 cx: &mut App,
22077 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22078
22079 fn perform_rename(
22080 &self,
22081 buffer: &Entity<Buffer>,
22082 position: text::Anchor,
22083 new_name: String,
22084 cx: &mut App,
22085 ) -> Option<Task<Result<ProjectTransaction>>>;
22086}
22087
22088pub trait CompletionProvider {
22089 fn completions(
22090 &self,
22091 excerpt_id: ExcerptId,
22092 buffer: &Entity<Buffer>,
22093 buffer_position: text::Anchor,
22094 trigger: CompletionContext,
22095 window: &mut Window,
22096 cx: &mut Context<Editor>,
22097 ) -> Task<Result<Vec<CompletionResponse>>>;
22098
22099 fn resolve_completions(
22100 &self,
22101 _buffer: Entity<Buffer>,
22102 _completion_indices: Vec<usize>,
22103 _completions: Rc<RefCell<Box<[Completion]>>>,
22104 _cx: &mut Context<Editor>,
22105 ) -> Task<Result<bool>> {
22106 Task::ready(Ok(false))
22107 }
22108
22109 fn apply_additional_edits_for_completion(
22110 &self,
22111 _buffer: Entity<Buffer>,
22112 _completions: Rc<RefCell<Box<[Completion]>>>,
22113 _completion_index: usize,
22114 _push_to_history: bool,
22115 _cx: &mut Context<Editor>,
22116 ) -> Task<Result<Option<language::Transaction>>> {
22117 Task::ready(Ok(None))
22118 }
22119
22120 fn is_completion_trigger(
22121 &self,
22122 buffer: &Entity<Buffer>,
22123 position: language::Anchor,
22124 text: &str,
22125 trigger_in_words: bool,
22126 menu_is_open: bool,
22127 cx: &mut Context<Editor>,
22128 ) -> bool;
22129
22130 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22131
22132 fn sort_completions(&self) -> bool {
22133 true
22134 }
22135
22136 fn filter_completions(&self) -> bool {
22137 true
22138 }
22139}
22140
22141pub trait CodeActionProvider {
22142 fn id(&self) -> Arc<str>;
22143
22144 fn code_actions(
22145 &self,
22146 buffer: &Entity<Buffer>,
22147 range: Range<text::Anchor>,
22148 window: &mut Window,
22149 cx: &mut App,
22150 ) -> Task<Result<Vec<CodeAction>>>;
22151
22152 fn apply_code_action(
22153 &self,
22154 buffer_handle: Entity<Buffer>,
22155 action: CodeAction,
22156 excerpt_id: ExcerptId,
22157 push_to_history: bool,
22158 window: &mut Window,
22159 cx: &mut App,
22160 ) -> Task<Result<ProjectTransaction>>;
22161}
22162
22163impl CodeActionProvider for Entity<Project> {
22164 fn id(&self) -> Arc<str> {
22165 "project".into()
22166 }
22167
22168 fn code_actions(
22169 &self,
22170 buffer: &Entity<Buffer>,
22171 range: Range<text::Anchor>,
22172 _window: &mut Window,
22173 cx: &mut App,
22174 ) -> Task<Result<Vec<CodeAction>>> {
22175 self.update(cx, |project, cx| {
22176 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22177 let code_actions = project.code_actions(buffer, range, None, cx);
22178 cx.background_spawn(async move {
22179 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22180 Ok(code_lens_actions
22181 .context("code lens fetch")?
22182 .into_iter()
22183 .flatten()
22184 .chain(
22185 code_actions
22186 .context("code action fetch")?
22187 .into_iter()
22188 .flatten(),
22189 )
22190 .collect())
22191 })
22192 })
22193 }
22194
22195 fn apply_code_action(
22196 &self,
22197 buffer_handle: Entity<Buffer>,
22198 action: CodeAction,
22199 _excerpt_id: ExcerptId,
22200 push_to_history: bool,
22201 _window: &mut Window,
22202 cx: &mut App,
22203 ) -> Task<Result<ProjectTransaction>> {
22204 self.update(cx, |project, cx| {
22205 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22206 })
22207 }
22208}
22209
22210fn snippet_completions(
22211 project: &Project,
22212 buffer: &Entity<Buffer>,
22213 buffer_position: text::Anchor,
22214 cx: &mut App,
22215) -> Task<Result<CompletionResponse>> {
22216 let languages = buffer.read(cx).languages_at(buffer_position);
22217 let snippet_store = project.snippets().read(cx);
22218
22219 let scopes: Vec<_> = languages
22220 .iter()
22221 .filter_map(|language| {
22222 let language_name = language.lsp_id();
22223 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22224
22225 if snippets.is_empty() {
22226 None
22227 } else {
22228 Some((language.default_scope(), snippets))
22229 }
22230 })
22231 .collect();
22232
22233 if scopes.is_empty() {
22234 return Task::ready(Ok(CompletionResponse {
22235 completions: vec![],
22236 display_options: CompletionDisplayOptions::default(),
22237 is_incomplete: false,
22238 }));
22239 }
22240
22241 let snapshot = buffer.read(cx).text_snapshot();
22242 let chars: String = snapshot
22243 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22244 .collect();
22245 let executor = cx.background_executor().clone();
22246
22247 cx.background_spawn(async move {
22248 let mut is_incomplete = false;
22249 let mut completions: Vec<Completion> = Vec::new();
22250 for (scope, snippets) in scopes.into_iter() {
22251 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22252 let mut last_word = chars
22253 .chars()
22254 .take_while(|c| classifier.is_word(*c))
22255 .collect::<String>();
22256 last_word = last_word.chars().rev().collect();
22257
22258 if last_word.is_empty() {
22259 return Ok(CompletionResponse {
22260 completions: vec![],
22261 display_options: CompletionDisplayOptions::default(),
22262 is_incomplete: true,
22263 });
22264 }
22265
22266 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22267 let to_lsp = |point: &text::Anchor| {
22268 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22269 point_to_lsp(end)
22270 };
22271 let lsp_end = to_lsp(&buffer_position);
22272
22273 let candidates = snippets
22274 .iter()
22275 .enumerate()
22276 .flat_map(|(ix, snippet)| {
22277 snippet
22278 .prefix
22279 .iter()
22280 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22281 })
22282 .collect::<Vec<StringMatchCandidate>>();
22283
22284 const MAX_RESULTS: usize = 100;
22285 let mut matches = fuzzy::match_strings(
22286 &candidates,
22287 &last_word,
22288 last_word.chars().any(|c| c.is_uppercase()),
22289 true,
22290 MAX_RESULTS,
22291 &Default::default(),
22292 executor.clone(),
22293 )
22294 .await;
22295
22296 if matches.len() >= MAX_RESULTS {
22297 is_incomplete = true;
22298 }
22299
22300 // Remove all candidates where the query's start does not match the start of any word in the candidate
22301 if let Some(query_start) = last_word.chars().next() {
22302 matches.retain(|string_match| {
22303 split_words(&string_match.string).any(|word| {
22304 // Check that the first codepoint of the word as lowercase matches the first
22305 // codepoint of the query as lowercase
22306 word.chars()
22307 .flat_map(|codepoint| codepoint.to_lowercase())
22308 .zip(query_start.to_lowercase())
22309 .all(|(word_cp, query_cp)| word_cp == query_cp)
22310 })
22311 });
22312 }
22313
22314 let matched_strings = matches
22315 .into_iter()
22316 .map(|m| m.string)
22317 .collect::<HashSet<_>>();
22318
22319 completions.extend(snippets.iter().filter_map(|snippet| {
22320 let matching_prefix = snippet
22321 .prefix
22322 .iter()
22323 .find(|prefix| matched_strings.contains(*prefix))?;
22324 let start = as_offset - last_word.len();
22325 let start = snapshot.anchor_before(start);
22326 let range = start..buffer_position;
22327 let lsp_start = to_lsp(&start);
22328 let lsp_range = lsp::Range {
22329 start: lsp_start,
22330 end: lsp_end,
22331 };
22332 Some(Completion {
22333 replace_range: range,
22334 new_text: snippet.body.clone(),
22335 source: CompletionSource::Lsp {
22336 insert_range: None,
22337 server_id: LanguageServerId(usize::MAX),
22338 resolved: true,
22339 lsp_completion: Box::new(lsp::CompletionItem {
22340 label: snippet.prefix.first().unwrap().clone(),
22341 kind: Some(CompletionItemKind::SNIPPET),
22342 label_details: snippet.description.as_ref().map(|description| {
22343 lsp::CompletionItemLabelDetails {
22344 detail: Some(description.clone()),
22345 description: None,
22346 }
22347 }),
22348 insert_text_format: Some(InsertTextFormat::SNIPPET),
22349 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22350 lsp::InsertReplaceEdit {
22351 new_text: snippet.body.clone(),
22352 insert: lsp_range,
22353 replace: lsp_range,
22354 },
22355 )),
22356 filter_text: Some(snippet.body.clone()),
22357 sort_text: Some(char::MAX.to_string()),
22358 ..lsp::CompletionItem::default()
22359 }),
22360 lsp_defaults: None,
22361 },
22362 label: CodeLabel {
22363 text: matching_prefix.clone(),
22364 runs: Vec::new(),
22365 filter_range: 0..matching_prefix.len(),
22366 },
22367 icon_path: None,
22368 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22369 single_line: snippet.name.clone().into(),
22370 plain_text: snippet
22371 .description
22372 .clone()
22373 .map(|description| description.into()),
22374 }),
22375 insert_text_mode: None,
22376 confirm: None,
22377 })
22378 }))
22379 }
22380
22381 Ok(CompletionResponse {
22382 completions,
22383 display_options: CompletionDisplayOptions::default(),
22384 is_incomplete,
22385 })
22386 })
22387}
22388
22389impl CompletionProvider for Entity<Project> {
22390 fn completions(
22391 &self,
22392 _excerpt_id: ExcerptId,
22393 buffer: &Entity<Buffer>,
22394 buffer_position: text::Anchor,
22395 options: CompletionContext,
22396 _window: &mut Window,
22397 cx: &mut Context<Editor>,
22398 ) -> Task<Result<Vec<CompletionResponse>>> {
22399 self.update(cx, |project, cx| {
22400 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22401 let project_completions = project.completions(buffer, buffer_position, options, cx);
22402 cx.background_spawn(async move {
22403 let mut responses = project_completions.await?;
22404 let snippets = snippets.await?;
22405 if !snippets.completions.is_empty() {
22406 responses.push(snippets);
22407 }
22408 Ok(responses)
22409 })
22410 })
22411 }
22412
22413 fn resolve_completions(
22414 &self,
22415 buffer: Entity<Buffer>,
22416 completion_indices: Vec<usize>,
22417 completions: Rc<RefCell<Box<[Completion]>>>,
22418 cx: &mut Context<Editor>,
22419 ) -> Task<Result<bool>> {
22420 self.update(cx, |project, cx| {
22421 project.lsp_store().update(cx, |lsp_store, cx| {
22422 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22423 })
22424 })
22425 }
22426
22427 fn apply_additional_edits_for_completion(
22428 &self,
22429 buffer: Entity<Buffer>,
22430 completions: Rc<RefCell<Box<[Completion]>>>,
22431 completion_index: usize,
22432 push_to_history: bool,
22433 cx: &mut Context<Editor>,
22434 ) -> Task<Result<Option<language::Transaction>>> {
22435 self.update(cx, |project, cx| {
22436 project.lsp_store().update(cx, |lsp_store, cx| {
22437 lsp_store.apply_additional_edits_for_completion(
22438 buffer,
22439 completions,
22440 completion_index,
22441 push_to_history,
22442 cx,
22443 )
22444 })
22445 })
22446 }
22447
22448 fn is_completion_trigger(
22449 &self,
22450 buffer: &Entity<Buffer>,
22451 position: language::Anchor,
22452 text: &str,
22453 trigger_in_words: bool,
22454 menu_is_open: bool,
22455 cx: &mut Context<Editor>,
22456 ) -> bool {
22457 let mut chars = text.chars();
22458 let char = if let Some(char) = chars.next() {
22459 char
22460 } else {
22461 return false;
22462 };
22463 if chars.next().is_some() {
22464 return false;
22465 }
22466
22467 let buffer = buffer.read(cx);
22468 let snapshot = buffer.snapshot();
22469 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22470 return false;
22471 }
22472 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22473 if trigger_in_words && classifier.is_word(char) {
22474 return true;
22475 }
22476
22477 buffer.completion_triggers().contains(text)
22478 }
22479}
22480
22481impl SemanticsProvider for Entity<Project> {
22482 fn hover(
22483 &self,
22484 buffer: &Entity<Buffer>,
22485 position: text::Anchor,
22486 cx: &mut App,
22487 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22488 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22489 }
22490
22491 fn document_highlights(
22492 &self,
22493 buffer: &Entity<Buffer>,
22494 position: text::Anchor,
22495 cx: &mut App,
22496 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22497 Some(self.update(cx, |project, cx| {
22498 project.document_highlights(buffer, position, cx)
22499 }))
22500 }
22501
22502 fn definitions(
22503 &self,
22504 buffer: &Entity<Buffer>,
22505 position: text::Anchor,
22506 kind: GotoDefinitionKind,
22507 cx: &mut App,
22508 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22509 Some(self.update(cx, |project, cx| match kind {
22510 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22511 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22512 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22513 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22514 }))
22515 }
22516
22517 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22518 self.update(cx, |project, cx| {
22519 if project
22520 .active_debug_session(cx)
22521 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22522 {
22523 return true;
22524 }
22525
22526 buffer.update(cx, |buffer, cx| {
22527 project.any_language_server_supports_inlay_hints(buffer, cx)
22528 })
22529 })
22530 }
22531
22532 fn inline_values(
22533 &self,
22534 buffer_handle: Entity<Buffer>,
22535 range: Range<text::Anchor>,
22536 cx: &mut App,
22537 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22538 self.update(cx, |project, cx| {
22539 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22540
22541 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22542 })
22543 }
22544
22545 fn inlay_hints(
22546 &self,
22547 buffer_handle: Entity<Buffer>,
22548 range: Range<text::Anchor>,
22549 cx: &mut App,
22550 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22551 Some(self.update(cx, |project, cx| {
22552 project.inlay_hints(buffer_handle, range, cx)
22553 }))
22554 }
22555
22556 fn resolve_inlay_hint(
22557 &self,
22558 hint: InlayHint,
22559 buffer_handle: Entity<Buffer>,
22560 server_id: LanguageServerId,
22561 cx: &mut App,
22562 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22563 Some(self.update(cx, |project, cx| {
22564 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22565 }))
22566 }
22567
22568 fn range_for_rename(
22569 &self,
22570 buffer: &Entity<Buffer>,
22571 position: text::Anchor,
22572 cx: &mut App,
22573 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22574 Some(self.update(cx, |project, cx| {
22575 let buffer = buffer.clone();
22576 let task = project.prepare_rename(buffer.clone(), position, cx);
22577 cx.spawn(async move |_, cx| {
22578 Ok(match task.await? {
22579 PrepareRenameResponse::Success(range) => Some(range),
22580 PrepareRenameResponse::InvalidPosition => None,
22581 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22582 // Fallback on using TreeSitter info to determine identifier range
22583 buffer.read_with(cx, |buffer, _| {
22584 let snapshot = buffer.snapshot();
22585 let (range, kind) = snapshot.surrounding_word(position, false);
22586 if kind != Some(CharKind::Word) {
22587 return None;
22588 }
22589 Some(
22590 snapshot.anchor_before(range.start)
22591 ..snapshot.anchor_after(range.end),
22592 )
22593 })?
22594 }
22595 })
22596 })
22597 }))
22598 }
22599
22600 fn perform_rename(
22601 &self,
22602 buffer: &Entity<Buffer>,
22603 position: text::Anchor,
22604 new_name: String,
22605 cx: &mut App,
22606 ) -> Option<Task<Result<ProjectTransaction>>> {
22607 Some(self.update(cx, |project, cx| {
22608 project.perform_rename(buffer.clone(), position, new_name, cx)
22609 }))
22610 }
22611}
22612
22613fn inlay_hint_settings(
22614 location: Anchor,
22615 snapshot: &MultiBufferSnapshot,
22616 cx: &mut Context<Editor>,
22617) -> InlayHintSettings {
22618 let file = snapshot.file_at(location);
22619 let language = snapshot.language_at(location).map(|l| l.name());
22620 language_settings(language, file, cx).inlay_hints
22621}
22622
22623fn consume_contiguous_rows(
22624 contiguous_row_selections: &mut Vec<Selection<Point>>,
22625 selection: &Selection<Point>,
22626 display_map: &DisplaySnapshot,
22627 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22628) -> (MultiBufferRow, MultiBufferRow) {
22629 contiguous_row_selections.push(selection.clone());
22630 let start_row = starting_row(selection, display_map);
22631 let mut end_row = ending_row(selection, display_map);
22632
22633 while let Some(next_selection) = selections.peek() {
22634 if next_selection.start.row <= end_row.0 {
22635 end_row = ending_row(next_selection, display_map);
22636 contiguous_row_selections.push(selections.next().unwrap().clone());
22637 } else {
22638 break;
22639 }
22640 }
22641 (start_row, end_row)
22642}
22643
22644fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22645 if selection.start.column > 0 {
22646 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22647 } else {
22648 MultiBufferRow(selection.start.row)
22649 }
22650}
22651
22652fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22653 if next_selection.end.column > 0 || next_selection.is_empty() {
22654 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22655 } else {
22656 MultiBufferRow(next_selection.end.row)
22657 }
22658}
22659
22660impl EditorSnapshot {
22661 pub fn remote_selections_in_range<'a>(
22662 &'a self,
22663 range: &'a Range<Anchor>,
22664 collaboration_hub: &dyn CollaborationHub,
22665 cx: &'a App,
22666 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22667 let participant_names = collaboration_hub.user_names(cx);
22668 let participant_indices = collaboration_hub.user_participant_indices(cx);
22669 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22670 let collaborators_by_replica_id = collaborators_by_peer_id
22671 .values()
22672 .map(|collaborator| (collaborator.replica_id, collaborator))
22673 .collect::<HashMap<_, _>>();
22674 self.buffer_snapshot
22675 .selections_in_range(range, false)
22676 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22677 if replica_id == AGENT_REPLICA_ID {
22678 Some(RemoteSelection {
22679 replica_id,
22680 selection,
22681 cursor_shape,
22682 line_mode,
22683 collaborator_id: CollaboratorId::Agent,
22684 user_name: Some("Agent".into()),
22685 color: cx.theme().players().agent(),
22686 })
22687 } else {
22688 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22689 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22690 let user_name = participant_names.get(&collaborator.user_id).cloned();
22691 Some(RemoteSelection {
22692 replica_id,
22693 selection,
22694 cursor_shape,
22695 line_mode,
22696 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22697 user_name,
22698 color: if let Some(index) = participant_index {
22699 cx.theme().players().color_for_participant(index.0)
22700 } else {
22701 cx.theme().players().absent()
22702 },
22703 })
22704 }
22705 })
22706 }
22707
22708 pub fn hunks_for_ranges(
22709 &self,
22710 ranges: impl IntoIterator<Item = Range<Point>>,
22711 ) -> Vec<MultiBufferDiffHunk> {
22712 let mut hunks = Vec::new();
22713 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22714 HashMap::default();
22715 for query_range in ranges {
22716 let query_rows =
22717 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22718 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22719 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22720 ) {
22721 // Include deleted hunks that are adjacent to the query range, because
22722 // otherwise they would be missed.
22723 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22724 if hunk.status().is_deleted() {
22725 intersects_range |= hunk.row_range.start == query_rows.end;
22726 intersects_range |= hunk.row_range.end == query_rows.start;
22727 }
22728 if intersects_range {
22729 if !processed_buffer_rows
22730 .entry(hunk.buffer_id)
22731 .or_default()
22732 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22733 {
22734 continue;
22735 }
22736 hunks.push(hunk);
22737 }
22738 }
22739 }
22740
22741 hunks
22742 }
22743
22744 fn display_diff_hunks_for_rows<'a>(
22745 &'a self,
22746 display_rows: Range<DisplayRow>,
22747 folded_buffers: &'a HashSet<BufferId>,
22748 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22749 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22750 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22751
22752 self.buffer_snapshot
22753 .diff_hunks_in_range(buffer_start..buffer_end)
22754 .filter_map(|hunk| {
22755 if folded_buffers.contains(&hunk.buffer_id) {
22756 return None;
22757 }
22758
22759 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22760 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22761
22762 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22763 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22764
22765 let display_hunk = if hunk_display_start.column() != 0 {
22766 DisplayDiffHunk::Folded {
22767 display_row: hunk_display_start.row(),
22768 }
22769 } else {
22770 let mut end_row = hunk_display_end.row();
22771 if hunk_display_end.column() > 0 {
22772 end_row.0 += 1;
22773 }
22774 let is_created_file = hunk.is_created_file();
22775 DisplayDiffHunk::Unfolded {
22776 status: hunk.status(),
22777 diff_base_byte_range: hunk.diff_base_byte_range,
22778 display_row_range: hunk_display_start.row()..end_row,
22779 multi_buffer_range: Anchor::range_in_buffer(
22780 hunk.excerpt_id,
22781 hunk.buffer_id,
22782 hunk.buffer_range,
22783 ),
22784 is_created_file,
22785 }
22786 };
22787
22788 Some(display_hunk)
22789 })
22790 }
22791
22792 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22793 self.display_snapshot.buffer_snapshot.language_at(position)
22794 }
22795
22796 pub fn is_focused(&self) -> bool {
22797 self.is_focused
22798 }
22799
22800 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22801 self.placeholder_text.as_ref()
22802 }
22803
22804 pub fn scroll_position(&self) -> gpui::Point<f32> {
22805 self.scroll_anchor.scroll_position(&self.display_snapshot)
22806 }
22807
22808 fn gutter_dimensions(
22809 &self,
22810 font_id: FontId,
22811 font_size: Pixels,
22812 max_line_number_width: Pixels,
22813 cx: &App,
22814 ) -> Option<GutterDimensions> {
22815 if !self.show_gutter {
22816 return None;
22817 }
22818
22819 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22820 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22821
22822 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22823 matches!(
22824 ProjectSettings::get_global(cx).git.git_gutter,
22825 Some(GitGutterSetting::TrackedFiles)
22826 )
22827 });
22828 let gutter_settings = EditorSettings::get_global(cx).gutter;
22829 let show_line_numbers = self
22830 .show_line_numbers
22831 .unwrap_or(gutter_settings.line_numbers);
22832 let line_gutter_width = if show_line_numbers {
22833 // Avoid flicker-like gutter resizes when the line number gains another digit by
22834 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22835 let min_width_for_number_on_gutter =
22836 ch_advance * gutter_settings.min_line_number_digits as f32;
22837 max_line_number_width.max(min_width_for_number_on_gutter)
22838 } else {
22839 0.0.into()
22840 };
22841
22842 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22843 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22844
22845 let git_blame_entries_width =
22846 self.git_blame_gutter_max_author_length
22847 .map(|max_author_length| {
22848 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22849 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22850
22851 /// The number of characters to dedicate to gaps and margins.
22852 const SPACING_WIDTH: usize = 4;
22853
22854 let max_char_count = max_author_length.min(renderer.max_author_length())
22855 + ::git::SHORT_SHA_LENGTH
22856 + MAX_RELATIVE_TIMESTAMP.len()
22857 + SPACING_WIDTH;
22858
22859 ch_advance * max_char_count
22860 });
22861
22862 let is_singleton = self.buffer_snapshot.is_singleton();
22863
22864 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22865 left_padding += if !is_singleton {
22866 ch_width * 4.0
22867 } else if show_runnables || show_breakpoints {
22868 ch_width * 3.0
22869 } else if show_git_gutter && show_line_numbers {
22870 ch_width * 2.0
22871 } else if show_git_gutter || show_line_numbers {
22872 ch_width
22873 } else {
22874 px(0.)
22875 };
22876
22877 let shows_folds = is_singleton && gutter_settings.folds;
22878
22879 let right_padding = if shows_folds && show_line_numbers {
22880 ch_width * 4.0
22881 } else if shows_folds || (!is_singleton && show_line_numbers) {
22882 ch_width * 3.0
22883 } else if show_line_numbers {
22884 ch_width
22885 } else {
22886 px(0.)
22887 };
22888
22889 Some(GutterDimensions {
22890 left_padding,
22891 right_padding,
22892 width: line_gutter_width + left_padding + right_padding,
22893 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22894 git_blame_entries_width,
22895 })
22896 }
22897
22898 pub fn render_crease_toggle(
22899 &self,
22900 buffer_row: MultiBufferRow,
22901 row_contains_cursor: bool,
22902 editor: Entity<Editor>,
22903 window: &mut Window,
22904 cx: &mut App,
22905 ) -> Option<AnyElement> {
22906 let folded = self.is_line_folded(buffer_row);
22907 let mut is_foldable = false;
22908
22909 if let Some(crease) = self
22910 .crease_snapshot
22911 .query_row(buffer_row, &self.buffer_snapshot)
22912 {
22913 is_foldable = true;
22914 match crease {
22915 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22916 if let Some(render_toggle) = render_toggle {
22917 let toggle_callback =
22918 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22919 if folded {
22920 editor.update(cx, |editor, cx| {
22921 editor.fold_at(buffer_row, window, cx)
22922 });
22923 } else {
22924 editor.update(cx, |editor, cx| {
22925 editor.unfold_at(buffer_row, window, cx)
22926 });
22927 }
22928 });
22929 return Some((render_toggle)(
22930 buffer_row,
22931 folded,
22932 toggle_callback,
22933 window,
22934 cx,
22935 ));
22936 }
22937 }
22938 }
22939 }
22940
22941 is_foldable |= self.starts_indent(buffer_row);
22942
22943 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22944 Some(
22945 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22946 .toggle_state(folded)
22947 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22948 if folded {
22949 this.unfold_at(buffer_row, window, cx);
22950 } else {
22951 this.fold_at(buffer_row, window, cx);
22952 }
22953 }))
22954 .into_any_element(),
22955 )
22956 } else {
22957 None
22958 }
22959 }
22960
22961 pub fn render_crease_trailer(
22962 &self,
22963 buffer_row: MultiBufferRow,
22964 window: &mut Window,
22965 cx: &mut App,
22966 ) -> Option<AnyElement> {
22967 let folded = self.is_line_folded(buffer_row);
22968 if let Crease::Inline { render_trailer, .. } = self
22969 .crease_snapshot
22970 .query_row(buffer_row, &self.buffer_snapshot)?
22971 {
22972 let render_trailer = render_trailer.as_ref()?;
22973 Some(render_trailer(buffer_row, folded, window, cx))
22974 } else {
22975 None
22976 }
22977 }
22978}
22979
22980impl Deref for EditorSnapshot {
22981 type Target = DisplaySnapshot;
22982
22983 fn deref(&self) -> &Self::Target {
22984 &self.display_snapshot
22985 }
22986}
22987
22988#[derive(Clone, Debug, PartialEq, Eq)]
22989pub enum EditorEvent {
22990 InputIgnored {
22991 text: Arc<str>,
22992 },
22993 InputHandled {
22994 utf16_range_to_replace: Option<Range<isize>>,
22995 text: Arc<str>,
22996 },
22997 ExcerptsAdded {
22998 buffer: Entity<Buffer>,
22999 predecessor: ExcerptId,
23000 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23001 },
23002 ExcerptsRemoved {
23003 ids: Vec<ExcerptId>,
23004 removed_buffer_ids: Vec<BufferId>,
23005 },
23006 BufferFoldToggled {
23007 ids: Vec<ExcerptId>,
23008 folded: bool,
23009 },
23010 ExcerptsEdited {
23011 ids: Vec<ExcerptId>,
23012 },
23013 ExcerptsExpanded {
23014 ids: Vec<ExcerptId>,
23015 },
23016 BufferEdited,
23017 Edited {
23018 transaction_id: clock::Lamport,
23019 },
23020 Reparsed(BufferId),
23021 Focused,
23022 FocusedIn,
23023 Blurred,
23024 DirtyChanged,
23025 Saved,
23026 TitleChanged,
23027 DiffBaseChanged,
23028 SelectionsChanged {
23029 local: bool,
23030 },
23031 ScrollPositionChanged {
23032 local: bool,
23033 autoscroll: bool,
23034 },
23035 Closed,
23036 TransactionUndone {
23037 transaction_id: clock::Lamport,
23038 },
23039 TransactionBegun {
23040 transaction_id: clock::Lamport,
23041 },
23042 Reloaded,
23043 CursorShapeChanged,
23044 BreadcrumbsChanged,
23045 PushedToNavHistory {
23046 anchor: Anchor,
23047 is_deactivate: bool,
23048 },
23049}
23050
23051impl EventEmitter<EditorEvent> for Editor {}
23052
23053impl Focusable for Editor {
23054 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23055 self.focus_handle.clone()
23056 }
23057}
23058
23059impl Render for Editor {
23060 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23061 let settings = ThemeSettings::get_global(cx);
23062
23063 let mut text_style = match self.mode {
23064 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23065 color: cx.theme().colors().editor_foreground,
23066 font_family: settings.ui_font.family.clone(),
23067 font_features: settings.ui_font.features.clone(),
23068 font_fallbacks: settings.ui_font.fallbacks.clone(),
23069 font_size: rems(0.875).into(),
23070 font_weight: settings.ui_font.weight,
23071 line_height: relative(settings.buffer_line_height.value()),
23072 ..Default::default()
23073 },
23074 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23075 color: cx.theme().colors().editor_foreground,
23076 font_family: settings.buffer_font.family.clone(),
23077 font_features: settings.buffer_font.features.clone(),
23078 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23079 font_size: settings.buffer_font_size(cx).into(),
23080 font_weight: settings.buffer_font.weight,
23081 line_height: relative(settings.buffer_line_height.value()),
23082 ..Default::default()
23083 },
23084 };
23085 if let Some(text_style_refinement) = &self.text_style_refinement {
23086 text_style.refine(text_style_refinement)
23087 }
23088
23089 let background = match self.mode {
23090 EditorMode::SingleLine => cx.theme().system().transparent,
23091 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23092 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23093 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23094 };
23095
23096 EditorElement::new(
23097 &cx.entity(),
23098 EditorStyle {
23099 background,
23100 border: cx.theme().colors().border,
23101 local_player: cx.theme().players().local(),
23102 text: text_style,
23103 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23104 syntax: cx.theme().syntax().clone(),
23105 status: cx.theme().status().clone(),
23106 inlay_hints_style: make_inlay_hints_style(cx),
23107 edit_prediction_styles: make_suggestion_styles(cx),
23108 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23109 show_underlines: self.diagnostics_enabled(),
23110 },
23111 )
23112 }
23113}
23114
23115impl EntityInputHandler for Editor {
23116 fn text_for_range(
23117 &mut self,
23118 range_utf16: Range<usize>,
23119 adjusted_range: &mut Option<Range<usize>>,
23120 _: &mut Window,
23121 cx: &mut Context<Self>,
23122 ) -> Option<String> {
23123 let snapshot = self.buffer.read(cx).read(cx);
23124 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23125 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23126 if (start.0..end.0) != range_utf16 {
23127 adjusted_range.replace(start.0..end.0);
23128 }
23129 Some(snapshot.text_for_range(start..end).collect())
23130 }
23131
23132 fn selected_text_range(
23133 &mut self,
23134 ignore_disabled_input: bool,
23135 _: &mut Window,
23136 cx: &mut Context<Self>,
23137 ) -> Option<UTF16Selection> {
23138 // Prevent the IME menu from appearing when holding down an alphabetic key
23139 // while input is disabled.
23140 if !ignore_disabled_input && !self.input_enabled {
23141 return None;
23142 }
23143
23144 let selection = self.selections.newest::<OffsetUtf16>(cx);
23145 let range = selection.range();
23146
23147 Some(UTF16Selection {
23148 range: range.start.0..range.end.0,
23149 reversed: selection.reversed,
23150 })
23151 }
23152
23153 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23154 let snapshot = self.buffer.read(cx).read(cx);
23155 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23156 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23157 }
23158
23159 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23160 self.clear_highlights::<InputComposition>(cx);
23161 self.ime_transaction.take();
23162 }
23163
23164 fn replace_text_in_range(
23165 &mut self,
23166 range_utf16: Option<Range<usize>>,
23167 text: &str,
23168 window: &mut Window,
23169 cx: &mut Context<Self>,
23170 ) {
23171 if !self.input_enabled {
23172 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23173 return;
23174 }
23175
23176 self.transact(window, cx, |this, window, cx| {
23177 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23178 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23179 Some(this.selection_replacement_ranges(range_utf16, cx))
23180 } else {
23181 this.marked_text_ranges(cx)
23182 };
23183
23184 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23185 let newest_selection_id = this.selections.newest_anchor().id;
23186 this.selections
23187 .all::<OffsetUtf16>(cx)
23188 .iter()
23189 .zip(ranges_to_replace.iter())
23190 .find_map(|(selection, range)| {
23191 if selection.id == newest_selection_id {
23192 Some(
23193 (range.start.0 as isize - selection.head().0 as isize)
23194 ..(range.end.0 as isize - selection.head().0 as isize),
23195 )
23196 } else {
23197 None
23198 }
23199 })
23200 });
23201
23202 cx.emit(EditorEvent::InputHandled {
23203 utf16_range_to_replace: range_to_replace,
23204 text: text.into(),
23205 });
23206
23207 if let Some(new_selected_ranges) = new_selected_ranges {
23208 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23209 selections.select_ranges(new_selected_ranges)
23210 });
23211 this.backspace(&Default::default(), window, cx);
23212 }
23213
23214 this.handle_input(text, window, cx);
23215 });
23216
23217 if let Some(transaction) = self.ime_transaction {
23218 self.buffer.update(cx, |buffer, cx| {
23219 buffer.group_until_transaction(transaction, cx);
23220 });
23221 }
23222
23223 self.unmark_text(window, cx);
23224 }
23225
23226 fn replace_and_mark_text_in_range(
23227 &mut self,
23228 range_utf16: Option<Range<usize>>,
23229 text: &str,
23230 new_selected_range_utf16: Option<Range<usize>>,
23231 window: &mut Window,
23232 cx: &mut Context<Self>,
23233 ) {
23234 if !self.input_enabled {
23235 return;
23236 }
23237
23238 let transaction = self.transact(window, cx, |this, window, cx| {
23239 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23240 let snapshot = this.buffer.read(cx).read(cx);
23241 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23242 for marked_range in &mut marked_ranges {
23243 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23244 marked_range.start.0 += relative_range_utf16.start;
23245 marked_range.start =
23246 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23247 marked_range.end =
23248 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23249 }
23250 }
23251 Some(marked_ranges)
23252 } else if let Some(range_utf16) = range_utf16 {
23253 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23254 Some(this.selection_replacement_ranges(range_utf16, cx))
23255 } else {
23256 None
23257 };
23258
23259 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23260 let newest_selection_id = this.selections.newest_anchor().id;
23261 this.selections
23262 .all::<OffsetUtf16>(cx)
23263 .iter()
23264 .zip(ranges_to_replace.iter())
23265 .find_map(|(selection, range)| {
23266 if selection.id == newest_selection_id {
23267 Some(
23268 (range.start.0 as isize - selection.head().0 as isize)
23269 ..(range.end.0 as isize - selection.head().0 as isize),
23270 )
23271 } else {
23272 None
23273 }
23274 })
23275 });
23276
23277 cx.emit(EditorEvent::InputHandled {
23278 utf16_range_to_replace: range_to_replace,
23279 text: text.into(),
23280 });
23281
23282 if let Some(ranges) = ranges_to_replace {
23283 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23284 s.select_ranges(ranges)
23285 });
23286 }
23287
23288 let marked_ranges = {
23289 let snapshot = this.buffer.read(cx).read(cx);
23290 this.selections
23291 .disjoint_anchors()
23292 .iter()
23293 .map(|selection| {
23294 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23295 })
23296 .collect::<Vec<_>>()
23297 };
23298
23299 if text.is_empty() {
23300 this.unmark_text(window, cx);
23301 } else {
23302 this.highlight_text::<InputComposition>(
23303 marked_ranges.clone(),
23304 HighlightStyle {
23305 underline: Some(UnderlineStyle {
23306 thickness: px(1.),
23307 color: None,
23308 wavy: false,
23309 }),
23310 ..Default::default()
23311 },
23312 cx,
23313 );
23314 }
23315
23316 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23317 let use_autoclose = this.use_autoclose;
23318 let use_auto_surround = this.use_auto_surround;
23319 this.set_use_autoclose(false);
23320 this.set_use_auto_surround(false);
23321 this.handle_input(text, window, cx);
23322 this.set_use_autoclose(use_autoclose);
23323 this.set_use_auto_surround(use_auto_surround);
23324
23325 if let Some(new_selected_range) = new_selected_range_utf16 {
23326 let snapshot = this.buffer.read(cx).read(cx);
23327 let new_selected_ranges = marked_ranges
23328 .into_iter()
23329 .map(|marked_range| {
23330 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23331 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23332 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23333 snapshot.clip_offset_utf16(new_start, Bias::Left)
23334 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23335 })
23336 .collect::<Vec<_>>();
23337
23338 drop(snapshot);
23339 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23340 selections.select_ranges(new_selected_ranges)
23341 });
23342 }
23343 });
23344
23345 self.ime_transaction = self.ime_transaction.or(transaction);
23346 if let Some(transaction) = self.ime_transaction {
23347 self.buffer.update(cx, |buffer, cx| {
23348 buffer.group_until_transaction(transaction, cx);
23349 });
23350 }
23351
23352 if self.text_highlights::<InputComposition>(cx).is_none() {
23353 self.ime_transaction.take();
23354 }
23355 }
23356
23357 fn bounds_for_range(
23358 &mut self,
23359 range_utf16: Range<usize>,
23360 element_bounds: gpui::Bounds<Pixels>,
23361 window: &mut Window,
23362 cx: &mut Context<Self>,
23363 ) -> Option<gpui::Bounds<Pixels>> {
23364 let text_layout_details = self.text_layout_details(window);
23365 let CharacterDimensions {
23366 em_width,
23367 em_advance,
23368 line_height,
23369 } = self.character_dimensions(window);
23370
23371 let snapshot = self.snapshot(window, cx);
23372 let scroll_position = snapshot.scroll_position();
23373 let scroll_left = scroll_position.x * em_advance;
23374
23375 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23376 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23377 + self.gutter_dimensions.full_width();
23378 let y = line_height * (start.row().as_f32() - scroll_position.y);
23379
23380 Some(Bounds {
23381 origin: element_bounds.origin + point(x, y),
23382 size: size(em_width, line_height),
23383 })
23384 }
23385
23386 fn character_index_for_point(
23387 &mut self,
23388 point: gpui::Point<Pixels>,
23389 _window: &mut Window,
23390 _cx: &mut Context<Self>,
23391 ) -> Option<usize> {
23392 let position_map = self.last_position_map.as_ref()?;
23393 if !position_map.text_hitbox.contains(&point) {
23394 return None;
23395 }
23396 let display_point = position_map.point_for_position(point).previous_valid;
23397 let anchor = position_map
23398 .snapshot
23399 .display_point_to_anchor(display_point, Bias::Left);
23400 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23401 Some(utf16_offset.0)
23402 }
23403}
23404
23405trait SelectionExt {
23406 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23407 fn spanned_rows(
23408 &self,
23409 include_end_if_at_line_start: bool,
23410 map: &DisplaySnapshot,
23411 ) -> Range<MultiBufferRow>;
23412}
23413
23414impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23415 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23416 let start = self
23417 .start
23418 .to_point(&map.buffer_snapshot)
23419 .to_display_point(map);
23420 let end = self
23421 .end
23422 .to_point(&map.buffer_snapshot)
23423 .to_display_point(map);
23424 if self.reversed {
23425 end..start
23426 } else {
23427 start..end
23428 }
23429 }
23430
23431 fn spanned_rows(
23432 &self,
23433 include_end_if_at_line_start: bool,
23434 map: &DisplaySnapshot,
23435 ) -> Range<MultiBufferRow> {
23436 let start = self.start.to_point(&map.buffer_snapshot);
23437 let mut end = self.end.to_point(&map.buffer_snapshot);
23438 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23439 end.row -= 1;
23440 }
23441
23442 let buffer_start = map.prev_line_boundary(start).0;
23443 let buffer_end = map.next_line_boundary(end).0;
23444 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23445 }
23446}
23447
23448impl<T: InvalidationRegion> InvalidationStack<T> {
23449 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23450 where
23451 S: Clone + ToOffset,
23452 {
23453 while let Some(region) = self.last() {
23454 let all_selections_inside_invalidation_ranges =
23455 if selections.len() == region.ranges().len() {
23456 selections
23457 .iter()
23458 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23459 .all(|(selection, invalidation_range)| {
23460 let head = selection.head().to_offset(buffer);
23461 invalidation_range.start <= head && invalidation_range.end >= head
23462 })
23463 } else {
23464 false
23465 };
23466
23467 if all_selections_inside_invalidation_ranges {
23468 break;
23469 } else {
23470 self.pop();
23471 }
23472 }
23473 }
23474}
23475
23476impl<T> Default for InvalidationStack<T> {
23477 fn default() -> Self {
23478 Self(Default::default())
23479 }
23480}
23481
23482impl<T> Deref for InvalidationStack<T> {
23483 type Target = Vec<T>;
23484
23485 fn deref(&self) -> &Self::Target {
23486 &self.0
23487 }
23488}
23489
23490impl<T> DerefMut for InvalidationStack<T> {
23491 fn deref_mut(&mut self) -> &mut Self::Target {
23492 &mut self.0
23493 }
23494}
23495
23496impl InvalidationRegion for SnippetState {
23497 fn ranges(&self) -> &[Range<Anchor>] {
23498 &self.ranges[self.active_index]
23499 }
23500}
23501
23502fn edit_prediction_edit_text(
23503 current_snapshot: &BufferSnapshot,
23504 edits: &[(Range<Anchor>, String)],
23505 edit_preview: &EditPreview,
23506 include_deletions: bool,
23507 cx: &App,
23508) -> HighlightedText {
23509 let edits = edits
23510 .iter()
23511 .map(|(anchor, text)| {
23512 (
23513 anchor.start.text_anchor..anchor.end.text_anchor,
23514 text.clone(),
23515 )
23516 })
23517 .collect::<Vec<_>>();
23518
23519 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23520}
23521
23522fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23523 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23524 // Just show the raw edit text with basic styling
23525 let mut text = String::new();
23526 let mut highlights = Vec::new();
23527
23528 let insertion_highlight_style = HighlightStyle {
23529 color: Some(cx.theme().colors().text),
23530 ..Default::default()
23531 };
23532
23533 for (_, edit_text) in edits {
23534 let start_offset = text.len();
23535 text.push_str(edit_text);
23536 let end_offset = text.len();
23537
23538 if start_offset < end_offset {
23539 highlights.push((start_offset..end_offset, insertion_highlight_style));
23540 }
23541 }
23542
23543 HighlightedText {
23544 text: text.into(),
23545 highlights,
23546 }
23547}
23548
23549pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23550 match severity {
23551 lsp::DiagnosticSeverity::ERROR => colors.error,
23552 lsp::DiagnosticSeverity::WARNING => colors.warning,
23553 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23554 lsp::DiagnosticSeverity::HINT => colors.info,
23555 _ => colors.ignored,
23556 }
23557}
23558
23559pub fn styled_runs_for_code_label<'a>(
23560 label: &'a CodeLabel,
23561 syntax_theme: &'a theme::SyntaxTheme,
23562) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23563 let fade_out = HighlightStyle {
23564 fade_out: Some(0.35),
23565 ..Default::default()
23566 };
23567
23568 let mut prev_end = label.filter_range.end;
23569 label
23570 .runs
23571 .iter()
23572 .enumerate()
23573 .flat_map(move |(ix, (range, highlight_id))| {
23574 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23575 style
23576 } else {
23577 return Default::default();
23578 };
23579 let mut muted_style = style;
23580 muted_style.highlight(fade_out);
23581
23582 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23583 if range.start >= label.filter_range.end {
23584 if range.start > prev_end {
23585 runs.push((prev_end..range.start, fade_out));
23586 }
23587 runs.push((range.clone(), muted_style));
23588 } else if range.end <= label.filter_range.end {
23589 runs.push((range.clone(), style));
23590 } else {
23591 runs.push((range.start..label.filter_range.end, style));
23592 runs.push((label.filter_range.end..range.end, muted_style));
23593 }
23594 prev_end = cmp::max(prev_end, range.end);
23595
23596 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23597 runs.push((prev_end..label.text.len(), fade_out));
23598 }
23599
23600 runs
23601 })
23602}
23603
23604pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23605 let mut prev_index = 0;
23606 let mut prev_codepoint: Option<char> = None;
23607 text.char_indices()
23608 .chain([(text.len(), '\0')])
23609 .filter_map(move |(index, codepoint)| {
23610 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23611 let is_boundary = index == text.len()
23612 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23613 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23614 if is_boundary {
23615 let chunk = &text[prev_index..index];
23616 prev_index = index;
23617 Some(chunk)
23618 } else {
23619 None
23620 }
23621 })
23622}
23623
23624pub trait RangeToAnchorExt: Sized {
23625 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23626
23627 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23628 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23629 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23630 }
23631}
23632
23633impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23634 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23635 let start_offset = self.start.to_offset(snapshot);
23636 let end_offset = self.end.to_offset(snapshot);
23637 if start_offset == end_offset {
23638 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23639 } else {
23640 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23641 }
23642 }
23643}
23644
23645pub trait RowExt {
23646 fn as_f32(&self) -> f32;
23647
23648 fn next_row(&self) -> Self;
23649
23650 fn previous_row(&self) -> Self;
23651
23652 fn minus(&self, other: Self) -> u32;
23653}
23654
23655impl RowExt for DisplayRow {
23656 fn as_f32(&self) -> f32 {
23657 self.0 as f32
23658 }
23659
23660 fn next_row(&self) -> Self {
23661 Self(self.0 + 1)
23662 }
23663
23664 fn previous_row(&self) -> Self {
23665 Self(self.0.saturating_sub(1))
23666 }
23667
23668 fn minus(&self, other: Self) -> u32 {
23669 self.0 - other.0
23670 }
23671}
23672
23673impl RowExt for MultiBufferRow {
23674 fn as_f32(&self) -> f32 {
23675 self.0 as f32
23676 }
23677
23678 fn next_row(&self) -> Self {
23679 Self(self.0 + 1)
23680 }
23681
23682 fn previous_row(&self) -> Self {
23683 Self(self.0.saturating_sub(1))
23684 }
23685
23686 fn minus(&self, other: Self) -> u32 {
23687 self.0 - other.0
23688 }
23689}
23690
23691trait RowRangeExt {
23692 type Row;
23693
23694 fn len(&self) -> usize;
23695
23696 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23697}
23698
23699impl RowRangeExt for Range<MultiBufferRow> {
23700 type Row = MultiBufferRow;
23701
23702 fn len(&self) -> usize {
23703 (self.end.0 - self.start.0) as usize
23704 }
23705
23706 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23707 (self.start.0..self.end.0).map(MultiBufferRow)
23708 }
23709}
23710
23711impl RowRangeExt for Range<DisplayRow> {
23712 type Row = DisplayRow;
23713
23714 fn len(&self) -> usize {
23715 (self.end.0 - self.start.0) as usize
23716 }
23717
23718 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23719 (self.start.0..self.end.0).map(DisplayRow)
23720 }
23721}
23722
23723/// If select range has more than one line, we
23724/// just point the cursor to range.start.
23725fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23726 if range.start.row == range.end.row {
23727 range
23728 } else {
23729 range.start..range.start
23730 }
23731}
23732pub struct KillRing(ClipboardItem);
23733impl Global for KillRing {}
23734
23735const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23736
23737enum BreakpointPromptEditAction {
23738 Log,
23739 Condition,
23740 HitCondition,
23741}
23742
23743struct BreakpointPromptEditor {
23744 pub(crate) prompt: Entity<Editor>,
23745 editor: WeakEntity<Editor>,
23746 breakpoint_anchor: Anchor,
23747 breakpoint: Breakpoint,
23748 edit_action: BreakpointPromptEditAction,
23749 block_ids: HashSet<CustomBlockId>,
23750 editor_margins: Arc<Mutex<EditorMargins>>,
23751 _subscriptions: Vec<Subscription>,
23752}
23753
23754impl BreakpointPromptEditor {
23755 const MAX_LINES: u8 = 4;
23756
23757 fn new(
23758 editor: WeakEntity<Editor>,
23759 breakpoint_anchor: Anchor,
23760 breakpoint: Breakpoint,
23761 edit_action: BreakpointPromptEditAction,
23762 window: &mut Window,
23763 cx: &mut Context<Self>,
23764 ) -> Self {
23765 let base_text = match edit_action {
23766 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23767 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23768 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23769 }
23770 .map(|msg| msg.to_string())
23771 .unwrap_or_default();
23772
23773 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23774 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23775
23776 let prompt = cx.new(|cx| {
23777 let mut prompt = Editor::new(
23778 EditorMode::AutoHeight {
23779 min_lines: 1,
23780 max_lines: Some(Self::MAX_LINES as usize),
23781 },
23782 buffer,
23783 None,
23784 window,
23785 cx,
23786 );
23787 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23788 prompt.set_show_cursor_when_unfocused(false, cx);
23789 prompt.set_placeholder_text(
23790 match edit_action {
23791 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23792 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23793 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23794 },
23795 cx,
23796 );
23797
23798 prompt
23799 });
23800
23801 Self {
23802 prompt,
23803 editor,
23804 breakpoint_anchor,
23805 breakpoint,
23806 edit_action,
23807 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23808 block_ids: Default::default(),
23809 _subscriptions: vec![],
23810 }
23811 }
23812
23813 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23814 self.block_ids.extend(block_ids)
23815 }
23816
23817 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23818 if let Some(editor) = self.editor.upgrade() {
23819 let message = self
23820 .prompt
23821 .read(cx)
23822 .buffer
23823 .read(cx)
23824 .as_singleton()
23825 .expect("A multi buffer in breakpoint prompt isn't possible")
23826 .read(cx)
23827 .as_rope()
23828 .to_string();
23829
23830 editor.update(cx, |editor, cx| {
23831 editor.edit_breakpoint_at_anchor(
23832 self.breakpoint_anchor,
23833 self.breakpoint.clone(),
23834 match self.edit_action {
23835 BreakpointPromptEditAction::Log => {
23836 BreakpointEditAction::EditLogMessage(message.into())
23837 }
23838 BreakpointPromptEditAction::Condition => {
23839 BreakpointEditAction::EditCondition(message.into())
23840 }
23841 BreakpointPromptEditAction::HitCondition => {
23842 BreakpointEditAction::EditHitCondition(message.into())
23843 }
23844 },
23845 cx,
23846 );
23847
23848 editor.remove_blocks(self.block_ids.clone(), None, cx);
23849 cx.focus_self(window);
23850 });
23851 }
23852 }
23853
23854 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23855 self.editor
23856 .update(cx, |editor, cx| {
23857 editor.remove_blocks(self.block_ids.clone(), None, cx);
23858 window.focus(&editor.focus_handle);
23859 })
23860 .log_err();
23861 }
23862
23863 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23864 let settings = ThemeSettings::get_global(cx);
23865 let text_style = TextStyle {
23866 color: if self.prompt.read(cx).read_only(cx) {
23867 cx.theme().colors().text_disabled
23868 } else {
23869 cx.theme().colors().text
23870 },
23871 font_family: settings.buffer_font.family.clone(),
23872 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23873 font_size: settings.buffer_font_size(cx).into(),
23874 font_weight: settings.buffer_font.weight,
23875 line_height: relative(settings.buffer_line_height.value()),
23876 ..Default::default()
23877 };
23878 EditorElement::new(
23879 &self.prompt,
23880 EditorStyle {
23881 background: cx.theme().colors().editor_background,
23882 local_player: cx.theme().players().local(),
23883 text: text_style,
23884 ..Default::default()
23885 },
23886 )
23887 }
23888}
23889
23890impl Render for BreakpointPromptEditor {
23891 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23892 let editor_margins = *self.editor_margins.lock();
23893 let gutter_dimensions = editor_margins.gutter;
23894 h_flex()
23895 .key_context("Editor")
23896 .bg(cx.theme().colors().editor_background)
23897 .border_y_1()
23898 .border_color(cx.theme().status().info_border)
23899 .size_full()
23900 .py(window.line_height() / 2.5)
23901 .on_action(cx.listener(Self::confirm))
23902 .on_action(cx.listener(Self::cancel))
23903 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23904 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23905 }
23906}
23907
23908impl Focusable for BreakpointPromptEditor {
23909 fn focus_handle(&self, cx: &App) -> FocusHandle {
23910 self.prompt.focus_handle(cx)
23911 }
23912}
23913
23914fn all_edits_insertions_or_deletions(
23915 edits: &Vec<(Range<Anchor>, String)>,
23916 snapshot: &MultiBufferSnapshot,
23917) -> bool {
23918 let mut all_insertions = true;
23919 let mut all_deletions = true;
23920
23921 for (range, new_text) in edits.iter() {
23922 let range_is_empty = range.to_offset(snapshot).is_empty();
23923 let text_is_empty = new_text.is_empty();
23924
23925 if range_is_empty != text_is_empty {
23926 if range_is_empty {
23927 all_deletions = false;
23928 } else {
23929 all_insertions = false;
23930 }
23931 } else {
23932 return false;
23933 }
23934
23935 if !all_insertions && !all_deletions {
23936 return false;
23937 }
23938 }
23939 all_insertions || all_deletions
23940}
23941
23942struct MissingEditPredictionKeybindingTooltip;
23943
23944impl Render for MissingEditPredictionKeybindingTooltip {
23945 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23946 ui::tooltip_container(window, cx, |container, _, cx| {
23947 container
23948 .flex_shrink_0()
23949 .max_w_80()
23950 .min_h(rems_from_px(124.))
23951 .justify_between()
23952 .child(
23953 v_flex()
23954 .flex_1()
23955 .text_ui_sm(cx)
23956 .child(Label::new("Conflict with Accept Keybinding"))
23957 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23958 )
23959 .child(
23960 h_flex()
23961 .pb_1()
23962 .gap_1()
23963 .items_end()
23964 .w_full()
23965 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23966 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23967 }))
23968 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23969 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23970 })),
23971 )
23972 })
23973 }
23974}
23975
23976#[derive(Debug, Clone, Copy, PartialEq)]
23977pub struct LineHighlight {
23978 pub background: Background,
23979 pub border: Option<gpui::Hsla>,
23980 pub include_gutter: bool,
23981 pub type_id: Option<TypeId>,
23982}
23983
23984struct LineManipulationResult {
23985 pub new_text: String,
23986 pub line_count_before: usize,
23987 pub line_count_after: usize,
23988}
23989
23990fn render_diff_hunk_controls(
23991 row: u32,
23992 status: &DiffHunkStatus,
23993 hunk_range: Range<Anchor>,
23994 is_created_file: bool,
23995 line_height: Pixels,
23996 editor: &Entity<Editor>,
23997 _window: &mut Window,
23998 cx: &mut App,
23999) -> AnyElement {
24000 h_flex()
24001 .h(line_height)
24002 .mr_1()
24003 .gap_1()
24004 .px_0p5()
24005 .pb_1()
24006 .border_x_1()
24007 .border_b_1()
24008 .border_color(cx.theme().colors().border_variant)
24009 .rounded_b_lg()
24010 .bg(cx.theme().colors().editor_background)
24011 .gap_1()
24012 .block_mouse_except_scroll()
24013 .shadow_md()
24014 .child(if status.has_secondary_hunk() {
24015 Button::new(("stage", row as u64), "Stage")
24016 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24017 .tooltip({
24018 let focus_handle = editor.focus_handle(cx);
24019 move |window, cx| {
24020 Tooltip::for_action_in(
24021 "Stage Hunk",
24022 &::git::ToggleStaged,
24023 &focus_handle,
24024 window,
24025 cx,
24026 )
24027 }
24028 })
24029 .on_click({
24030 let editor = editor.clone();
24031 move |_event, _window, cx| {
24032 editor.update(cx, |editor, cx| {
24033 editor.stage_or_unstage_diff_hunks(
24034 true,
24035 vec![hunk_range.start..hunk_range.start],
24036 cx,
24037 );
24038 });
24039 }
24040 })
24041 } else {
24042 Button::new(("unstage", row as u64), "Unstage")
24043 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24044 .tooltip({
24045 let focus_handle = editor.focus_handle(cx);
24046 move |window, cx| {
24047 Tooltip::for_action_in(
24048 "Unstage Hunk",
24049 &::git::ToggleStaged,
24050 &focus_handle,
24051 window,
24052 cx,
24053 )
24054 }
24055 })
24056 .on_click({
24057 let editor = editor.clone();
24058 move |_event, _window, cx| {
24059 editor.update(cx, |editor, cx| {
24060 editor.stage_or_unstage_diff_hunks(
24061 false,
24062 vec![hunk_range.start..hunk_range.start],
24063 cx,
24064 );
24065 });
24066 }
24067 })
24068 })
24069 .child(
24070 Button::new(("restore", row as u64), "Restore")
24071 .tooltip({
24072 let focus_handle = editor.focus_handle(cx);
24073 move |window, cx| {
24074 Tooltip::for_action_in(
24075 "Restore Hunk",
24076 &::git::Restore,
24077 &focus_handle,
24078 window,
24079 cx,
24080 )
24081 }
24082 })
24083 .on_click({
24084 let editor = editor.clone();
24085 move |_event, window, cx| {
24086 editor.update(cx, |editor, cx| {
24087 let snapshot = editor.snapshot(window, cx);
24088 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24089 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24090 });
24091 }
24092 })
24093 .disabled(is_created_file),
24094 )
24095 .when(
24096 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24097 |el| {
24098 el.child(
24099 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24100 .shape(IconButtonShape::Square)
24101 .icon_size(IconSize::Small)
24102 // .disabled(!has_multiple_hunks)
24103 .tooltip({
24104 let focus_handle = editor.focus_handle(cx);
24105 move |window, cx| {
24106 Tooltip::for_action_in(
24107 "Next Hunk",
24108 &GoToHunk,
24109 &focus_handle,
24110 window,
24111 cx,
24112 )
24113 }
24114 })
24115 .on_click({
24116 let editor = editor.clone();
24117 move |_event, window, cx| {
24118 editor.update(cx, |editor, cx| {
24119 let snapshot = editor.snapshot(window, cx);
24120 let position =
24121 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24122 editor.go_to_hunk_before_or_after_position(
24123 &snapshot,
24124 position,
24125 Direction::Next,
24126 window,
24127 cx,
24128 );
24129 editor.expand_selected_diff_hunks(cx);
24130 });
24131 }
24132 }),
24133 )
24134 .child(
24135 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24136 .shape(IconButtonShape::Square)
24137 .icon_size(IconSize::Small)
24138 // .disabled(!has_multiple_hunks)
24139 .tooltip({
24140 let focus_handle = editor.focus_handle(cx);
24141 move |window, cx| {
24142 Tooltip::for_action_in(
24143 "Previous Hunk",
24144 &GoToPreviousHunk,
24145 &focus_handle,
24146 window,
24147 cx,
24148 )
24149 }
24150 })
24151 .on_click({
24152 let editor = editor.clone();
24153 move |_event, window, cx| {
24154 editor.update(cx, |editor, cx| {
24155 let snapshot = editor.snapshot(window, cx);
24156 let point =
24157 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24158 editor.go_to_hunk_before_or_after_position(
24159 &snapshot,
24160 point,
24161 Direction::Prev,
24162 window,
24163 cx,
24164 );
24165 editor.expand_selected_diff_hunks(cx);
24166 });
24167 }
24168 }),
24169 )
24170 },
24171 )
24172 .into_any_element()
24173}
24174
24175pub fn multibuffer_context_lines(cx: &App) -> u32 {
24176 EditorSettings::try_get(cx)
24177 .map(|settings| settings.excerpt_context_lines)
24178 .unwrap_or(2)
24179 .clamp(1, 32)
24180}