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;
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 word_completions_enabled: bool,
1034 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1035 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1036 hard_wrap: Option<usize>,
1037 project: Option<Entity<Project>>,
1038 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1039 completion_provider: Option<Rc<dyn CompletionProvider>>,
1040 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1041 blink_manager: Entity<BlinkManager>,
1042 show_cursor_names: bool,
1043 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1044 pub show_local_selections: bool,
1045 mode: EditorMode,
1046 show_breadcrumbs: bool,
1047 show_gutter: bool,
1048 show_scrollbars: ScrollbarAxes,
1049 minimap_visibility: MinimapVisibility,
1050 offset_content: bool,
1051 disable_expand_excerpt_buttons: bool,
1052 show_line_numbers: Option<bool>,
1053 use_relative_line_numbers: Option<bool>,
1054 show_git_diff_gutter: Option<bool>,
1055 show_code_actions: Option<bool>,
1056 show_runnables: Option<bool>,
1057 show_breakpoints: Option<bool>,
1058 show_wrap_guides: Option<bool>,
1059 show_indent_guides: Option<bool>,
1060 placeholder_text: Option<Arc<str>>,
1061 highlight_order: usize,
1062 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1063 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1064 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1065 scrollbar_marker_state: ScrollbarMarkerState,
1066 active_indent_guides_state: ActiveIndentGuidesState,
1067 nav_history: Option<ItemNavHistory>,
1068 context_menu: RefCell<Option<CodeContextMenu>>,
1069 context_menu_options: Option<ContextMenuOptions>,
1070 mouse_context_menu: Option<MouseContextMenu>,
1071 completion_tasks: Vec<(CompletionId, Task<()>)>,
1072 inline_blame_popover: Option<InlineBlamePopover>,
1073 inline_blame_popover_show_task: Option<Task<()>>,
1074 signature_help_state: SignatureHelpState,
1075 auto_signature_help: Option<bool>,
1076 find_all_references_task_sources: Vec<Anchor>,
1077 next_completion_id: CompletionId,
1078 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1079 code_actions_task: Option<Task<Result<()>>>,
1080 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1082 document_highlights_task: Option<Task<()>>,
1083 linked_editing_range_task: Option<Task<Option<()>>>,
1084 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1085 pending_rename: Option<RenameState>,
1086 searchable: bool,
1087 cursor_shape: CursorShape,
1088 current_line_highlight: Option<CurrentLineHighlight>,
1089 collapse_matches: bool,
1090 autoindent_mode: Option<AutoindentMode>,
1091 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1092 input_enabled: bool,
1093 use_modal_editing: bool,
1094 read_only: bool,
1095 leader_id: Option<CollaboratorId>,
1096 remote_id: Option<ViewId>,
1097 pub hover_state: HoverState,
1098 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1099 gutter_hovered: bool,
1100 hovered_link_state: Option<HoveredLinkState>,
1101 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1102 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1103 active_edit_prediction: Option<EditPredictionState>,
1104 /// Used to prevent flickering as the user types while the menu is open
1105 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1106 edit_prediction_settings: EditPredictionSettings,
1107 edit_predictions_hidden_for_vim_mode: bool,
1108 show_edit_predictions_override: Option<bool>,
1109 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1110 edit_prediction_preview: EditPredictionPreview,
1111 edit_prediction_indent_conflict: bool,
1112 edit_prediction_requires_modifier_in_indent_conflict: bool,
1113 inlay_hint_cache: InlayHintCache,
1114 next_inlay_id: usize,
1115 _subscriptions: Vec<Subscription>,
1116 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1117 gutter_dimensions: GutterDimensions,
1118 style: Option<EditorStyle>,
1119 text_style_refinement: Option<TextStyleRefinement>,
1120 next_editor_action_id: EditorActionId,
1121 editor_actions: Rc<
1122 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1123 >,
1124 use_autoclose: bool,
1125 use_auto_surround: bool,
1126 auto_replace_emoji_shortcode: bool,
1127 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1128 show_git_blame_gutter: bool,
1129 show_git_blame_inline: bool,
1130 show_git_blame_inline_delay_task: Option<Task<()>>,
1131 git_blame_inline_enabled: bool,
1132 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1133 serialize_dirty_buffers: bool,
1134 show_selection_menu: Option<bool>,
1135 blame: Option<Entity<GitBlame>>,
1136 blame_subscription: Option<Subscription>,
1137 custom_context_menu: Option<
1138 Box<
1139 dyn 'static
1140 + Fn(
1141 &mut Self,
1142 DisplayPoint,
1143 &mut Window,
1144 &mut Context<Self>,
1145 ) -> Option<Entity<ui::ContextMenu>>,
1146 >,
1147 >,
1148 last_bounds: Option<Bounds<Pixels>>,
1149 last_position_map: Option<Rc<PositionMap>>,
1150 expect_bounds_change: Option<Bounds<Pixels>>,
1151 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1152 tasks_update_task: Option<Task<()>>,
1153 breakpoint_store: Option<Entity<BreakpointStore>>,
1154 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1155 hovered_diff_hunk_row: Option<DisplayRow>,
1156 pull_diagnostics_task: Task<()>,
1157 in_project_search: bool,
1158 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1159 breadcrumb_header: Option<String>,
1160 focused_block: Option<FocusedBlock>,
1161 next_scroll_position: NextScrollCursorCenterTopBottom,
1162 addons: HashMap<TypeId, Box<dyn Addon>>,
1163 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1164 load_diff_task: Option<Shared<Task<()>>>,
1165 /// Whether we are temporarily displaying a diff other than git's
1166 temporary_diff_override: bool,
1167 selection_mark_mode: bool,
1168 toggle_fold_multiple_buffers: Task<()>,
1169 _scroll_cursor_center_top_bottom_task: Task<()>,
1170 serialize_selections: Task<()>,
1171 serialize_folds: Task<()>,
1172 mouse_cursor_hidden: bool,
1173 minimap: Option<Entity<Self>>,
1174 hide_mouse_mode: HideMouseMode,
1175 pub change_list: ChangeList,
1176 inline_value_cache: InlineValueCache,
1177 selection_drag_state: SelectionDragState,
1178 next_color_inlay_id: usize,
1179 colors: Option<LspColorData>,
1180 folding_newlines: Task<()>,
1181}
1182
1183#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1184enum NextScrollCursorCenterTopBottom {
1185 #[default]
1186 Center,
1187 Top,
1188 Bottom,
1189}
1190
1191impl NextScrollCursorCenterTopBottom {
1192 fn next(&self) -> Self {
1193 match self {
1194 Self::Center => Self::Top,
1195 Self::Top => Self::Bottom,
1196 Self::Bottom => Self::Center,
1197 }
1198 }
1199}
1200
1201#[derive(Clone)]
1202pub struct EditorSnapshot {
1203 pub mode: EditorMode,
1204 show_gutter: bool,
1205 show_line_numbers: Option<bool>,
1206 show_git_diff_gutter: Option<bool>,
1207 show_code_actions: Option<bool>,
1208 show_runnables: Option<bool>,
1209 show_breakpoints: Option<bool>,
1210 git_blame_gutter_max_author_length: Option<usize>,
1211 pub display_snapshot: DisplaySnapshot,
1212 pub placeholder_text: Option<Arc<str>>,
1213 is_focused: bool,
1214 scroll_anchor: ScrollAnchor,
1215 ongoing_scroll: OngoingScroll,
1216 current_line_highlight: CurrentLineHighlight,
1217 gutter_hovered: bool,
1218}
1219
1220#[derive(Default, Debug, Clone, Copy)]
1221pub struct GutterDimensions {
1222 pub left_padding: Pixels,
1223 pub right_padding: Pixels,
1224 pub width: Pixels,
1225 pub margin: Pixels,
1226 pub git_blame_entries_width: Option<Pixels>,
1227}
1228
1229impl GutterDimensions {
1230 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1231 Self {
1232 margin: Self::default_gutter_margin(font_id, font_size, cx),
1233 ..Default::default()
1234 }
1235 }
1236
1237 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1238 -cx.text_system().descent(font_id, font_size)
1239 }
1240 /// The full width of the space taken up by the gutter.
1241 pub fn full_width(&self) -> Pixels {
1242 self.margin + self.width
1243 }
1244
1245 /// The width of the space reserved for the fold indicators,
1246 /// use alongside 'justify_end' and `gutter_width` to
1247 /// right align content with the line numbers
1248 pub fn fold_area_width(&self) -> Pixels {
1249 self.margin + self.right_padding
1250 }
1251}
1252
1253struct CharacterDimensions {
1254 em_width: Pixels,
1255 em_advance: Pixels,
1256 line_height: Pixels,
1257}
1258
1259#[derive(Debug)]
1260pub struct RemoteSelection {
1261 pub replica_id: ReplicaId,
1262 pub selection: Selection<Anchor>,
1263 pub cursor_shape: CursorShape,
1264 pub collaborator_id: CollaboratorId,
1265 pub line_mode: bool,
1266 pub user_name: Option<SharedString>,
1267 pub color: PlayerColor,
1268}
1269
1270#[derive(Clone, Debug)]
1271struct SelectionHistoryEntry {
1272 selections: Arc<[Selection<Anchor>]>,
1273 select_next_state: Option<SelectNextState>,
1274 select_prev_state: Option<SelectNextState>,
1275 add_selections_state: Option<AddSelectionsState>,
1276}
1277
1278#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1279enum SelectionHistoryMode {
1280 Normal,
1281 Undoing,
1282 Redoing,
1283 Skipping,
1284}
1285
1286#[derive(Clone, PartialEq, Eq, Hash)]
1287struct HoveredCursor {
1288 replica_id: u16,
1289 selection_id: usize,
1290}
1291
1292impl Default for SelectionHistoryMode {
1293 fn default() -> Self {
1294 Self::Normal
1295 }
1296}
1297
1298#[derive(Debug)]
1299/// SelectionEffects controls the side-effects of updating the selection.
1300///
1301/// The default behaviour does "what you mostly want":
1302/// - it pushes to the nav history if the cursor moved by >10 lines
1303/// - it re-triggers completion requests
1304/// - it scrolls to fit
1305///
1306/// You might want to modify these behaviours. For example when doing a "jump"
1307/// like go to definition, we always want to add to nav history; but when scrolling
1308/// in vim mode we never do.
1309///
1310/// Similarly, you might want to disable scrolling if you don't want the viewport to
1311/// move.
1312#[derive(Clone)]
1313pub struct SelectionEffects {
1314 nav_history: Option<bool>,
1315 completions: bool,
1316 scroll: Option<Autoscroll>,
1317}
1318
1319impl Default for SelectionEffects {
1320 fn default() -> Self {
1321 Self {
1322 nav_history: None,
1323 completions: true,
1324 scroll: Some(Autoscroll::fit()),
1325 }
1326 }
1327}
1328impl SelectionEffects {
1329 pub fn scroll(scroll: Autoscroll) -> Self {
1330 Self {
1331 scroll: Some(scroll),
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn no_scroll() -> Self {
1337 Self {
1338 scroll: None,
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn completions(self, completions: bool) -> Self {
1344 Self {
1345 completions,
1346 ..self
1347 }
1348 }
1349
1350 pub fn nav_history(self, nav_history: bool) -> Self {
1351 Self {
1352 nav_history: Some(nav_history),
1353 ..self
1354 }
1355 }
1356}
1357
1358struct DeferredSelectionEffectsState {
1359 changed: bool,
1360 effects: SelectionEffects,
1361 old_cursor_position: Anchor,
1362 history_entry: SelectionHistoryEntry,
1363}
1364
1365#[derive(Default)]
1366struct SelectionHistory {
1367 #[allow(clippy::type_complexity)]
1368 selections_by_transaction:
1369 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1370 mode: SelectionHistoryMode,
1371 undo_stack: VecDeque<SelectionHistoryEntry>,
1372 redo_stack: VecDeque<SelectionHistoryEntry>,
1373}
1374
1375impl SelectionHistory {
1376 #[track_caller]
1377 fn insert_transaction(
1378 &mut self,
1379 transaction_id: TransactionId,
1380 selections: Arc<[Selection<Anchor>]>,
1381 ) {
1382 if selections.is_empty() {
1383 log::error!(
1384 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1385 std::panic::Location::caller()
1386 );
1387 return;
1388 }
1389 self.selections_by_transaction
1390 .insert(transaction_id, (selections, None));
1391 }
1392
1393 #[allow(clippy::type_complexity)]
1394 fn transaction(
1395 &self,
1396 transaction_id: TransactionId,
1397 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1398 self.selections_by_transaction.get(&transaction_id)
1399 }
1400
1401 #[allow(clippy::type_complexity)]
1402 fn transaction_mut(
1403 &mut self,
1404 transaction_id: TransactionId,
1405 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1406 self.selections_by_transaction.get_mut(&transaction_id)
1407 }
1408
1409 fn push(&mut self, entry: SelectionHistoryEntry) {
1410 if !entry.selections.is_empty() {
1411 match self.mode {
1412 SelectionHistoryMode::Normal => {
1413 self.push_undo(entry);
1414 self.redo_stack.clear();
1415 }
1416 SelectionHistoryMode::Undoing => self.push_redo(entry),
1417 SelectionHistoryMode::Redoing => self.push_undo(entry),
1418 SelectionHistoryMode::Skipping => {}
1419 }
1420 }
1421 }
1422
1423 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1424 if self
1425 .undo_stack
1426 .back()
1427 .is_none_or(|e| e.selections != entry.selections)
1428 {
1429 self.undo_stack.push_back(entry);
1430 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1431 self.undo_stack.pop_front();
1432 }
1433 }
1434 }
1435
1436 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1437 if self
1438 .redo_stack
1439 .back()
1440 .is_none_or(|e| e.selections != entry.selections)
1441 {
1442 self.redo_stack.push_back(entry);
1443 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1444 self.redo_stack.pop_front();
1445 }
1446 }
1447 }
1448}
1449
1450#[derive(Clone, Copy)]
1451pub struct RowHighlightOptions {
1452 pub autoscroll: bool,
1453 pub include_gutter: bool,
1454}
1455
1456impl Default for RowHighlightOptions {
1457 fn default() -> Self {
1458 Self {
1459 autoscroll: Default::default(),
1460 include_gutter: true,
1461 }
1462 }
1463}
1464
1465struct RowHighlight {
1466 index: usize,
1467 range: Range<Anchor>,
1468 color: Hsla,
1469 options: RowHighlightOptions,
1470 type_id: TypeId,
1471}
1472
1473#[derive(Clone, Debug)]
1474struct AddSelectionsState {
1475 groups: Vec<AddSelectionsGroup>,
1476}
1477
1478#[derive(Clone, Debug)]
1479struct AddSelectionsGroup {
1480 above: bool,
1481 stack: Vec<usize>,
1482}
1483
1484#[derive(Clone)]
1485struct SelectNextState {
1486 query: AhoCorasick,
1487 wordwise: bool,
1488 done: bool,
1489}
1490
1491impl std::fmt::Debug for SelectNextState {
1492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1493 f.debug_struct(std::any::type_name::<Self>())
1494 .field("wordwise", &self.wordwise)
1495 .field("done", &self.done)
1496 .finish()
1497 }
1498}
1499
1500#[derive(Debug)]
1501struct AutocloseRegion {
1502 selection_id: usize,
1503 range: Range<Anchor>,
1504 pair: BracketPair,
1505}
1506
1507#[derive(Debug)]
1508struct SnippetState {
1509 ranges: Vec<Vec<Range<Anchor>>>,
1510 active_index: usize,
1511 choices: Vec<Option<Vec<String>>>,
1512}
1513
1514#[doc(hidden)]
1515pub struct RenameState {
1516 pub range: Range<Anchor>,
1517 pub old_name: Arc<str>,
1518 pub editor: Entity<Editor>,
1519 block_id: CustomBlockId,
1520}
1521
1522struct InvalidationStack<T>(Vec<T>);
1523
1524struct RegisteredEditPredictionProvider {
1525 provider: Arc<dyn EditPredictionProviderHandle>,
1526 _subscription: Subscription,
1527}
1528
1529#[derive(Debug, PartialEq, Eq)]
1530pub struct ActiveDiagnosticGroup {
1531 pub active_range: Range<Anchor>,
1532 pub active_message: String,
1533 pub group_id: usize,
1534 pub blocks: HashSet<CustomBlockId>,
1535}
1536
1537#[derive(Debug, PartialEq, Eq)]
1538
1539pub(crate) enum ActiveDiagnostic {
1540 None,
1541 All,
1542 Group(ActiveDiagnosticGroup),
1543}
1544
1545#[derive(Serialize, Deserialize, Clone, Debug)]
1546pub struct ClipboardSelection {
1547 /// The number of bytes in this selection.
1548 pub len: usize,
1549 /// Whether this was a full-line selection.
1550 pub is_entire_line: bool,
1551 /// The indentation of the first line when this content was originally copied.
1552 pub first_line_indent: u32,
1553}
1554
1555// selections, scroll behavior, was newest selection reversed
1556type SelectSyntaxNodeHistoryState = (
1557 Box<[Selection<usize>]>,
1558 SelectSyntaxNodeScrollBehavior,
1559 bool,
1560);
1561
1562#[derive(Default)]
1563struct SelectSyntaxNodeHistory {
1564 stack: Vec<SelectSyntaxNodeHistoryState>,
1565 // disable temporarily to allow changing selections without losing the stack
1566 pub disable_clearing: bool,
1567}
1568
1569impl SelectSyntaxNodeHistory {
1570 pub fn try_clear(&mut self) {
1571 if !self.disable_clearing {
1572 self.stack.clear();
1573 }
1574 }
1575
1576 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1577 self.stack.push(selection);
1578 }
1579
1580 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1581 self.stack.pop()
1582 }
1583}
1584
1585enum SelectSyntaxNodeScrollBehavior {
1586 CursorTop,
1587 FitSelection,
1588 CursorBottom,
1589}
1590
1591#[derive(Debug)]
1592pub(crate) struct NavigationData {
1593 cursor_anchor: Anchor,
1594 cursor_position: Point,
1595 scroll_anchor: ScrollAnchor,
1596 scroll_top_row: u32,
1597}
1598
1599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1600pub enum GotoDefinitionKind {
1601 Symbol,
1602 Declaration,
1603 Type,
1604 Implementation,
1605}
1606
1607#[derive(Debug, Clone)]
1608enum InlayHintRefreshReason {
1609 ModifiersChanged(bool),
1610 Toggle(bool),
1611 SettingsChange(InlayHintSettings),
1612 NewLinesShown,
1613 BufferEdited(HashSet<Arc<Language>>),
1614 RefreshRequested,
1615 ExcerptsRemoved(Vec<ExcerptId>),
1616}
1617
1618impl InlayHintRefreshReason {
1619 fn description(&self) -> &'static str {
1620 match self {
1621 Self::ModifiersChanged(_) => "modifiers changed",
1622 Self::Toggle(_) => "toggle",
1623 Self::SettingsChange(_) => "settings change",
1624 Self::NewLinesShown => "new lines shown",
1625 Self::BufferEdited(_) => "buffer edited",
1626 Self::RefreshRequested => "refresh requested",
1627 Self::ExcerptsRemoved(_) => "excerpts removed",
1628 }
1629 }
1630}
1631
1632pub enum FormatTarget {
1633 Buffers(HashSet<Entity<Buffer>>),
1634 Ranges(Vec<Range<MultiBufferPoint>>),
1635}
1636
1637pub(crate) struct FocusedBlock {
1638 id: BlockId,
1639 focus_handle: WeakFocusHandle,
1640}
1641
1642#[derive(Clone)]
1643enum JumpData {
1644 MultiBufferRow {
1645 row: MultiBufferRow,
1646 line_offset_from_top: u32,
1647 },
1648 MultiBufferPoint {
1649 excerpt_id: ExcerptId,
1650 position: Point,
1651 anchor: text::Anchor,
1652 line_offset_from_top: u32,
1653 },
1654}
1655
1656pub enum MultibufferSelectionMode {
1657 First,
1658 All,
1659}
1660
1661#[derive(Clone, Copy, Debug, Default)]
1662pub struct RewrapOptions {
1663 pub override_language_settings: bool,
1664 pub preserve_existing_whitespace: bool,
1665}
1666
1667impl Editor {
1668 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1669 let buffer = cx.new(|cx| Buffer::local("", cx));
1670 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1671 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1672 }
1673
1674 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(EditorMode::full(), buffer, None, window, cx)
1678 }
1679
1680 pub fn auto_height(
1681 min_lines: usize,
1682 max_lines: usize,
1683 window: &mut Window,
1684 cx: &mut Context<Self>,
1685 ) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(
1689 EditorMode::AutoHeight {
1690 min_lines,
1691 max_lines: Some(max_lines),
1692 },
1693 buffer,
1694 None,
1695 window,
1696 cx,
1697 )
1698 }
1699
1700 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1701 /// The editor grows as tall as needed to fit its content.
1702 pub fn auto_height_unbounded(
1703 min_lines: usize,
1704 window: &mut Window,
1705 cx: &mut Context<Self>,
1706 ) -> Self {
1707 let buffer = cx.new(|cx| Buffer::local("", cx));
1708 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1709 Self::new(
1710 EditorMode::AutoHeight {
1711 min_lines,
1712 max_lines: None,
1713 },
1714 buffer,
1715 None,
1716 window,
1717 cx,
1718 )
1719 }
1720
1721 pub fn for_buffer(
1722 buffer: Entity<Buffer>,
1723 project: Option<Entity<Project>>,
1724 window: &mut Window,
1725 cx: &mut Context<Self>,
1726 ) -> Self {
1727 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1728 Self::new(EditorMode::full(), buffer, project, window, cx)
1729 }
1730
1731 pub fn for_multibuffer(
1732 buffer: Entity<MultiBuffer>,
1733 project: Option<Entity<Project>>,
1734 window: &mut Window,
1735 cx: &mut Context<Self>,
1736 ) -> Self {
1737 Self::new(EditorMode::full(), buffer, project, window, cx)
1738 }
1739
1740 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1741 let mut clone = Self::new(
1742 self.mode.clone(),
1743 self.buffer.clone(),
1744 self.project.clone(),
1745 window,
1746 cx,
1747 );
1748 self.display_map.update(cx, |display_map, cx| {
1749 let snapshot = display_map.snapshot(cx);
1750 clone.display_map.update(cx, |display_map, cx| {
1751 display_map.set_state(&snapshot, cx);
1752 });
1753 });
1754 clone.folds_did_change(cx);
1755 clone.selections.clone_state(&self.selections);
1756 clone.scroll_manager.clone_state(&self.scroll_manager);
1757 clone.searchable = self.searchable;
1758 clone.read_only = self.read_only;
1759 clone
1760 }
1761
1762 pub fn new(
1763 mode: EditorMode,
1764 buffer: Entity<MultiBuffer>,
1765 project: Option<Entity<Project>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 Editor::new_internal(mode, buffer, project, None, window, cx)
1770 }
1771
1772 fn new_internal(
1773 mode: EditorMode,
1774 buffer: Entity<MultiBuffer>,
1775 project: Option<Entity<Project>>,
1776 display_map: Option<Entity<DisplayMap>>,
1777 window: &mut Window,
1778 cx: &mut Context<Self>,
1779 ) -> Self {
1780 debug_assert!(
1781 display_map.is_none() || mode.is_minimap(),
1782 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1783 );
1784
1785 let full_mode = mode.is_full();
1786 let is_minimap = mode.is_minimap();
1787 let diagnostics_max_severity = if full_mode {
1788 EditorSettings::get_global(cx)
1789 .diagnostics_max_severity
1790 .unwrap_or(DiagnosticSeverity::Hint)
1791 } else {
1792 DiagnosticSeverity::Off
1793 };
1794 let style = window.text_style();
1795 let font_size = style.font_size.to_pixels(window.rem_size());
1796 let editor = cx.entity().downgrade();
1797 let fold_placeholder = FoldPlaceholder {
1798 constrain_width: false,
1799 render: Arc::new(move |fold_id, fold_range, cx| {
1800 let editor = editor.clone();
1801 div()
1802 .id(fold_id)
1803 .bg(cx.theme().colors().ghost_element_background)
1804 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1805 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1806 .rounded_xs()
1807 .size_full()
1808 .cursor_pointer()
1809 .child("⋯")
1810 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1811 .on_click(move |_, _window, cx| {
1812 editor
1813 .update(cx, |editor, cx| {
1814 editor.unfold_ranges(
1815 &[fold_range.start..fold_range.end],
1816 true,
1817 false,
1818 cx,
1819 );
1820 cx.stop_propagation();
1821 })
1822 .ok();
1823 })
1824 .into_any()
1825 }),
1826 merge_adjacent: true,
1827 ..FoldPlaceholder::default()
1828 };
1829 let display_map = display_map.unwrap_or_else(|| {
1830 cx.new(|cx| {
1831 DisplayMap::new(
1832 buffer.clone(),
1833 style.font(),
1834 font_size,
1835 None,
1836 FILE_HEADER_HEIGHT,
1837 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1838 fold_placeholder,
1839 diagnostics_max_severity,
1840 cx,
1841 )
1842 })
1843 });
1844
1845 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1846
1847 let blink_manager = cx.new(|cx| {
1848 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1849 if is_minimap {
1850 blink_manager.disable(cx);
1851 }
1852 blink_manager
1853 });
1854
1855 let soft_wrap_mode_override =
1856 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1857
1858 let mut project_subscriptions = Vec::new();
1859 if full_mode && let Some(project) = project.as_ref() {
1860 project_subscriptions.push(cx.subscribe_in(
1861 project,
1862 window,
1863 |editor, _, event, window, cx| match event {
1864 project::Event::RefreshCodeLens => {
1865 // we always query lens with actions, without storing them, always refreshing them
1866 }
1867 project::Event::RefreshInlayHints => {
1868 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1869 }
1870 project::Event::LanguageServerAdded(..)
1871 | project::Event::LanguageServerRemoved(..) => {
1872 if editor.tasks_update_task.is_none() {
1873 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1874 }
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1897 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1898 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1899 }
1900 }
1901
1902 project::Event::EntryRenamed(transaction) => {
1903 let Some(workspace) = editor.workspace() else {
1904 return;
1905 };
1906 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1907 else {
1908 return;
1909 };
1910 if active_editor.entity_id() == cx.entity_id() {
1911 let edited_buffers_already_open = {
1912 let other_editors: Vec<Entity<Editor>> = workspace
1913 .read(cx)
1914 .panes()
1915 .iter()
1916 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1917 .filter(|editor| editor.entity_id() != cx.entity_id())
1918 .collect();
1919
1920 transaction.0.keys().all(|buffer| {
1921 other_editors.iter().any(|editor| {
1922 let multi_buffer = editor.read(cx).buffer();
1923 multi_buffer.read(cx).is_singleton()
1924 && multi_buffer.read(cx).as_singleton().map_or(
1925 false,
1926 |singleton| {
1927 singleton.entity_id() == buffer.entity_id()
1928 },
1929 )
1930 })
1931 })
1932 };
1933
1934 if !edited_buffers_already_open {
1935 let workspace = workspace.downgrade();
1936 let transaction = transaction.clone();
1937 cx.defer_in(window, move |_, window, cx| {
1938 cx.spawn_in(window, async move |editor, cx| {
1939 Self::open_project_transaction(
1940 &editor,
1941 workspace,
1942 transaction,
1943 "Rename".to_string(),
1944 cx,
1945 )
1946 .await
1947 .ok()
1948 })
1949 .detach();
1950 });
1951 }
1952 }
1953 }
1954
1955 _ => {}
1956 },
1957 ));
1958 if let Some(task_inventory) = project
1959 .read(cx)
1960 .task_store()
1961 .read(cx)
1962 .task_inventory()
1963 .cloned()
1964 {
1965 project_subscriptions.push(cx.observe_in(
1966 &task_inventory,
1967 window,
1968 |editor, _, window, cx| {
1969 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1970 },
1971 ));
1972 };
1973
1974 project_subscriptions.push(cx.subscribe_in(
1975 &project.read(cx).breakpoint_store(),
1976 window,
1977 |editor, _, event, window, cx| match event {
1978 BreakpointStoreEvent::ClearDebugLines => {
1979 editor.clear_row_highlights::<ActiveDebugLine>();
1980 editor.refresh_inline_values(cx);
1981 }
1982 BreakpointStoreEvent::SetDebugLine => {
1983 if editor.go_to_active_debug_line(window, cx) {
1984 cx.stop_propagation();
1985 }
1986
1987 editor.refresh_inline_values(cx);
1988 }
1989 _ => {}
1990 },
1991 ));
1992 let git_store = project.read(cx).git_store().clone();
1993 let project = project.clone();
1994 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1995 if let GitStoreEvent::RepositoryUpdated(
1996 _,
1997 RepositoryEvent::Updated {
1998 new_instance: true, ..
1999 },
2000 _,
2001 ) = event
2002 {
2003 this.load_diff_task = Some(
2004 update_uncommitted_diff_for_buffer(
2005 cx.entity(),
2006 &project,
2007 this.buffer.read(cx).all_buffers(),
2008 this.buffer.clone(),
2009 cx,
2010 )
2011 .shared(),
2012 );
2013 }
2014 }));
2015 }
2016
2017 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2018
2019 let inlay_hint_settings =
2020 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2021 let focus_handle = cx.focus_handle();
2022 if !is_minimap {
2023 cx.on_focus(&focus_handle, window, Self::handle_focus)
2024 .detach();
2025 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2026 .detach();
2027 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2028 .detach();
2029 cx.on_blur(&focus_handle, window, Self::handle_blur)
2030 .detach();
2031 cx.observe_pending_input(window, Self::observe_pending_input)
2032 .detach();
2033 }
2034
2035 let show_indent_guides =
2036 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2037 Some(false)
2038 } else {
2039 None
2040 };
2041
2042 let breakpoint_store = match (&mode, project.as_ref()) {
2043 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2044 _ => None,
2045 };
2046
2047 let mut code_action_providers = Vec::new();
2048 let mut load_uncommitted_diff = None;
2049 if let Some(project) = project.clone() {
2050 load_uncommitted_diff = Some(
2051 update_uncommitted_diff_for_buffer(
2052 cx.entity(),
2053 &project,
2054 buffer.read(cx).all_buffers(),
2055 buffer.clone(),
2056 cx,
2057 )
2058 .shared(),
2059 );
2060 code_action_providers.push(Rc::new(project) as Rc<_>);
2061 }
2062
2063 let mut editor = Self {
2064 focus_handle,
2065 show_cursor_when_unfocused: false,
2066 last_focused_descendant: None,
2067 buffer: buffer.clone(),
2068 display_map: display_map.clone(),
2069 selections,
2070 scroll_manager: ScrollManager::new(cx),
2071 columnar_selection_state: None,
2072 add_selections_state: None,
2073 select_next_state: None,
2074 select_prev_state: None,
2075 selection_history: SelectionHistory::default(),
2076 defer_selection_effects: false,
2077 deferred_selection_effects_state: None,
2078 autoclose_regions: Vec::new(),
2079 snippet_stack: InvalidationStack::default(),
2080 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2081 ime_transaction: None,
2082 active_diagnostics: ActiveDiagnostic::None,
2083 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2084 inline_diagnostics_update: Task::ready(()),
2085 inline_diagnostics: Vec::new(),
2086 soft_wrap_mode_override,
2087 diagnostics_max_severity,
2088 hard_wrap: None,
2089 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2090 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2091 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2092 project,
2093 blink_manager: blink_manager.clone(),
2094 show_local_selections: true,
2095 show_scrollbars: ScrollbarAxes {
2096 horizontal: full_mode,
2097 vertical: full_mode,
2098 },
2099 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2100 offset_content: !matches!(mode, EditorMode::SingleLine),
2101 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2102 show_gutter: full_mode,
2103 show_line_numbers: (!full_mode).then_some(false),
2104 use_relative_line_numbers: None,
2105 disable_expand_excerpt_buttons: !full_mode,
2106 show_git_diff_gutter: None,
2107 show_code_actions: None,
2108 show_runnables: None,
2109 show_breakpoints: None,
2110 show_wrap_guides: None,
2111 show_indent_guides,
2112 placeholder_text: None,
2113 highlight_order: 0,
2114 highlighted_rows: HashMap::default(),
2115 background_highlights: HashMap::default(),
2116 gutter_highlights: HashMap::default(),
2117 scrollbar_marker_state: ScrollbarMarkerState::default(),
2118 active_indent_guides_state: ActiveIndentGuidesState::default(),
2119 nav_history: None,
2120 context_menu: RefCell::new(None),
2121 context_menu_options: None,
2122 mouse_context_menu: None,
2123 completion_tasks: Vec::new(),
2124 inline_blame_popover: None,
2125 inline_blame_popover_show_task: None,
2126 signature_help_state: SignatureHelpState::default(),
2127 auto_signature_help: None,
2128 find_all_references_task_sources: Vec::new(),
2129 next_completion_id: 0,
2130 next_inlay_id: 0,
2131 code_action_providers,
2132 available_code_actions: None,
2133 code_actions_task: None,
2134 quick_selection_highlight_task: None,
2135 debounced_selection_highlight_task: None,
2136 document_highlights_task: None,
2137 linked_editing_range_task: None,
2138 pending_rename: None,
2139 searchable: !is_minimap,
2140 cursor_shape: EditorSettings::get_global(cx)
2141 .cursor_shape
2142 .unwrap_or_default(),
2143 current_line_highlight: None,
2144 autoindent_mode: Some(AutoindentMode::EachLine),
2145 collapse_matches: false,
2146 workspace: None,
2147 input_enabled: !is_minimap,
2148 use_modal_editing: full_mode,
2149 read_only: is_minimap,
2150 use_autoclose: true,
2151 use_auto_surround: true,
2152 auto_replace_emoji_shortcode: false,
2153 jsx_tag_auto_close_enabled_in_any_buffer: false,
2154 leader_id: None,
2155 remote_id: None,
2156 hover_state: HoverState::default(),
2157 pending_mouse_down: None,
2158 hovered_link_state: None,
2159 edit_prediction_provider: None,
2160 active_edit_prediction: None,
2161 stale_edit_prediction_in_menu: None,
2162 edit_prediction_preview: EditPredictionPreview::Inactive {
2163 released_too_fast: false,
2164 },
2165 inline_diagnostics_enabled: full_mode,
2166 diagnostics_enabled: full_mode,
2167 word_completions_enabled: full_mode,
2168 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2169 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2170 gutter_hovered: false,
2171 pixel_position_of_newest_cursor: None,
2172 last_bounds: None,
2173 last_position_map: None,
2174 expect_bounds_change: None,
2175 gutter_dimensions: GutterDimensions::default(),
2176 style: None,
2177 show_cursor_names: false,
2178 hovered_cursors: HashMap::default(),
2179 next_editor_action_id: EditorActionId::default(),
2180 editor_actions: Rc::default(),
2181 edit_predictions_hidden_for_vim_mode: false,
2182 show_edit_predictions_override: None,
2183 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2184 edit_prediction_settings: EditPredictionSettings::Disabled,
2185 edit_prediction_indent_conflict: false,
2186 edit_prediction_requires_modifier_in_indent_conflict: true,
2187 custom_context_menu: None,
2188 show_git_blame_gutter: false,
2189 show_git_blame_inline: false,
2190 show_selection_menu: None,
2191 show_git_blame_inline_delay_task: None,
2192 git_blame_inline_enabled: full_mode
2193 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2194 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2195 serialize_dirty_buffers: !is_minimap
2196 && ProjectSettings::get_global(cx)
2197 .session
2198 .restore_unsaved_buffers,
2199 blame: None,
2200 blame_subscription: None,
2201 tasks: BTreeMap::default(),
2202
2203 breakpoint_store,
2204 gutter_breakpoint_indicator: (None, None),
2205 hovered_diff_hunk_row: None,
2206 _subscriptions: (!is_minimap)
2207 .then(|| {
2208 vec![
2209 cx.observe(&buffer, Self::on_buffer_changed),
2210 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2211 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2212 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2213 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2214 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2215 cx.observe_window_activation(window, |editor, window, cx| {
2216 let active = window.is_window_active();
2217 editor.blink_manager.update(cx, |blink_manager, cx| {
2218 if active {
2219 blink_manager.enable(cx);
2220 } else {
2221 blink_manager.disable(cx);
2222 }
2223 });
2224 if active {
2225 editor.show_mouse_cursor(cx);
2226 }
2227 }),
2228 ]
2229 })
2230 .unwrap_or_default(),
2231 tasks_update_task: None,
2232 pull_diagnostics_task: Task::ready(()),
2233 colors: None,
2234 next_color_inlay_id: 0,
2235 linked_edit_ranges: Default::default(),
2236 in_project_search: false,
2237 previous_search_ranges: None,
2238 breadcrumb_header: None,
2239 focused_block: None,
2240 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2241 addons: HashMap::default(),
2242 registered_buffers: HashMap::default(),
2243 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2244 selection_mark_mode: false,
2245 toggle_fold_multiple_buffers: Task::ready(()),
2246 serialize_selections: Task::ready(()),
2247 serialize_folds: Task::ready(()),
2248 text_style_refinement: None,
2249 load_diff_task: load_uncommitted_diff,
2250 temporary_diff_override: false,
2251 mouse_cursor_hidden: false,
2252 minimap: None,
2253 hide_mouse_mode: EditorSettings::get_global(cx)
2254 .hide_mouse
2255 .unwrap_or_default(),
2256 change_list: ChangeList::new(),
2257 mode,
2258 selection_drag_state: SelectionDragState::None,
2259 folding_newlines: Task::ready(()),
2260 };
2261
2262 if is_minimap {
2263 return editor;
2264 }
2265
2266 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2267 editor
2268 ._subscriptions
2269 .push(cx.observe(breakpoints, |_, _, cx| {
2270 cx.notify();
2271 }));
2272 }
2273 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2274 editor._subscriptions.extend(project_subscriptions);
2275
2276 editor._subscriptions.push(cx.subscribe_in(
2277 &cx.entity(),
2278 window,
2279 |editor, _, e: &EditorEvent, window, cx| match e {
2280 EditorEvent::ScrollPositionChanged { local, .. } => {
2281 if *local {
2282 let new_anchor = editor.scroll_manager.anchor();
2283 let snapshot = editor.snapshot(window, cx);
2284 editor.update_restoration_data(cx, move |data| {
2285 data.scroll_position = (
2286 new_anchor.top_row(&snapshot.buffer_snapshot),
2287 new_anchor.offset,
2288 );
2289 });
2290 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2291 editor.inline_blame_popover.take();
2292 }
2293 }
2294 EditorEvent::Edited { .. } => {
2295 if !vim_enabled(cx) {
2296 let (map, selections) = editor.selections.all_adjusted_display(cx);
2297 let pop_state = editor
2298 .change_list
2299 .last()
2300 .map(|previous| {
2301 previous.len() == selections.len()
2302 && previous.iter().enumerate().all(|(ix, p)| {
2303 p.to_display_point(&map).row()
2304 == selections[ix].head().row()
2305 })
2306 })
2307 .unwrap_or(false);
2308 let new_positions = selections
2309 .into_iter()
2310 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2311 .collect();
2312 editor
2313 .change_list
2314 .push_to_change_list(pop_state, new_positions);
2315 }
2316 }
2317 _ => (),
2318 },
2319 ));
2320
2321 if let Some(dap_store) = editor
2322 .project
2323 .as_ref()
2324 .map(|project| project.read(cx).dap_store())
2325 {
2326 let weak_editor = cx.weak_entity();
2327
2328 editor
2329 ._subscriptions
2330 .push(
2331 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2332 let session_entity = cx.entity();
2333 weak_editor
2334 .update(cx, |editor, cx| {
2335 editor._subscriptions.push(
2336 cx.subscribe(&session_entity, Self::on_debug_session_event),
2337 );
2338 })
2339 .ok();
2340 }),
2341 );
2342
2343 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2344 editor
2345 ._subscriptions
2346 .push(cx.subscribe(&session, Self::on_debug_session_event));
2347 }
2348 }
2349
2350 // skip adding the initial selection to selection history
2351 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2352 editor.end_selection(window, cx);
2353 editor.selection_history.mode = SelectionHistoryMode::Normal;
2354
2355 editor.scroll_manager.show_scrollbars(window, cx);
2356 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2357
2358 if full_mode {
2359 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2360 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2361
2362 if editor.git_blame_inline_enabled {
2363 editor.start_git_blame_inline(false, window, cx);
2364 }
2365
2366 editor.go_to_active_debug_line(window, cx);
2367
2368 if let Some(buffer) = buffer.read(cx).as_singleton()
2369 && let Some(project) = editor.project()
2370 {
2371 let handle = project.update(cx, |project, cx| {
2372 project.register_buffer_with_language_servers(&buffer, cx)
2373 });
2374 editor
2375 .registered_buffers
2376 .insert(buffer.read(cx).remote_id(), handle);
2377 }
2378
2379 editor.minimap =
2380 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2381 editor.colors = Some(LspColorData::new(cx));
2382 editor.update_lsp_data(false, None, window, cx);
2383 }
2384
2385 if editor.mode.is_full() {
2386 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2387 }
2388
2389 editor
2390 }
2391
2392 pub fn deploy_mouse_context_menu(
2393 &mut self,
2394 position: gpui::Point<Pixels>,
2395 context_menu: Entity<ContextMenu>,
2396 window: &mut Window,
2397 cx: &mut Context<Self>,
2398 ) {
2399 self.mouse_context_menu = Some(MouseContextMenu::new(
2400 self,
2401 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2402 context_menu,
2403 window,
2404 cx,
2405 ));
2406 }
2407
2408 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2409 self.mouse_context_menu
2410 .as_ref()
2411 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2412 }
2413
2414 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2415 if self
2416 .selections
2417 .pending
2418 .as_ref()
2419 .is_some_and(|pending_selection| {
2420 let snapshot = self.buffer().read(cx).snapshot(cx);
2421 pending_selection
2422 .selection
2423 .range()
2424 .includes(range, &snapshot)
2425 })
2426 {
2427 return true;
2428 }
2429
2430 self.selections
2431 .disjoint_in_range::<usize>(range.clone(), cx)
2432 .into_iter()
2433 .any(|selection| {
2434 // This is needed to cover a corner case, if we just check for an existing
2435 // selection in the fold range, having a cursor at the start of the fold
2436 // marks it as selected. Non-empty selections don't cause this.
2437 let length = selection.end - selection.start;
2438 length > 0
2439 })
2440 }
2441
2442 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2443 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2444 }
2445
2446 fn key_context_internal(
2447 &self,
2448 has_active_edit_prediction: bool,
2449 window: &Window,
2450 cx: &App,
2451 ) -> KeyContext {
2452 let mut key_context = KeyContext::new_with_defaults();
2453 key_context.add("Editor");
2454 let mode = match self.mode {
2455 EditorMode::SingleLine => "single_line",
2456 EditorMode::AutoHeight { .. } => "auto_height",
2457 EditorMode::Minimap { .. } => "minimap",
2458 EditorMode::Full { .. } => "full",
2459 };
2460
2461 if EditorSettings::jupyter_enabled(cx) {
2462 key_context.add("jupyter");
2463 }
2464
2465 key_context.set("mode", mode);
2466 if self.pending_rename.is_some() {
2467 key_context.add("renaming");
2468 }
2469
2470 match self.context_menu.borrow().as_ref() {
2471 Some(CodeContextMenu::Completions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_completions");
2475 }
2476 }
2477 Some(CodeContextMenu::CodeActions(menu)) => {
2478 if menu.visible() {
2479 key_context.add("menu");
2480 key_context.add("showing_code_actions")
2481 }
2482 }
2483 None => {}
2484 }
2485
2486 if self.signature_help_state.has_multiple_signatures() {
2487 key_context.add("showing_signature_help");
2488 }
2489
2490 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2491 if !self.focus_handle(cx).contains_focused(window, cx)
2492 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2493 {
2494 for addon in self.addons.values() {
2495 addon.extend_key_context(&mut key_context, cx)
2496 }
2497 }
2498
2499 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2500 if let Some(extension) = singleton_buffer
2501 .read(cx)
2502 .file()
2503 .and_then(|file| file.path().extension()?.to_str())
2504 {
2505 key_context.set("extension", extension.to_string());
2506 }
2507 } else {
2508 key_context.add("multibuffer");
2509 }
2510
2511 if has_active_edit_prediction {
2512 if self.edit_prediction_in_conflict() {
2513 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2514 } else {
2515 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2516 key_context.add("copilot_suggestion");
2517 }
2518 }
2519
2520 if self.selection_mark_mode {
2521 key_context.add("selection_mode");
2522 }
2523
2524 key_context
2525 }
2526
2527 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2528 if self.mouse_cursor_hidden {
2529 self.mouse_cursor_hidden = false;
2530 cx.notify();
2531 }
2532 }
2533
2534 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2535 let hide_mouse_cursor = match origin {
2536 HideMouseCursorOrigin::TypingAction => {
2537 matches!(
2538 self.hide_mouse_mode,
2539 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2540 )
2541 }
2542 HideMouseCursorOrigin::MovementAction => {
2543 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2544 }
2545 };
2546 if self.mouse_cursor_hidden != hide_mouse_cursor {
2547 self.mouse_cursor_hidden = hide_mouse_cursor;
2548 cx.notify();
2549 }
2550 }
2551
2552 pub fn edit_prediction_in_conflict(&self) -> bool {
2553 if !self.show_edit_predictions_in_menu() {
2554 return false;
2555 }
2556
2557 let showing_completions = self
2558 .context_menu
2559 .borrow()
2560 .as_ref()
2561 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2562
2563 showing_completions
2564 || self.edit_prediction_requires_modifier()
2565 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2566 // bindings to insert tab characters.
2567 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2568 }
2569
2570 pub fn accept_edit_prediction_keybind(
2571 &self,
2572 accept_partial: bool,
2573 window: &Window,
2574 cx: &App,
2575 ) -> AcceptEditPredictionBinding {
2576 let key_context = self.key_context_internal(true, window, cx);
2577 let in_conflict = self.edit_prediction_in_conflict();
2578
2579 let bindings = if accept_partial {
2580 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2581 } else {
2582 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2583 };
2584
2585 // TODO: if the binding contains multiple keystrokes, display all of them, not
2586 // just the first one.
2587 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2588 !in_conflict
2589 || binding
2590 .keystrokes()
2591 .first()
2592 .is_some_and(|keystroke| keystroke.modifiers().modified())
2593 }))
2594 }
2595
2596 pub fn new_file(
2597 workspace: &mut Workspace,
2598 _: &workspace::NewFile,
2599 window: &mut Window,
2600 cx: &mut Context<Workspace>,
2601 ) {
2602 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2603 "Failed to create buffer",
2604 window,
2605 cx,
2606 |e, _, _| match e.error_code() {
2607 ErrorCode::RemoteUpgradeRequired => Some(format!(
2608 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2609 e.error_tag("required").unwrap_or("the latest version")
2610 )),
2611 _ => None,
2612 },
2613 );
2614 }
2615
2616 pub fn new_in_workspace(
2617 workspace: &mut Workspace,
2618 window: &mut Window,
2619 cx: &mut Context<Workspace>,
2620 ) -> Task<Result<Entity<Editor>>> {
2621 let project = workspace.project().clone();
2622 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2623
2624 cx.spawn_in(window, async move |workspace, cx| {
2625 let buffer = create.await?;
2626 workspace.update_in(cx, |workspace, window, cx| {
2627 let editor =
2628 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2629 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2630 editor
2631 })
2632 })
2633 }
2634
2635 fn new_file_vertical(
2636 workspace: &mut Workspace,
2637 _: &workspace::NewFileSplitVertical,
2638 window: &mut Window,
2639 cx: &mut Context<Workspace>,
2640 ) {
2641 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2642 }
2643
2644 fn new_file_horizontal(
2645 workspace: &mut Workspace,
2646 _: &workspace::NewFileSplitHorizontal,
2647 window: &mut Window,
2648 cx: &mut Context<Workspace>,
2649 ) {
2650 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2651 }
2652
2653 fn new_file_in_direction(
2654 workspace: &mut Workspace,
2655 direction: SplitDirection,
2656 window: &mut Window,
2657 cx: &mut Context<Workspace>,
2658 ) {
2659 let project = workspace.project().clone();
2660 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2661
2662 cx.spawn_in(window, async move |workspace, cx| {
2663 let buffer = create.await?;
2664 workspace.update_in(cx, move |workspace, window, cx| {
2665 workspace.split_item(
2666 direction,
2667 Box::new(
2668 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2669 ),
2670 window,
2671 cx,
2672 )
2673 })?;
2674 anyhow::Ok(())
2675 })
2676 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2677 match e.error_code() {
2678 ErrorCode::RemoteUpgradeRequired => Some(format!(
2679 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2680 e.error_tag("required").unwrap_or("the latest version")
2681 )),
2682 _ => None,
2683 }
2684 });
2685 }
2686
2687 pub fn leader_id(&self) -> Option<CollaboratorId> {
2688 self.leader_id
2689 }
2690
2691 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2692 &self.buffer
2693 }
2694
2695 pub fn project(&self) -> Option<&Entity<Project>> {
2696 self.project.as_ref()
2697 }
2698
2699 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2700 self.workspace.as_ref()?.0.upgrade()
2701 }
2702
2703 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2704 self.buffer().read(cx).title(cx)
2705 }
2706
2707 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2708 let git_blame_gutter_max_author_length = self
2709 .render_git_blame_gutter(cx)
2710 .then(|| {
2711 if let Some(blame) = self.blame.as_ref() {
2712 let max_author_length =
2713 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2714 Some(max_author_length)
2715 } else {
2716 None
2717 }
2718 })
2719 .flatten();
2720
2721 EditorSnapshot {
2722 mode: self.mode.clone(),
2723 show_gutter: self.show_gutter,
2724 show_line_numbers: self.show_line_numbers,
2725 show_git_diff_gutter: self.show_git_diff_gutter,
2726 show_code_actions: self.show_code_actions,
2727 show_runnables: self.show_runnables,
2728 show_breakpoints: self.show_breakpoints,
2729 git_blame_gutter_max_author_length,
2730 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2731 scroll_anchor: self.scroll_manager.anchor(),
2732 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2733 placeholder_text: self.placeholder_text.clone(),
2734 is_focused: self.focus_handle.is_focused(window),
2735 current_line_highlight: self
2736 .current_line_highlight
2737 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2738 gutter_hovered: self.gutter_hovered,
2739 }
2740 }
2741
2742 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2743 self.buffer.read(cx).language_at(point, cx)
2744 }
2745
2746 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2747 self.buffer.read(cx).read(cx).file_at(point).cloned()
2748 }
2749
2750 pub fn active_excerpt(
2751 &self,
2752 cx: &App,
2753 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2754 self.buffer
2755 .read(cx)
2756 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2757 }
2758
2759 pub fn mode(&self) -> &EditorMode {
2760 &self.mode
2761 }
2762
2763 pub fn set_mode(&mut self, mode: EditorMode) {
2764 self.mode = mode;
2765 }
2766
2767 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2768 self.collaboration_hub.as_deref()
2769 }
2770
2771 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2772 self.collaboration_hub = Some(hub);
2773 }
2774
2775 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2776 self.in_project_search = in_project_search;
2777 }
2778
2779 pub fn set_custom_context_menu(
2780 &mut self,
2781 f: impl 'static
2782 + Fn(
2783 &mut Self,
2784 DisplayPoint,
2785 &mut Window,
2786 &mut Context<Self>,
2787 ) -> Option<Entity<ui::ContextMenu>>,
2788 ) {
2789 self.custom_context_menu = Some(Box::new(f))
2790 }
2791
2792 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2793 self.completion_provider = provider;
2794 }
2795
2796 #[cfg(any(test, feature = "test-support"))]
2797 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2798 self.completion_provider.clone()
2799 }
2800
2801 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2802 self.semantics_provider.clone()
2803 }
2804
2805 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2806 self.semantics_provider = provider;
2807 }
2808
2809 pub fn set_edit_prediction_provider<T>(
2810 &mut self,
2811 provider: Option<Entity<T>>,
2812 window: &mut Window,
2813 cx: &mut Context<Self>,
2814 ) where
2815 T: EditPredictionProvider,
2816 {
2817 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2818 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2819 if this.focus_handle.is_focused(window) {
2820 this.update_visible_edit_prediction(window, cx);
2821 }
2822 }),
2823 provider: Arc::new(provider),
2824 });
2825 self.update_edit_prediction_settings(cx);
2826 self.refresh_edit_prediction(false, false, window, cx);
2827 }
2828
2829 pub fn placeholder_text(&self) -> Option<&str> {
2830 self.placeholder_text.as_deref()
2831 }
2832
2833 pub fn set_placeholder_text(
2834 &mut self,
2835 placeholder_text: impl Into<Arc<str>>,
2836 cx: &mut Context<Self>,
2837 ) {
2838 let placeholder_text = Some(placeholder_text.into());
2839 if self.placeholder_text != placeholder_text {
2840 self.placeholder_text = placeholder_text;
2841 cx.notify();
2842 }
2843 }
2844
2845 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2846 self.cursor_shape = cursor_shape;
2847
2848 // Disrupt blink for immediate user feedback that the cursor shape has changed
2849 self.blink_manager.update(cx, BlinkManager::show_cursor);
2850
2851 cx.notify();
2852 }
2853
2854 pub fn set_current_line_highlight(
2855 &mut self,
2856 current_line_highlight: Option<CurrentLineHighlight>,
2857 ) {
2858 self.current_line_highlight = current_line_highlight;
2859 }
2860
2861 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2862 self.collapse_matches = collapse_matches;
2863 }
2864
2865 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2866 let buffers = self.buffer.read(cx).all_buffers();
2867 let Some(project) = self.project.as_ref() else {
2868 return;
2869 };
2870 project.update(cx, |project, cx| {
2871 for buffer in buffers {
2872 self.registered_buffers
2873 .entry(buffer.read(cx).remote_id())
2874 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2875 }
2876 })
2877 }
2878
2879 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2880 if self.collapse_matches {
2881 return range.start..range.start;
2882 }
2883 range.clone()
2884 }
2885
2886 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2887 if self.display_map.read(cx).clip_at_line_ends != clip {
2888 self.display_map
2889 .update(cx, |map, _| map.clip_at_line_ends = clip);
2890 }
2891 }
2892
2893 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2894 self.input_enabled = input_enabled;
2895 }
2896
2897 pub fn set_edit_predictions_hidden_for_vim_mode(
2898 &mut self,
2899 hidden: bool,
2900 window: &mut Window,
2901 cx: &mut Context<Self>,
2902 ) {
2903 if hidden != self.edit_predictions_hidden_for_vim_mode {
2904 self.edit_predictions_hidden_for_vim_mode = hidden;
2905 if hidden {
2906 self.update_visible_edit_prediction(window, cx);
2907 } else {
2908 self.refresh_edit_prediction(true, false, window, cx);
2909 }
2910 }
2911 }
2912
2913 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2914 self.menu_edit_predictions_policy = value;
2915 }
2916
2917 pub fn set_autoindent(&mut self, autoindent: bool) {
2918 if autoindent {
2919 self.autoindent_mode = Some(AutoindentMode::EachLine);
2920 } else {
2921 self.autoindent_mode = None;
2922 }
2923 }
2924
2925 pub fn read_only(&self, cx: &App) -> bool {
2926 self.read_only || self.buffer.read(cx).read_only()
2927 }
2928
2929 pub fn set_read_only(&mut self, read_only: bool) {
2930 self.read_only = read_only;
2931 }
2932
2933 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2934 self.use_autoclose = autoclose;
2935 }
2936
2937 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2938 self.use_auto_surround = auto_surround;
2939 }
2940
2941 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2942 self.auto_replace_emoji_shortcode = auto_replace;
2943 }
2944
2945 pub fn toggle_edit_predictions(
2946 &mut self,
2947 _: &ToggleEditPrediction,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) {
2951 if self.show_edit_predictions_override.is_some() {
2952 self.set_show_edit_predictions(None, window, cx);
2953 } else {
2954 let show_edit_predictions = !self.edit_predictions_enabled();
2955 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2956 }
2957 }
2958
2959 pub fn set_show_edit_predictions(
2960 &mut self,
2961 show_edit_predictions: Option<bool>,
2962 window: &mut Window,
2963 cx: &mut Context<Self>,
2964 ) {
2965 self.show_edit_predictions_override = show_edit_predictions;
2966 self.update_edit_prediction_settings(cx);
2967
2968 if let Some(false) = show_edit_predictions {
2969 self.discard_edit_prediction(false, cx);
2970 } else {
2971 self.refresh_edit_prediction(false, true, window, cx);
2972 }
2973 }
2974
2975 fn edit_predictions_disabled_in_scope(
2976 &self,
2977 buffer: &Entity<Buffer>,
2978 buffer_position: language::Anchor,
2979 cx: &App,
2980 ) -> bool {
2981 let snapshot = buffer.read(cx).snapshot();
2982 let settings = snapshot.settings_at(buffer_position, cx);
2983
2984 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2985 return false;
2986 };
2987
2988 scope.override_name().is_some_and(|scope_name| {
2989 settings
2990 .edit_predictions_disabled_in
2991 .iter()
2992 .any(|s| s == scope_name)
2993 })
2994 }
2995
2996 pub fn set_use_modal_editing(&mut self, to: bool) {
2997 self.use_modal_editing = to;
2998 }
2999
3000 pub fn use_modal_editing(&self) -> bool {
3001 self.use_modal_editing
3002 }
3003
3004 fn selections_did_change(
3005 &mut self,
3006 local: bool,
3007 old_cursor_position: &Anchor,
3008 effects: SelectionEffects,
3009 window: &mut Window,
3010 cx: &mut Context<Self>,
3011 ) {
3012 window.invalidate_character_coordinates();
3013
3014 // Copy selections to primary selection buffer
3015 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3016 if local {
3017 let selections = self.selections.all::<usize>(cx);
3018 let buffer_handle = self.buffer.read(cx).read(cx);
3019
3020 let mut text = String::new();
3021 for (index, selection) in selections.iter().enumerate() {
3022 let text_for_selection = buffer_handle
3023 .text_for_range(selection.start..selection.end)
3024 .collect::<String>();
3025
3026 text.push_str(&text_for_selection);
3027 if index != selections.len() - 1 {
3028 text.push('\n');
3029 }
3030 }
3031
3032 if !text.is_empty() {
3033 cx.write_to_primary(ClipboardItem::new_string(text));
3034 }
3035 }
3036
3037 let selection_anchors = self.selections.disjoint_anchors();
3038
3039 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3040 self.buffer.update(cx, |buffer, cx| {
3041 buffer.set_active_selections(
3042 &selection_anchors,
3043 self.selections.line_mode,
3044 self.cursor_shape,
3045 cx,
3046 )
3047 });
3048 }
3049 let display_map = self
3050 .display_map
3051 .update(cx, |display_map, cx| display_map.snapshot(cx));
3052 let buffer = &display_map.buffer_snapshot;
3053 if self.selections.count() == 1 {
3054 self.add_selections_state = None;
3055 }
3056 self.select_next_state = None;
3057 self.select_prev_state = None;
3058 self.select_syntax_node_history.try_clear();
3059 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3060 self.snippet_stack.invalidate(&selection_anchors, buffer);
3061 self.take_rename(false, window, cx);
3062
3063 let newest_selection = self.selections.newest_anchor();
3064 let new_cursor_position = newest_selection.head();
3065 let selection_start = newest_selection.start;
3066
3067 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3068 self.push_to_nav_history(
3069 *old_cursor_position,
3070 Some(new_cursor_position.to_point(buffer)),
3071 false,
3072 effects.nav_history == Some(true),
3073 cx,
3074 );
3075 }
3076
3077 if local {
3078 if let Some(buffer_id) = new_cursor_position.buffer_id
3079 && !self.registered_buffers.contains_key(&buffer_id)
3080 && let Some(project) = self.project.as_ref()
3081 {
3082 project.update(cx, |project, cx| {
3083 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3084 return;
3085 };
3086 self.registered_buffers.insert(
3087 buffer_id,
3088 project.register_buffer_with_language_servers(&buffer, cx),
3089 );
3090 })
3091 }
3092
3093 let mut context_menu = self.context_menu.borrow_mut();
3094 let completion_menu = match context_menu.as_ref() {
3095 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3096 Some(CodeContextMenu::CodeActions(_)) => {
3097 *context_menu = None;
3098 None
3099 }
3100 None => None,
3101 };
3102 let completion_position = completion_menu.map(|menu| menu.initial_position);
3103 drop(context_menu);
3104
3105 if effects.completions
3106 && let Some(completion_position) = completion_position
3107 {
3108 let start_offset = selection_start.to_offset(buffer);
3109 let position_matches = start_offset == completion_position.to_offset(buffer);
3110 let continue_showing = if position_matches {
3111 if self.snippet_stack.is_empty() {
3112 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3113 } else {
3114 // Snippet choices can be shown even when the cursor is in whitespace.
3115 // Dismissing the menu with actions like backspace is handled by
3116 // invalidation regions.
3117 true
3118 }
3119 } else {
3120 false
3121 };
3122
3123 if continue_showing {
3124 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3125 } else {
3126 self.hide_context_menu(window, cx);
3127 }
3128 }
3129
3130 hide_hover(self, cx);
3131
3132 if old_cursor_position.to_display_point(&display_map).row()
3133 != new_cursor_position.to_display_point(&display_map).row()
3134 {
3135 self.available_code_actions.take();
3136 }
3137 self.refresh_code_actions(window, cx);
3138 self.refresh_document_highlights(cx);
3139 self.refresh_selected_text_highlights(false, window, cx);
3140 refresh_matching_bracket_highlights(self, window, cx);
3141 self.update_visible_edit_prediction(window, cx);
3142 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3143 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3144 self.inline_blame_popover.take();
3145 if self.git_blame_inline_enabled {
3146 self.start_inline_blame_timer(window, cx);
3147 }
3148 }
3149
3150 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3151 cx.emit(EditorEvent::SelectionsChanged { local });
3152
3153 let selections = &self.selections.disjoint;
3154 if selections.len() == 1 {
3155 cx.emit(SearchEvent::ActiveMatchChanged)
3156 }
3157 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3158 let inmemory_selections = selections
3159 .iter()
3160 .map(|s| {
3161 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3162 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3163 })
3164 .collect();
3165 self.update_restoration_data(cx, |data| {
3166 data.selections = inmemory_selections;
3167 });
3168
3169 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3170 && let Some(workspace_id) =
3171 self.workspace.as_ref().and_then(|workspace| workspace.1)
3172 {
3173 let snapshot = self.buffer().read(cx).snapshot(cx);
3174 let selections = selections.clone();
3175 let background_executor = cx.background_executor().clone();
3176 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3177 self.serialize_selections = cx.background_spawn(async move {
3178 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3179 let db_selections = selections
3180 .iter()
3181 .map(|selection| {
3182 (
3183 selection.start.to_offset(&snapshot),
3184 selection.end.to_offset(&snapshot),
3185 )
3186 })
3187 .collect();
3188
3189 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3190 .await
3191 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3192 .log_err();
3193 });
3194 }
3195 }
3196
3197 cx.notify();
3198 }
3199
3200 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3201 use text::ToOffset as _;
3202 use text::ToPoint as _;
3203
3204 if self.mode.is_minimap()
3205 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3206 {
3207 return;
3208 }
3209
3210 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3211 return;
3212 };
3213
3214 let snapshot = singleton.read(cx).snapshot();
3215 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3216 let display_snapshot = display_map.snapshot(cx);
3217
3218 display_snapshot
3219 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3220 .map(|fold| {
3221 fold.range.start.text_anchor.to_point(&snapshot)
3222 ..fold.range.end.text_anchor.to_point(&snapshot)
3223 })
3224 .collect()
3225 });
3226 self.update_restoration_data(cx, |data| {
3227 data.folds = inmemory_folds;
3228 });
3229
3230 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3231 return;
3232 };
3233 let background_executor = cx.background_executor().clone();
3234 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3235 let db_folds = self.display_map.update(cx, |display_map, cx| {
3236 display_map
3237 .snapshot(cx)
3238 .folds_in_range(0..snapshot.len())
3239 .map(|fold| {
3240 (
3241 fold.range.start.text_anchor.to_offset(&snapshot),
3242 fold.range.end.text_anchor.to_offset(&snapshot),
3243 )
3244 })
3245 .collect()
3246 });
3247 self.serialize_folds = cx.background_spawn(async move {
3248 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3249 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3250 .await
3251 .with_context(|| {
3252 format!(
3253 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3254 )
3255 })
3256 .log_err();
3257 });
3258 }
3259
3260 pub fn sync_selections(
3261 &mut self,
3262 other: Entity<Editor>,
3263 cx: &mut Context<Self>,
3264 ) -> gpui::Subscription {
3265 let other_selections = other.read(cx).selections.disjoint.to_vec();
3266 self.selections.change_with(cx, |selections| {
3267 selections.select_anchors(other_selections);
3268 });
3269
3270 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3271 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3272 let other_selections = other.read(cx).selections.disjoint.to_vec();
3273 if other_selections.is_empty() {
3274 return;
3275 }
3276 this.selections.change_with(cx, |selections| {
3277 selections.select_anchors(other_selections);
3278 });
3279 }
3280 });
3281
3282 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3283 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3284 let these_selections = this.selections.disjoint.to_vec();
3285 if these_selections.is_empty() {
3286 return;
3287 }
3288 other.update(cx, |other_editor, cx| {
3289 other_editor.selections.change_with(cx, |selections| {
3290 selections.select_anchors(these_selections);
3291 })
3292 });
3293 }
3294 });
3295
3296 Subscription::join(other_subscription, this_subscription)
3297 }
3298
3299 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3300 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3301 /// effects of selection change occur at the end of the transaction.
3302 pub fn change_selections<R>(
3303 &mut self,
3304 effects: SelectionEffects,
3305 window: &mut Window,
3306 cx: &mut Context<Self>,
3307 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3308 ) -> R {
3309 if let Some(state) = &mut self.deferred_selection_effects_state {
3310 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3311 state.effects.completions = effects.completions;
3312 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3313 let (changed, result) = self.selections.change_with(cx, change);
3314 state.changed |= changed;
3315 return result;
3316 }
3317 let mut state = DeferredSelectionEffectsState {
3318 changed: false,
3319 effects,
3320 old_cursor_position: self.selections.newest_anchor().head(),
3321 history_entry: SelectionHistoryEntry {
3322 selections: self.selections.disjoint_anchors(),
3323 select_next_state: self.select_next_state.clone(),
3324 select_prev_state: self.select_prev_state.clone(),
3325 add_selections_state: self.add_selections_state.clone(),
3326 },
3327 };
3328 let (changed, result) = self.selections.change_with(cx, change);
3329 state.changed = state.changed || changed;
3330 if self.defer_selection_effects {
3331 self.deferred_selection_effects_state = Some(state);
3332 } else {
3333 self.apply_selection_effects(state, window, cx);
3334 }
3335 result
3336 }
3337
3338 /// Defers the effects of selection change, so that the effects of multiple calls to
3339 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3340 /// to selection history and the state of popovers based on selection position aren't
3341 /// erroneously updated.
3342 pub fn with_selection_effects_deferred<R>(
3343 &mut self,
3344 window: &mut Window,
3345 cx: &mut Context<Self>,
3346 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3347 ) -> R {
3348 let already_deferred = self.defer_selection_effects;
3349 self.defer_selection_effects = true;
3350 let result = update(self, window, cx);
3351 if !already_deferred {
3352 self.defer_selection_effects = false;
3353 if let Some(state) = self.deferred_selection_effects_state.take() {
3354 self.apply_selection_effects(state, window, cx);
3355 }
3356 }
3357 result
3358 }
3359
3360 fn apply_selection_effects(
3361 &mut self,
3362 state: DeferredSelectionEffectsState,
3363 window: &mut Window,
3364 cx: &mut Context<Self>,
3365 ) {
3366 if state.changed {
3367 self.selection_history.push(state.history_entry);
3368
3369 if let Some(autoscroll) = state.effects.scroll {
3370 self.request_autoscroll(autoscroll, cx);
3371 }
3372
3373 let old_cursor_position = &state.old_cursor_position;
3374
3375 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3376
3377 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3378 self.show_signature_help(&ShowSignatureHelp, window, cx);
3379 }
3380 }
3381 }
3382
3383 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3384 where
3385 I: IntoIterator<Item = (Range<S>, T)>,
3386 S: ToOffset,
3387 T: Into<Arc<str>>,
3388 {
3389 if self.read_only(cx) {
3390 return;
3391 }
3392
3393 self.buffer
3394 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3395 }
3396
3397 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3398 where
3399 I: IntoIterator<Item = (Range<S>, T)>,
3400 S: ToOffset,
3401 T: Into<Arc<str>>,
3402 {
3403 if self.read_only(cx) {
3404 return;
3405 }
3406
3407 self.buffer.update(cx, |buffer, cx| {
3408 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3409 });
3410 }
3411
3412 pub fn edit_with_block_indent<I, S, T>(
3413 &mut self,
3414 edits: I,
3415 original_indent_columns: Vec<Option<u32>>,
3416 cx: &mut Context<Self>,
3417 ) where
3418 I: IntoIterator<Item = (Range<S>, T)>,
3419 S: ToOffset,
3420 T: Into<Arc<str>>,
3421 {
3422 if self.read_only(cx) {
3423 return;
3424 }
3425
3426 self.buffer.update(cx, |buffer, cx| {
3427 buffer.edit(
3428 edits,
3429 Some(AutoindentMode::Block {
3430 original_indent_columns,
3431 }),
3432 cx,
3433 )
3434 });
3435 }
3436
3437 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3438 self.hide_context_menu(window, cx);
3439
3440 match phase {
3441 SelectPhase::Begin {
3442 position,
3443 add,
3444 click_count,
3445 } => self.begin_selection(position, add, click_count, window, cx),
3446 SelectPhase::BeginColumnar {
3447 position,
3448 goal_column,
3449 reset,
3450 mode,
3451 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3452 SelectPhase::Extend {
3453 position,
3454 click_count,
3455 } => self.extend_selection(position, click_count, window, cx),
3456 SelectPhase::Update {
3457 position,
3458 goal_column,
3459 scroll_delta,
3460 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3461 SelectPhase::End => self.end_selection(window, cx),
3462 }
3463 }
3464
3465 fn extend_selection(
3466 &mut self,
3467 position: DisplayPoint,
3468 click_count: usize,
3469 window: &mut Window,
3470 cx: &mut Context<Self>,
3471 ) {
3472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3473 let tail = self.selections.newest::<usize>(cx).tail();
3474 self.begin_selection(position, false, click_count, window, cx);
3475
3476 let position = position.to_offset(&display_map, Bias::Left);
3477 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3478
3479 let mut pending_selection = self
3480 .selections
3481 .pending_anchor()
3482 .expect("extend_selection not called with pending selection");
3483 if position >= tail {
3484 pending_selection.start = tail_anchor;
3485 } else {
3486 pending_selection.end = tail_anchor;
3487 pending_selection.reversed = true;
3488 }
3489
3490 let mut pending_mode = self.selections.pending_mode().unwrap();
3491 match &mut pending_mode {
3492 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3493 _ => {}
3494 }
3495
3496 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3497 SelectionEffects::scroll(Autoscroll::fit())
3498 } else {
3499 SelectionEffects::no_scroll()
3500 };
3501
3502 self.change_selections(effects, window, cx, |s| {
3503 s.set_pending(pending_selection, pending_mode)
3504 });
3505 }
3506
3507 fn begin_selection(
3508 &mut self,
3509 position: DisplayPoint,
3510 add: bool,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 if !self.focus_handle.is_focused(window) {
3516 self.last_focused_descendant = None;
3517 window.focus(&self.focus_handle);
3518 }
3519
3520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3521 let buffer = &display_map.buffer_snapshot;
3522 let position = display_map.clip_point(position, Bias::Left);
3523
3524 let start;
3525 let end;
3526 let mode;
3527 let mut auto_scroll;
3528 match click_count {
3529 1 => {
3530 start = buffer.anchor_before(position.to_point(&display_map));
3531 end = start;
3532 mode = SelectMode::Character;
3533 auto_scroll = true;
3534 }
3535 2 => {
3536 let position = display_map
3537 .clip_point(position, Bias::Left)
3538 .to_offset(&display_map, Bias::Left);
3539 let (range, _) = buffer.surrounding_word(position, false);
3540 start = buffer.anchor_before(range.start);
3541 end = buffer.anchor_before(range.end);
3542 mode = SelectMode::Word(start..end);
3543 auto_scroll = true;
3544 }
3545 3 => {
3546 let position = display_map
3547 .clip_point(position, Bias::Left)
3548 .to_point(&display_map);
3549 let line_start = display_map.prev_line_boundary(position).0;
3550 let next_line_start = buffer.clip_point(
3551 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3552 Bias::Left,
3553 );
3554 start = buffer.anchor_before(line_start);
3555 end = buffer.anchor_before(next_line_start);
3556 mode = SelectMode::Line(start..end);
3557 auto_scroll = true;
3558 }
3559 _ => {
3560 start = buffer.anchor_before(0);
3561 end = buffer.anchor_before(buffer.len());
3562 mode = SelectMode::All;
3563 auto_scroll = false;
3564 }
3565 }
3566 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3567
3568 let point_to_delete: Option<usize> = {
3569 let selected_points: Vec<Selection<Point>> =
3570 self.selections.disjoint_in_range(start..end, cx);
3571
3572 if !add || click_count > 1 {
3573 None
3574 } else if !selected_points.is_empty() {
3575 Some(selected_points[0].id)
3576 } else {
3577 let clicked_point_already_selected =
3578 self.selections.disjoint.iter().find(|selection| {
3579 selection.start.to_point(buffer) == start.to_point(buffer)
3580 || selection.end.to_point(buffer) == end.to_point(buffer)
3581 });
3582
3583 clicked_point_already_selected.map(|selection| selection.id)
3584 }
3585 };
3586
3587 let selections_count = self.selections.count();
3588 let effects = if auto_scroll {
3589 SelectionEffects::default()
3590 } else {
3591 SelectionEffects::no_scroll()
3592 };
3593
3594 self.change_selections(effects, window, cx, |s| {
3595 if let Some(point_to_delete) = point_to_delete {
3596 s.delete(point_to_delete);
3597
3598 if selections_count == 1 {
3599 s.set_pending_anchor_range(start..end, mode);
3600 }
3601 } else {
3602 if !add {
3603 s.clear_disjoint();
3604 }
3605
3606 s.set_pending_anchor_range(start..end, mode);
3607 }
3608 });
3609 }
3610
3611 fn begin_columnar_selection(
3612 &mut self,
3613 position: DisplayPoint,
3614 goal_column: u32,
3615 reset: bool,
3616 mode: ColumnarMode,
3617 window: &mut Window,
3618 cx: &mut Context<Self>,
3619 ) {
3620 if !self.focus_handle.is_focused(window) {
3621 self.last_focused_descendant = None;
3622 window.focus(&self.focus_handle);
3623 }
3624
3625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3626
3627 if reset {
3628 let pointer_position = display_map
3629 .buffer_snapshot
3630 .anchor_before(position.to_point(&display_map));
3631
3632 self.change_selections(
3633 SelectionEffects::scroll(Autoscroll::newest()),
3634 window,
3635 cx,
3636 |s| {
3637 s.clear_disjoint();
3638 s.set_pending_anchor_range(
3639 pointer_position..pointer_position,
3640 SelectMode::Character,
3641 );
3642 },
3643 );
3644 };
3645
3646 let tail = self.selections.newest::<Point>(cx).tail();
3647 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3648 self.columnar_selection_state = match mode {
3649 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3650 selection_tail: selection_anchor,
3651 display_point: if reset {
3652 if position.column() != goal_column {
3653 Some(DisplayPoint::new(position.row(), goal_column))
3654 } else {
3655 None
3656 }
3657 } else {
3658 None
3659 },
3660 }),
3661 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3662 selection_tail: selection_anchor,
3663 }),
3664 };
3665
3666 if !reset {
3667 self.select_columns(position, goal_column, &display_map, window, cx);
3668 }
3669 }
3670
3671 fn update_selection(
3672 &mut self,
3673 position: DisplayPoint,
3674 goal_column: u32,
3675 scroll_delta: gpui::Point<f32>,
3676 window: &mut Window,
3677 cx: &mut Context<Self>,
3678 ) {
3679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3680
3681 if self.columnar_selection_state.is_some() {
3682 self.select_columns(position, goal_column, &display_map, window, cx);
3683 } else if let Some(mut pending) = self.selections.pending_anchor() {
3684 let buffer = &display_map.buffer_snapshot;
3685 let head;
3686 let tail;
3687 let mode = self.selections.pending_mode().unwrap();
3688 match &mode {
3689 SelectMode::Character => {
3690 head = position.to_point(&display_map);
3691 tail = pending.tail().to_point(buffer);
3692 }
3693 SelectMode::Word(original_range) => {
3694 let offset = display_map
3695 .clip_point(position, Bias::Left)
3696 .to_offset(&display_map, Bias::Left);
3697 let original_range = original_range.to_offset(buffer);
3698
3699 let head_offset = if buffer.is_inside_word(offset, false)
3700 || original_range.contains(&offset)
3701 {
3702 let (word_range, _) = buffer.surrounding_word(offset, false);
3703 if word_range.start < original_range.start {
3704 word_range.start
3705 } else {
3706 word_range.end
3707 }
3708 } else {
3709 offset
3710 };
3711
3712 head = head_offset.to_point(buffer);
3713 if head_offset <= original_range.start {
3714 tail = original_range.end.to_point(buffer);
3715 } else {
3716 tail = original_range.start.to_point(buffer);
3717 }
3718 }
3719 SelectMode::Line(original_range) => {
3720 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3721
3722 let position = display_map
3723 .clip_point(position, Bias::Left)
3724 .to_point(&display_map);
3725 let line_start = display_map.prev_line_boundary(position).0;
3726 let next_line_start = buffer.clip_point(
3727 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3728 Bias::Left,
3729 );
3730
3731 if line_start < original_range.start {
3732 head = line_start
3733 } else {
3734 head = next_line_start
3735 }
3736
3737 if head <= original_range.start {
3738 tail = original_range.end;
3739 } else {
3740 tail = original_range.start;
3741 }
3742 }
3743 SelectMode::All => {
3744 return;
3745 }
3746 };
3747
3748 if head < tail {
3749 pending.start = buffer.anchor_before(head);
3750 pending.end = buffer.anchor_before(tail);
3751 pending.reversed = true;
3752 } else {
3753 pending.start = buffer.anchor_before(tail);
3754 pending.end = buffer.anchor_before(head);
3755 pending.reversed = false;
3756 }
3757
3758 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3759 s.set_pending(pending, mode);
3760 });
3761 } else {
3762 log::error!("update_selection dispatched with no pending selection");
3763 return;
3764 }
3765
3766 self.apply_scroll_delta(scroll_delta, window, cx);
3767 cx.notify();
3768 }
3769
3770 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3771 self.columnar_selection_state.take();
3772 if self.selections.pending_anchor().is_some() {
3773 let selections = self.selections.all::<usize>(cx);
3774 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3775 s.select(selections);
3776 s.clear_pending();
3777 });
3778 }
3779 }
3780
3781 fn select_columns(
3782 &mut self,
3783 head: DisplayPoint,
3784 goal_column: u32,
3785 display_map: &DisplaySnapshot,
3786 window: &mut Window,
3787 cx: &mut Context<Self>,
3788 ) {
3789 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3790 return;
3791 };
3792
3793 let tail = match columnar_state {
3794 ColumnarSelectionState::FromMouse {
3795 selection_tail,
3796 display_point,
3797 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3798 ColumnarSelectionState::FromSelection { selection_tail } => {
3799 selection_tail.to_display_point(display_map)
3800 }
3801 };
3802
3803 let start_row = cmp::min(tail.row(), head.row());
3804 let end_row = cmp::max(tail.row(), head.row());
3805 let start_column = cmp::min(tail.column(), goal_column);
3806 let end_column = cmp::max(tail.column(), goal_column);
3807 let reversed = start_column < tail.column();
3808
3809 let selection_ranges = (start_row.0..=end_row.0)
3810 .map(DisplayRow)
3811 .filter_map(|row| {
3812 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3813 || start_column <= display_map.line_len(row))
3814 && !display_map.is_block_line(row)
3815 {
3816 let start = display_map
3817 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3818 .to_point(display_map);
3819 let end = display_map
3820 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3821 .to_point(display_map);
3822 if reversed {
3823 Some(end..start)
3824 } else {
3825 Some(start..end)
3826 }
3827 } else {
3828 None
3829 }
3830 })
3831 .collect::<Vec<_>>();
3832
3833 let ranges = match columnar_state {
3834 ColumnarSelectionState::FromMouse { .. } => {
3835 let mut non_empty_ranges = selection_ranges
3836 .iter()
3837 .filter(|selection_range| selection_range.start != selection_range.end)
3838 .peekable();
3839 if non_empty_ranges.peek().is_some() {
3840 non_empty_ranges.cloned().collect()
3841 } else {
3842 selection_ranges
3843 }
3844 }
3845 _ => selection_ranges,
3846 };
3847
3848 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3849 s.select_ranges(ranges);
3850 });
3851 cx.notify();
3852 }
3853
3854 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3855 self.selections
3856 .all_adjusted(cx)
3857 .iter()
3858 .any(|selection| !selection.is_empty())
3859 }
3860
3861 pub fn has_pending_nonempty_selection(&self) -> bool {
3862 let pending_nonempty_selection = match self.selections.pending_anchor() {
3863 Some(Selection { start, end, .. }) => start != end,
3864 None => false,
3865 };
3866
3867 pending_nonempty_selection
3868 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3869 }
3870
3871 pub fn has_pending_selection(&self) -> bool {
3872 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3873 }
3874
3875 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3876 self.selection_mark_mode = false;
3877 self.selection_drag_state = SelectionDragState::None;
3878
3879 if self.clear_expanded_diff_hunks(cx) {
3880 cx.notify();
3881 return;
3882 }
3883 if self.dismiss_menus_and_popups(true, window, cx) {
3884 return;
3885 }
3886
3887 if self.mode.is_full()
3888 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3889 {
3890 return;
3891 }
3892
3893 cx.propagate();
3894 }
3895
3896 pub fn dismiss_menus_and_popups(
3897 &mut self,
3898 is_user_requested: bool,
3899 window: &mut Window,
3900 cx: &mut Context<Self>,
3901 ) -> bool {
3902 if self.take_rename(false, window, cx).is_some() {
3903 return true;
3904 }
3905
3906 if hide_hover(self, cx) {
3907 return true;
3908 }
3909
3910 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3911 return true;
3912 }
3913
3914 if self.hide_context_menu(window, cx).is_some() {
3915 return true;
3916 }
3917
3918 if self.mouse_context_menu.take().is_some() {
3919 return true;
3920 }
3921
3922 if is_user_requested && self.discard_edit_prediction(true, cx) {
3923 return true;
3924 }
3925
3926 if self.snippet_stack.pop().is_some() {
3927 return true;
3928 }
3929
3930 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3931 self.dismiss_diagnostics(cx);
3932 return true;
3933 }
3934
3935 false
3936 }
3937
3938 fn linked_editing_ranges_for(
3939 &self,
3940 selection: Range<text::Anchor>,
3941 cx: &App,
3942 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3943 if self.linked_edit_ranges.is_empty() {
3944 return None;
3945 }
3946 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3947 selection.end.buffer_id.and_then(|end_buffer_id| {
3948 if selection.start.buffer_id != Some(end_buffer_id) {
3949 return None;
3950 }
3951 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3952 let snapshot = buffer.read(cx).snapshot();
3953 self.linked_edit_ranges
3954 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3955 .map(|ranges| (ranges, snapshot, buffer))
3956 })?;
3957 use text::ToOffset as TO;
3958 // find offset from the start of current range to current cursor position
3959 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3960
3961 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3962 let start_difference = start_offset - start_byte_offset;
3963 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3964 let end_difference = end_offset - start_byte_offset;
3965 // Current range has associated linked ranges.
3966 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3967 for range in linked_ranges.iter() {
3968 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3969 let end_offset = start_offset + end_difference;
3970 let start_offset = start_offset + start_difference;
3971 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3972 continue;
3973 }
3974 if self.selections.disjoint_anchor_ranges().any(|s| {
3975 if s.start.buffer_id != selection.start.buffer_id
3976 || s.end.buffer_id != selection.end.buffer_id
3977 {
3978 return false;
3979 }
3980 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3981 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3982 }) {
3983 continue;
3984 }
3985 let start = buffer_snapshot.anchor_after(start_offset);
3986 let end = buffer_snapshot.anchor_after(end_offset);
3987 linked_edits
3988 .entry(buffer.clone())
3989 .or_default()
3990 .push(start..end);
3991 }
3992 Some(linked_edits)
3993 }
3994
3995 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3996 let text: Arc<str> = text.into();
3997
3998 if self.read_only(cx) {
3999 return;
4000 }
4001
4002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4003
4004 let selections = self.selections.all_adjusted(cx);
4005 let mut bracket_inserted = false;
4006 let mut edits = Vec::new();
4007 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4008 let mut new_selections = Vec::with_capacity(selections.len());
4009 let mut new_autoclose_regions = Vec::new();
4010 let snapshot = self.buffer.read(cx).read(cx);
4011 let mut clear_linked_edit_ranges = false;
4012
4013 for (selection, autoclose_region) in
4014 self.selections_with_autoclose_regions(selections, &snapshot)
4015 {
4016 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4017 // Determine if the inserted text matches the opening or closing
4018 // bracket of any of this language's bracket pairs.
4019 let mut bracket_pair = None;
4020 let mut is_bracket_pair_start = false;
4021 let mut is_bracket_pair_end = false;
4022 if !text.is_empty() {
4023 let mut bracket_pair_matching_end = None;
4024 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4025 // and they are removing the character that triggered IME popup.
4026 for (pair, enabled) in scope.brackets() {
4027 if !pair.close && !pair.surround {
4028 continue;
4029 }
4030
4031 if enabled && pair.start.ends_with(text.as_ref()) {
4032 let prefix_len = pair.start.len() - text.len();
4033 let preceding_text_matches_prefix = prefix_len == 0
4034 || (selection.start.column >= (prefix_len as u32)
4035 && snapshot.contains_str_at(
4036 Point::new(
4037 selection.start.row,
4038 selection.start.column - (prefix_len as u32),
4039 ),
4040 &pair.start[..prefix_len],
4041 ));
4042 if preceding_text_matches_prefix {
4043 bracket_pair = Some(pair.clone());
4044 is_bracket_pair_start = true;
4045 break;
4046 }
4047 }
4048 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4049 {
4050 // take first bracket pair matching end, but don't break in case a later bracket
4051 // pair matches start
4052 bracket_pair_matching_end = Some(pair.clone());
4053 }
4054 }
4055 if let Some(end) = bracket_pair_matching_end
4056 && bracket_pair.is_none()
4057 {
4058 bracket_pair = Some(end);
4059 is_bracket_pair_end = true;
4060 }
4061 }
4062
4063 if let Some(bracket_pair) = bracket_pair {
4064 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4065 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4066 let auto_surround =
4067 self.use_auto_surround && snapshot_settings.use_auto_surround;
4068 if selection.is_empty() {
4069 if is_bracket_pair_start {
4070 // If the inserted text is a suffix of an opening bracket and the
4071 // selection is preceded by the rest of the opening bracket, then
4072 // insert the closing bracket.
4073 let following_text_allows_autoclose = snapshot
4074 .chars_at(selection.start)
4075 .next()
4076 .is_none_or(|c| scope.should_autoclose_before(c));
4077
4078 let preceding_text_allows_autoclose = selection.start.column == 0
4079 || snapshot
4080 .reversed_chars_at(selection.start)
4081 .next()
4082 .is_none_or(|c| {
4083 bracket_pair.start != bracket_pair.end
4084 || !snapshot
4085 .char_classifier_at(selection.start)
4086 .is_word(c)
4087 });
4088
4089 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4090 && bracket_pair.start.len() == 1
4091 {
4092 let target = bracket_pair.start.chars().next().unwrap();
4093 let current_line_count = snapshot
4094 .reversed_chars_at(selection.start)
4095 .take_while(|&c| c != '\n')
4096 .filter(|&c| c == target)
4097 .count();
4098 current_line_count % 2 == 1
4099 } else {
4100 false
4101 };
4102
4103 if autoclose
4104 && bracket_pair.close
4105 && following_text_allows_autoclose
4106 && preceding_text_allows_autoclose
4107 && !is_closing_quote
4108 {
4109 let anchor = snapshot.anchor_before(selection.end);
4110 new_selections.push((selection.map(|_| anchor), text.len()));
4111 new_autoclose_regions.push((
4112 anchor,
4113 text.len(),
4114 selection.id,
4115 bracket_pair.clone(),
4116 ));
4117 edits.push((
4118 selection.range(),
4119 format!("{}{}", text, bracket_pair.end).into(),
4120 ));
4121 bracket_inserted = true;
4122 continue;
4123 }
4124 }
4125
4126 if let Some(region) = autoclose_region {
4127 // If the selection is followed by an auto-inserted closing bracket,
4128 // then don't insert that closing bracket again; just move the selection
4129 // past the closing bracket.
4130 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4131 && text.as_ref() == region.pair.end.as_str()
4132 && snapshot.contains_str_at(region.range.end, text.as_ref());
4133 if should_skip {
4134 let anchor = snapshot.anchor_after(selection.end);
4135 new_selections
4136 .push((selection.map(|_| anchor), region.pair.end.len()));
4137 continue;
4138 }
4139 }
4140
4141 let always_treat_brackets_as_autoclosed = snapshot
4142 .language_settings_at(selection.start, cx)
4143 .always_treat_brackets_as_autoclosed;
4144 if always_treat_brackets_as_autoclosed
4145 && is_bracket_pair_end
4146 && snapshot.contains_str_at(selection.end, text.as_ref())
4147 {
4148 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4149 // and the inserted text is a closing bracket and the selection is followed
4150 // by the closing bracket then move the selection past the closing bracket.
4151 let anchor = snapshot.anchor_after(selection.end);
4152 new_selections.push((selection.map(|_| anchor), text.len()));
4153 continue;
4154 }
4155 }
4156 // If an opening bracket is 1 character long and is typed while
4157 // text is selected, then surround that text with the bracket pair.
4158 else if auto_surround
4159 && bracket_pair.surround
4160 && is_bracket_pair_start
4161 && bracket_pair.start.chars().count() == 1
4162 {
4163 edits.push((selection.start..selection.start, text.clone()));
4164 edits.push((
4165 selection.end..selection.end,
4166 bracket_pair.end.as_str().into(),
4167 ));
4168 bracket_inserted = true;
4169 new_selections.push((
4170 Selection {
4171 id: selection.id,
4172 start: snapshot.anchor_after(selection.start),
4173 end: snapshot.anchor_before(selection.end),
4174 reversed: selection.reversed,
4175 goal: selection.goal,
4176 },
4177 0,
4178 ));
4179 continue;
4180 }
4181 }
4182 }
4183
4184 if self.auto_replace_emoji_shortcode
4185 && selection.is_empty()
4186 && text.as_ref().ends_with(':')
4187 && let Some(possible_emoji_short_code) =
4188 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4189 && !possible_emoji_short_code.is_empty()
4190 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4191 {
4192 let emoji_shortcode_start = Point::new(
4193 selection.start.row,
4194 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4195 );
4196
4197 // Remove shortcode from buffer
4198 edits.push((
4199 emoji_shortcode_start..selection.start,
4200 "".to_string().into(),
4201 ));
4202 new_selections.push((
4203 Selection {
4204 id: selection.id,
4205 start: snapshot.anchor_after(emoji_shortcode_start),
4206 end: snapshot.anchor_before(selection.start),
4207 reversed: selection.reversed,
4208 goal: selection.goal,
4209 },
4210 0,
4211 ));
4212
4213 // Insert emoji
4214 let selection_start_anchor = snapshot.anchor_after(selection.start);
4215 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4216 edits.push((selection.start..selection.end, emoji.to_string().into()));
4217
4218 continue;
4219 }
4220
4221 // If not handling any auto-close operation, then just replace the selected
4222 // text with the given input and move the selection to the end of the
4223 // newly inserted text.
4224 let anchor = snapshot.anchor_after(selection.end);
4225 if !self.linked_edit_ranges.is_empty() {
4226 let start_anchor = snapshot.anchor_before(selection.start);
4227
4228 let is_word_char = text.chars().next().is_none_or(|char| {
4229 let classifier = snapshot
4230 .char_classifier_at(start_anchor.to_offset(&snapshot))
4231 .ignore_punctuation(true);
4232 classifier.is_word(char)
4233 });
4234
4235 if is_word_char {
4236 if let Some(ranges) = self
4237 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4238 {
4239 for (buffer, edits) in ranges {
4240 linked_edits
4241 .entry(buffer.clone())
4242 .or_default()
4243 .extend(edits.into_iter().map(|range| (range, text.clone())));
4244 }
4245 }
4246 } else {
4247 clear_linked_edit_ranges = true;
4248 }
4249 }
4250
4251 new_selections.push((selection.map(|_| anchor), 0));
4252 edits.push((selection.start..selection.end, text.clone()));
4253 }
4254
4255 drop(snapshot);
4256
4257 self.transact(window, cx, |this, window, cx| {
4258 if clear_linked_edit_ranges {
4259 this.linked_edit_ranges.clear();
4260 }
4261 let initial_buffer_versions =
4262 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4263
4264 this.buffer.update(cx, |buffer, cx| {
4265 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4266 });
4267 for (buffer, edits) in linked_edits {
4268 buffer.update(cx, |buffer, cx| {
4269 let snapshot = buffer.snapshot();
4270 let edits = edits
4271 .into_iter()
4272 .map(|(range, text)| {
4273 use text::ToPoint as TP;
4274 let end_point = TP::to_point(&range.end, &snapshot);
4275 let start_point = TP::to_point(&range.start, &snapshot);
4276 (start_point..end_point, text)
4277 })
4278 .sorted_by_key(|(range, _)| range.start);
4279 buffer.edit(edits, None, cx);
4280 })
4281 }
4282 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4283 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4284 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4285 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4286 .zip(new_selection_deltas)
4287 .map(|(selection, delta)| Selection {
4288 id: selection.id,
4289 start: selection.start + delta,
4290 end: selection.end + delta,
4291 reversed: selection.reversed,
4292 goal: SelectionGoal::None,
4293 })
4294 .collect::<Vec<_>>();
4295
4296 let mut i = 0;
4297 for (position, delta, selection_id, pair) in new_autoclose_regions {
4298 let position = position.to_offset(&map.buffer_snapshot) + delta;
4299 let start = map.buffer_snapshot.anchor_before(position);
4300 let end = map.buffer_snapshot.anchor_after(position);
4301 while let Some(existing_state) = this.autoclose_regions.get(i) {
4302 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4303 Ordering::Less => i += 1,
4304 Ordering::Greater => break,
4305 Ordering::Equal => {
4306 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4307 Ordering::Less => i += 1,
4308 Ordering::Equal => break,
4309 Ordering::Greater => break,
4310 }
4311 }
4312 }
4313 }
4314 this.autoclose_regions.insert(
4315 i,
4316 AutocloseRegion {
4317 selection_id,
4318 range: start..end,
4319 pair,
4320 },
4321 );
4322 }
4323
4324 let had_active_edit_prediction = this.has_active_edit_prediction();
4325 this.change_selections(
4326 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4327 window,
4328 cx,
4329 |s| s.select(new_selections),
4330 );
4331
4332 if !bracket_inserted
4333 && let Some(on_type_format_task) =
4334 this.trigger_on_type_formatting(text.to_string(), window, cx)
4335 {
4336 on_type_format_task.detach_and_log_err(cx);
4337 }
4338
4339 let editor_settings = EditorSettings::get_global(cx);
4340 if bracket_inserted
4341 && (editor_settings.auto_signature_help
4342 || editor_settings.show_signature_help_after_edits)
4343 {
4344 this.show_signature_help(&ShowSignatureHelp, window, cx);
4345 }
4346
4347 let trigger_in_words =
4348 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4349 if this.hard_wrap.is_some() {
4350 let latest: Range<Point> = this.selections.newest(cx).range();
4351 if latest.is_empty()
4352 && this
4353 .buffer()
4354 .read(cx)
4355 .snapshot(cx)
4356 .line_len(MultiBufferRow(latest.start.row))
4357 == latest.start.column
4358 {
4359 this.rewrap_impl(
4360 RewrapOptions {
4361 override_language_settings: true,
4362 preserve_existing_whitespace: true,
4363 },
4364 cx,
4365 )
4366 }
4367 }
4368 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4369 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4370 this.refresh_edit_prediction(true, false, window, cx);
4371 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4372 });
4373 }
4374
4375 fn find_possible_emoji_shortcode_at_position(
4376 snapshot: &MultiBufferSnapshot,
4377 position: Point,
4378 ) -> Option<String> {
4379 let mut chars = Vec::new();
4380 let mut found_colon = false;
4381 for char in snapshot.reversed_chars_at(position).take(100) {
4382 // Found a possible emoji shortcode in the middle of the buffer
4383 if found_colon {
4384 if char.is_whitespace() {
4385 chars.reverse();
4386 return Some(chars.iter().collect());
4387 }
4388 // If the previous character is not a whitespace, we are in the middle of a word
4389 // and we only want to complete the shortcode if the word is made up of other emojis
4390 let mut containing_word = String::new();
4391 for ch in snapshot
4392 .reversed_chars_at(position)
4393 .skip(chars.len() + 1)
4394 .take(100)
4395 {
4396 if ch.is_whitespace() {
4397 break;
4398 }
4399 containing_word.push(ch);
4400 }
4401 let containing_word = containing_word.chars().rev().collect::<String>();
4402 if util::word_consists_of_emojis(containing_word.as_str()) {
4403 chars.reverse();
4404 return Some(chars.iter().collect());
4405 }
4406 }
4407
4408 if char.is_whitespace() || !char.is_ascii() {
4409 return None;
4410 }
4411 if char == ':' {
4412 found_colon = true;
4413 } else {
4414 chars.push(char);
4415 }
4416 }
4417 // Found a possible emoji shortcode at the beginning of the buffer
4418 chars.reverse();
4419 Some(chars.iter().collect())
4420 }
4421
4422 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4423 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4424 self.transact(window, cx, |this, window, cx| {
4425 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4426 let selections = this.selections.all::<usize>(cx);
4427 let multi_buffer = this.buffer.read(cx);
4428 let buffer = multi_buffer.snapshot(cx);
4429 selections
4430 .iter()
4431 .map(|selection| {
4432 let start_point = selection.start.to_point(&buffer);
4433 let mut existing_indent =
4434 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4435 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4436 let start = selection.start;
4437 let end = selection.end;
4438 let selection_is_empty = start == end;
4439 let language_scope = buffer.language_scope_at(start);
4440 let (
4441 comment_delimiter,
4442 doc_delimiter,
4443 insert_extra_newline,
4444 indent_on_newline,
4445 indent_on_extra_newline,
4446 ) = if let Some(language) = &language_scope {
4447 let mut insert_extra_newline =
4448 insert_extra_newline_brackets(&buffer, start..end, language)
4449 || insert_extra_newline_tree_sitter(&buffer, start..end);
4450
4451 // Comment extension on newline is allowed only for cursor selections
4452 let comment_delimiter = maybe!({
4453 if !selection_is_empty {
4454 return None;
4455 }
4456
4457 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4458 return None;
4459 }
4460
4461 let delimiters = language.line_comment_prefixes();
4462 let max_len_of_delimiter =
4463 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4464 let (snapshot, range) =
4465 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4466
4467 let num_of_whitespaces = snapshot
4468 .chars_for_range(range.clone())
4469 .take_while(|c| c.is_whitespace())
4470 .count();
4471 let comment_candidate = snapshot
4472 .chars_for_range(range.clone())
4473 .skip(num_of_whitespaces)
4474 .take(max_len_of_delimiter)
4475 .collect::<String>();
4476 let (delimiter, trimmed_len) = delimiters
4477 .iter()
4478 .filter_map(|delimiter| {
4479 let prefix = delimiter.trim_end();
4480 if comment_candidate.starts_with(prefix) {
4481 Some((delimiter, prefix.len()))
4482 } else {
4483 None
4484 }
4485 })
4486 .max_by_key(|(_, len)| *len)?;
4487
4488 if let Some(BlockCommentConfig {
4489 start: block_start, ..
4490 }) = language.block_comment()
4491 {
4492 let block_start_trimmed = block_start.trim_end();
4493 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4494 let line_content = snapshot
4495 .chars_for_range(range)
4496 .skip(num_of_whitespaces)
4497 .take(block_start_trimmed.len())
4498 .collect::<String>();
4499
4500 if line_content.starts_with(block_start_trimmed) {
4501 return None;
4502 }
4503 }
4504 }
4505
4506 let cursor_is_placed_after_comment_marker =
4507 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4508 if cursor_is_placed_after_comment_marker {
4509 Some(delimiter.clone())
4510 } else {
4511 None
4512 }
4513 });
4514
4515 let mut indent_on_newline = IndentSize::spaces(0);
4516 let mut indent_on_extra_newline = IndentSize::spaces(0);
4517
4518 let doc_delimiter = maybe!({
4519 if !selection_is_empty {
4520 return None;
4521 }
4522
4523 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4524 return None;
4525 }
4526
4527 let BlockCommentConfig {
4528 start: start_tag,
4529 end: end_tag,
4530 prefix: delimiter,
4531 tab_size: len,
4532 } = language.documentation_comment()?;
4533 let is_within_block_comment = buffer
4534 .language_scope_at(start_point)
4535 .is_some_and(|scope| scope.override_name() == Some("comment"));
4536 if !is_within_block_comment {
4537 return None;
4538 }
4539
4540 let (snapshot, range) =
4541 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4542
4543 let num_of_whitespaces = snapshot
4544 .chars_for_range(range.clone())
4545 .take_while(|c| c.is_whitespace())
4546 .count();
4547
4548 // 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.
4549 let column = start_point.column;
4550 let cursor_is_after_start_tag = {
4551 let start_tag_len = start_tag.len();
4552 let start_tag_line = snapshot
4553 .chars_for_range(range.clone())
4554 .skip(num_of_whitespaces)
4555 .take(start_tag_len)
4556 .collect::<String>();
4557 if start_tag_line.starts_with(start_tag.as_ref()) {
4558 num_of_whitespaces + start_tag_len <= column as usize
4559 } else {
4560 false
4561 }
4562 };
4563
4564 let cursor_is_after_delimiter = {
4565 let delimiter_trim = delimiter.trim_end();
4566 let delimiter_line = snapshot
4567 .chars_for_range(range.clone())
4568 .skip(num_of_whitespaces)
4569 .take(delimiter_trim.len())
4570 .collect::<String>();
4571 if delimiter_line.starts_with(delimiter_trim) {
4572 num_of_whitespaces + delimiter_trim.len() <= column as usize
4573 } else {
4574 false
4575 }
4576 };
4577
4578 let cursor_is_before_end_tag_if_exists = {
4579 let mut char_position = 0u32;
4580 let mut end_tag_offset = None;
4581
4582 'outer: for chunk in snapshot.text_for_range(range) {
4583 if let Some(byte_pos) = chunk.find(&**end_tag) {
4584 let chars_before_match =
4585 chunk[..byte_pos].chars().count() as u32;
4586 end_tag_offset =
4587 Some(char_position + chars_before_match);
4588 break 'outer;
4589 }
4590 char_position += chunk.chars().count() as u32;
4591 }
4592
4593 if let Some(end_tag_offset) = end_tag_offset {
4594 let cursor_is_before_end_tag = column <= end_tag_offset;
4595 if cursor_is_after_start_tag {
4596 if cursor_is_before_end_tag {
4597 insert_extra_newline = true;
4598 }
4599 let cursor_is_at_start_of_end_tag =
4600 column == end_tag_offset;
4601 if cursor_is_at_start_of_end_tag {
4602 indent_on_extra_newline.len = *len;
4603 }
4604 }
4605 cursor_is_before_end_tag
4606 } else {
4607 true
4608 }
4609 };
4610
4611 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4612 && cursor_is_before_end_tag_if_exists
4613 {
4614 if cursor_is_after_start_tag {
4615 indent_on_newline.len = *len;
4616 }
4617 Some(delimiter.clone())
4618 } else {
4619 None
4620 }
4621 });
4622
4623 (
4624 comment_delimiter,
4625 doc_delimiter,
4626 insert_extra_newline,
4627 indent_on_newline,
4628 indent_on_extra_newline,
4629 )
4630 } else {
4631 (
4632 None,
4633 None,
4634 false,
4635 IndentSize::default(),
4636 IndentSize::default(),
4637 )
4638 };
4639
4640 let prevent_auto_indent = doc_delimiter.is_some();
4641 let delimiter = comment_delimiter.or(doc_delimiter);
4642
4643 let capacity_for_delimiter =
4644 delimiter.as_deref().map(str::len).unwrap_or_default();
4645 let mut new_text = String::with_capacity(
4646 1 + capacity_for_delimiter
4647 + existing_indent.len as usize
4648 + indent_on_newline.len as usize
4649 + indent_on_extra_newline.len as usize,
4650 );
4651 new_text.push('\n');
4652 new_text.extend(existing_indent.chars());
4653 new_text.extend(indent_on_newline.chars());
4654
4655 if let Some(delimiter) = &delimiter {
4656 new_text.push_str(delimiter);
4657 }
4658
4659 if insert_extra_newline {
4660 new_text.push('\n');
4661 new_text.extend(existing_indent.chars());
4662 new_text.extend(indent_on_extra_newline.chars());
4663 }
4664
4665 let anchor = buffer.anchor_after(end);
4666 let new_selection = selection.map(|_| anchor);
4667 (
4668 ((start..end, new_text), prevent_auto_indent),
4669 (insert_extra_newline, new_selection),
4670 )
4671 })
4672 .unzip()
4673 };
4674
4675 let mut auto_indent_edits = Vec::new();
4676 let mut edits = Vec::new();
4677 for (edit, prevent_auto_indent) in edits_with_flags {
4678 if prevent_auto_indent {
4679 edits.push(edit);
4680 } else {
4681 auto_indent_edits.push(edit);
4682 }
4683 }
4684 if !edits.is_empty() {
4685 this.edit(edits, cx);
4686 }
4687 if !auto_indent_edits.is_empty() {
4688 this.edit_with_autoindent(auto_indent_edits, cx);
4689 }
4690
4691 let buffer = this.buffer.read(cx).snapshot(cx);
4692 let new_selections = selection_info
4693 .into_iter()
4694 .map(|(extra_newline_inserted, new_selection)| {
4695 let mut cursor = new_selection.end.to_point(&buffer);
4696 if extra_newline_inserted {
4697 cursor.row -= 1;
4698 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4699 }
4700 new_selection.map(|_| cursor)
4701 })
4702 .collect();
4703
4704 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4705 this.refresh_edit_prediction(true, false, window, cx);
4706 });
4707 }
4708
4709 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4711
4712 let buffer = self.buffer.read(cx);
4713 let snapshot = buffer.snapshot(cx);
4714
4715 let mut edits = Vec::new();
4716 let mut rows = Vec::new();
4717
4718 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4719 let cursor = selection.head();
4720 let row = cursor.row;
4721
4722 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4723
4724 let newline = "\n".to_string();
4725 edits.push((start_of_line..start_of_line, newline));
4726
4727 rows.push(row + rows_inserted as u32);
4728 }
4729
4730 self.transact(window, cx, |editor, window, cx| {
4731 editor.edit(edits, cx);
4732
4733 editor.change_selections(Default::default(), window, cx, |s| {
4734 let mut index = 0;
4735 s.move_cursors_with(|map, _, _| {
4736 let row = rows[index];
4737 index += 1;
4738
4739 let point = Point::new(row, 0);
4740 let boundary = map.next_line_boundary(point).1;
4741 let clipped = map.clip_point(boundary, Bias::Left);
4742
4743 (clipped, SelectionGoal::None)
4744 });
4745 });
4746
4747 let mut indent_edits = Vec::new();
4748 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4749 for row in rows {
4750 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4751 for (row, indent) in indents {
4752 if indent.len == 0 {
4753 continue;
4754 }
4755
4756 let text = match indent.kind {
4757 IndentKind::Space => " ".repeat(indent.len as usize),
4758 IndentKind::Tab => "\t".repeat(indent.len as usize),
4759 };
4760 let point = Point::new(row.0, 0);
4761 indent_edits.push((point..point, text));
4762 }
4763 }
4764 editor.edit(indent_edits, cx);
4765 });
4766 }
4767
4768 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4770
4771 let buffer = self.buffer.read(cx);
4772 let snapshot = buffer.snapshot(cx);
4773
4774 let mut edits = Vec::new();
4775 let mut rows = Vec::new();
4776 let mut rows_inserted = 0;
4777
4778 for selection in self.selections.all_adjusted(cx) {
4779 let cursor = selection.head();
4780 let row = cursor.row;
4781
4782 let point = Point::new(row + 1, 0);
4783 let start_of_line = snapshot.clip_point(point, Bias::Left);
4784
4785 let newline = "\n".to_string();
4786 edits.push((start_of_line..start_of_line, newline));
4787
4788 rows_inserted += 1;
4789 rows.push(row + rows_inserted);
4790 }
4791
4792 self.transact(window, cx, |editor, window, cx| {
4793 editor.edit(edits, cx);
4794
4795 editor.change_selections(Default::default(), window, cx, |s| {
4796 let mut index = 0;
4797 s.move_cursors_with(|map, _, _| {
4798 let row = rows[index];
4799 index += 1;
4800
4801 let point = Point::new(row, 0);
4802 let boundary = map.next_line_boundary(point).1;
4803 let clipped = map.clip_point(boundary, Bias::Left);
4804
4805 (clipped, SelectionGoal::None)
4806 });
4807 });
4808
4809 let mut indent_edits = Vec::new();
4810 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4811 for row in rows {
4812 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4813 for (row, indent) in indents {
4814 if indent.len == 0 {
4815 continue;
4816 }
4817
4818 let text = match indent.kind {
4819 IndentKind::Space => " ".repeat(indent.len as usize),
4820 IndentKind::Tab => "\t".repeat(indent.len as usize),
4821 };
4822 let point = Point::new(row.0, 0);
4823 indent_edits.push((point..point, text));
4824 }
4825 }
4826 editor.edit(indent_edits, cx);
4827 });
4828 }
4829
4830 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4831 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4832 original_indent_columns: Vec::new(),
4833 });
4834 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4835 }
4836
4837 fn insert_with_autoindent_mode(
4838 &mut self,
4839 text: &str,
4840 autoindent_mode: Option<AutoindentMode>,
4841 window: &mut Window,
4842 cx: &mut Context<Self>,
4843 ) {
4844 if self.read_only(cx) {
4845 return;
4846 }
4847
4848 let text: Arc<str> = text.into();
4849 self.transact(window, cx, |this, window, cx| {
4850 let old_selections = this.selections.all_adjusted(cx);
4851 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4852 let anchors = {
4853 let snapshot = buffer.read(cx);
4854 old_selections
4855 .iter()
4856 .map(|s| {
4857 let anchor = snapshot.anchor_after(s.head());
4858 s.map(|_| anchor)
4859 })
4860 .collect::<Vec<_>>()
4861 };
4862 buffer.edit(
4863 old_selections
4864 .iter()
4865 .map(|s| (s.start..s.end, text.clone())),
4866 autoindent_mode,
4867 cx,
4868 );
4869 anchors
4870 });
4871
4872 this.change_selections(Default::default(), window, cx, |s| {
4873 s.select_anchors(selection_anchors);
4874 });
4875
4876 cx.notify();
4877 });
4878 }
4879
4880 fn trigger_completion_on_input(
4881 &mut self,
4882 text: &str,
4883 trigger_in_words: bool,
4884 window: &mut Window,
4885 cx: &mut Context<Self>,
4886 ) {
4887 let completions_source = self
4888 .context_menu
4889 .borrow()
4890 .as_ref()
4891 .and_then(|menu| match menu {
4892 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4893 CodeContextMenu::CodeActions(_) => None,
4894 });
4895
4896 match completions_source {
4897 Some(CompletionsMenuSource::Words { .. }) => {
4898 self.open_or_update_completions_menu(
4899 Some(CompletionsMenuSource::Words {
4900 ignore_threshold: false,
4901 }),
4902 None,
4903 window,
4904 cx,
4905 );
4906 }
4907 Some(CompletionsMenuSource::Normal)
4908 | Some(CompletionsMenuSource::SnippetChoices)
4909 | None
4910 if self.is_completion_trigger(
4911 text,
4912 trigger_in_words,
4913 completions_source.is_some(),
4914 cx,
4915 ) =>
4916 {
4917 self.show_completions(
4918 &ShowCompletions {
4919 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4920 },
4921 window,
4922 cx,
4923 )
4924 }
4925 _ => {
4926 self.hide_context_menu(window, cx);
4927 }
4928 }
4929 }
4930
4931 fn is_completion_trigger(
4932 &self,
4933 text: &str,
4934 trigger_in_words: bool,
4935 menu_is_open: bool,
4936 cx: &mut Context<Self>,
4937 ) -> bool {
4938 let position = self.selections.newest_anchor().head();
4939 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4940 return false;
4941 };
4942
4943 if let Some(completion_provider) = &self.completion_provider {
4944 completion_provider.is_completion_trigger(
4945 &buffer,
4946 position.text_anchor,
4947 text,
4948 trigger_in_words,
4949 menu_is_open,
4950 cx,
4951 )
4952 } else {
4953 false
4954 }
4955 }
4956
4957 /// If any empty selections is touching the start of its innermost containing autoclose
4958 /// region, expand it to select the brackets.
4959 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4960 let selections = self.selections.all::<usize>(cx);
4961 let buffer = self.buffer.read(cx).read(cx);
4962 let new_selections = self
4963 .selections_with_autoclose_regions(selections, &buffer)
4964 .map(|(mut selection, region)| {
4965 if !selection.is_empty() {
4966 return selection;
4967 }
4968
4969 if let Some(region) = region {
4970 let mut range = region.range.to_offset(&buffer);
4971 if selection.start == range.start && range.start >= region.pair.start.len() {
4972 range.start -= region.pair.start.len();
4973 if buffer.contains_str_at(range.start, ®ion.pair.start)
4974 && buffer.contains_str_at(range.end, ®ion.pair.end)
4975 {
4976 range.end += region.pair.end.len();
4977 selection.start = range.start;
4978 selection.end = range.end;
4979
4980 return selection;
4981 }
4982 }
4983 }
4984
4985 let always_treat_brackets_as_autoclosed = buffer
4986 .language_settings_at(selection.start, cx)
4987 .always_treat_brackets_as_autoclosed;
4988
4989 if !always_treat_brackets_as_autoclosed {
4990 return selection;
4991 }
4992
4993 if let Some(scope) = buffer.language_scope_at(selection.start) {
4994 for (pair, enabled) in scope.brackets() {
4995 if !enabled || !pair.close {
4996 continue;
4997 }
4998
4999 if buffer.contains_str_at(selection.start, &pair.end) {
5000 let pair_start_len = pair.start.len();
5001 if buffer.contains_str_at(
5002 selection.start.saturating_sub(pair_start_len),
5003 &pair.start,
5004 ) {
5005 selection.start -= pair_start_len;
5006 selection.end += pair.end.len();
5007
5008 return selection;
5009 }
5010 }
5011 }
5012 }
5013
5014 selection
5015 })
5016 .collect();
5017
5018 drop(buffer);
5019 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5020 selections.select(new_selections)
5021 });
5022 }
5023
5024 /// Iterate the given selections, and for each one, find the smallest surrounding
5025 /// autoclose region. This uses the ordering of the selections and the autoclose
5026 /// regions to avoid repeated comparisons.
5027 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5028 &'a self,
5029 selections: impl IntoIterator<Item = Selection<D>>,
5030 buffer: &'a MultiBufferSnapshot,
5031 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5032 let mut i = 0;
5033 let mut regions = self.autoclose_regions.as_slice();
5034 selections.into_iter().map(move |selection| {
5035 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5036
5037 let mut enclosing = None;
5038 while let Some(pair_state) = regions.get(i) {
5039 if pair_state.range.end.to_offset(buffer) < range.start {
5040 regions = ®ions[i + 1..];
5041 i = 0;
5042 } else if pair_state.range.start.to_offset(buffer) > range.end {
5043 break;
5044 } else {
5045 if pair_state.selection_id == selection.id {
5046 enclosing = Some(pair_state);
5047 }
5048 i += 1;
5049 }
5050 }
5051
5052 (selection, enclosing)
5053 })
5054 }
5055
5056 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5057 fn invalidate_autoclose_regions(
5058 &mut self,
5059 mut selections: &[Selection<Anchor>],
5060 buffer: &MultiBufferSnapshot,
5061 ) {
5062 self.autoclose_regions.retain(|state| {
5063 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5064 return false;
5065 }
5066
5067 let mut i = 0;
5068 while let Some(selection) = selections.get(i) {
5069 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5070 selections = &selections[1..];
5071 continue;
5072 }
5073 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5074 break;
5075 }
5076 if selection.id == state.selection_id {
5077 return true;
5078 } else {
5079 i += 1;
5080 }
5081 }
5082 false
5083 });
5084 }
5085
5086 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5087 let offset = position.to_offset(buffer);
5088 let (word_range, kind) = buffer.surrounding_word(offset, true);
5089 if offset > word_range.start && kind == Some(CharKind::Word) {
5090 Some(
5091 buffer
5092 .text_for_range(word_range.start..offset)
5093 .collect::<String>(),
5094 )
5095 } else {
5096 None
5097 }
5098 }
5099
5100 pub fn toggle_inline_values(
5101 &mut self,
5102 _: &ToggleInlineValues,
5103 _: &mut Window,
5104 cx: &mut Context<Self>,
5105 ) {
5106 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5107
5108 self.refresh_inline_values(cx);
5109 }
5110
5111 pub fn toggle_inlay_hints(
5112 &mut self,
5113 _: &ToggleInlayHints,
5114 _: &mut Window,
5115 cx: &mut Context<Self>,
5116 ) {
5117 self.refresh_inlay_hints(
5118 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5119 cx,
5120 );
5121 }
5122
5123 pub fn inlay_hints_enabled(&self) -> bool {
5124 self.inlay_hint_cache.enabled
5125 }
5126
5127 pub fn inline_values_enabled(&self) -> bool {
5128 self.inline_value_cache.enabled
5129 }
5130
5131 #[cfg(any(test, feature = "test-support"))]
5132 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5133 self.display_map
5134 .read(cx)
5135 .current_inlays()
5136 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5137 .cloned()
5138 .collect()
5139 }
5140
5141 #[cfg(any(test, feature = "test-support"))]
5142 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5143 self.display_map
5144 .read(cx)
5145 .current_inlays()
5146 .cloned()
5147 .collect()
5148 }
5149
5150 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5151 if self.semantics_provider.is_none() || !self.mode.is_full() {
5152 return;
5153 }
5154
5155 let reason_description = reason.description();
5156 let ignore_debounce = matches!(
5157 reason,
5158 InlayHintRefreshReason::SettingsChange(_)
5159 | InlayHintRefreshReason::Toggle(_)
5160 | InlayHintRefreshReason::ExcerptsRemoved(_)
5161 | InlayHintRefreshReason::ModifiersChanged(_)
5162 );
5163 let (invalidate_cache, required_languages) = match reason {
5164 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5165 match self.inlay_hint_cache.modifiers_override(enabled) {
5166 Some(enabled) => {
5167 if enabled {
5168 (InvalidationStrategy::RefreshRequested, None)
5169 } else {
5170 self.splice_inlays(
5171 &self
5172 .visible_inlay_hints(cx)
5173 .iter()
5174 .map(|inlay| inlay.id)
5175 .collect::<Vec<InlayId>>(),
5176 Vec::new(),
5177 cx,
5178 );
5179 return;
5180 }
5181 }
5182 None => return,
5183 }
5184 }
5185 InlayHintRefreshReason::Toggle(enabled) => {
5186 if self.inlay_hint_cache.toggle(enabled) {
5187 if enabled {
5188 (InvalidationStrategy::RefreshRequested, None)
5189 } else {
5190 self.splice_inlays(
5191 &self
5192 .visible_inlay_hints(cx)
5193 .iter()
5194 .map(|inlay| inlay.id)
5195 .collect::<Vec<InlayId>>(),
5196 Vec::new(),
5197 cx,
5198 );
5199 return;
5200 }
5201 } else {
5202 return;
5203 }
5204 }
5205 InlayHintRefreshReason::SettingsChange(new_settings) => {
5206 match self.inlay_hint_cache.update_settings(
5207 &self.buffer,
5208 new_settings,
5209 self.visible_inlay_hints(cx),
5210 cx,
5211 ) {
5212 ControlFlow::Break(Some(InlaySplice {
5213 to_remove,
5214 to_insert,
5215 })) => {
5216 self.splice_inlays(&to_remove, to_insert, cx);
5217 return;
5218 }
5219 ControlFlow::Break(None) => return,
5220 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5221 }
5222 }
5223 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5224 if let Some(InlaySplice {
5225 to_remove,
5226 to_insert,
5227 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5228 {
5229 self.splice_inlays(&to_remove, to_insert, cx);
5230 }
5231 self.display_map.update(cx, |display_map, _| {
5232 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5233 });
5234 return;
5235 }
5236 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5237 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5238 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5239 }
5240 InlayHintRefreshReason::RefreshRequested => {
5241 (InvalidationStrategy::RefreshRequested, None)
5242 }
5243 };
5244
5245 if let Some(InlaySplice {
5246 to_remove,
5247 to_insert,
5248 }) = self.inlay_hint_cache.spawn_hint_refresh(
5249 reason_description,
5250 self.visible_excerpts(required_languages.as_ref(), cx),
5251 invalidate_cache,
5252 ignore_debounce,
5253 cx,
5254 ) {
5255 self.splice_inlays(&to_remove, to_insert, cx);
5256 }
5257 }
5258
5259 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5260 self.display_map
5261 .read(cx)
5262 .current_inlays()
5263 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5264 .cloned()
5265 .collect()
5266 }
5267
5268 pub fn visible_excerpts(
5269 &self,
5270 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5271 cx: &mut Context<Editor>,
5272 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5273 let Some(project) = self.project() else {
5274 return HashMap::default();
5275 };
5276 let project = project.read(cx);
5277 let multi_buffer = self.buffer().read(cx);
5278 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5279 let multi_buffer_visible_start = self
5280 .scroll_manager
5281 .anchor()
5282 .anchor
5283 .to_point(&multi_buffer_snapshot);
5284 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5285 multi_buffer_visible_start
5286 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5287 Bias::Left,
5288 );
5289 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5290 multi_buffer_snapshot
5291 .range_to_buffer_ranges(multi_buffer_visible_range)
5292 .into_iter()
5293 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5294 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5295 let buffer_file = project::File::from_dyn(buffer.file())?;
5296 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5297 let worktree_entry = buffer_worktree
5298 .read(cx)
5299 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5300 if worktree_entry.is_ignored {
5301 return None;
5302 }
5303
5304 let language = buffer.language()?;
5305 if let Some(restrict_to_languages) = restrict_to_languages
5306 && !restrict_to_languages.contains(language)
5307 {
5308 return None;
5309 }
5310 Some((
5311 excerpt_id,
5312 (
5313 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5314 buffer.version().clone(),
5315 excerpt_visible_range,
5316 ),
5317 ))
5318 })
5319 .collect()
5320 }
5321
5322 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5323 TextLayoutDetails {
5324 text_system: window.text_system().clone(),
5325 editor_style: self.style.clone().unwrap(),
5326 rem_size: window.rem_size(),
5327 scroll_anchor: self.scroll_manager.anchor(),
5328 visible_rows: self.visible_line_count(),
5329 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5330 }
5331 }
5332
5333 pub fn splice_inlays(
5334 &self,
5335 to_remove: &[InlayId],
5336 to_insert: Vec<Inlay>,
5337 cx: &mut Context<Self>,
5338 ) {
5339 self.display_map.update(cx, |display_map, cx| {
5340 display_map.splice_inlays(to_remove, to_insert, cx)
5341 });
5342 cx.notify();
5343 }
5344
5345 fn trigger_on_type_formatting(
5346 &self,
5347 input: String,
5348 window: &mut Window,
5349 cx: &mut Context<Self>,
5350 ) -> Option<Task<Result<()>>> {
5351 if input.len() != 1 {
5352 return None;
5353 }
5354
5355 let project = self.project()?;
5356 let position = self.selections.newest_anchor().head();
5357 let (buffer, buffer_position) = self
5358 .buffer
5359 .read(cx)
5360 .text_anchor_for_position(position, cx)?;
5361
5362 let settings = language_settings::language_settings(
5363 buffer
5364 .read(cx)
5365 .language_at(buffer_position)
5366 .map(|l| l.name()),
5367 buffer.read(cx).file(),
5368 cx,
5369 );
5370 if !settings.use_on_type_format {
5371 return None;
5372 }
5373
5374 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5375 // hence we do LSP request & edit on host side only — add formats to host's history.
5376 let push_to_lsp_host_history = true;
5377 // If this is not the host, append its history with new edits.
5378 let push_to_client_history = project.read(cx).is_via_collab();
5379
5380 let on_type_formatting = project.update(cx, |project, cx| {
5381 project.on_type_format(
5382 buffer.clone(),
5383 buffer_position,
5384 input,
5385 push_to_lsp_host_history,
5386 cx,
5387 )
5388 });
5389 Some(cx.spawn_in(window, async move |editor, cx| {
5390 if let Some(transaction) = on_type_formatting.await? {
5391 if push_to_client_history {
5392 buffer
5393 .update(cx, |buffer, _| {
5394 buffer.push_transaction(transaction, Instant::now());
5395 buffer.finalize_last_transaction();
5396 })
5397 .ok();
5398 }
5399 editor.update(cx, |editor, cx| {
5400 editor.refresh_document_highlights(cx);
5401 })?;
5402 }
5403 Ok(())
5404 }))
5405 }
5406
5407 pub fn show_word_completions(
5408 &mut self,
5409 _: &ShowWordCompletions,
5410 window: &mut Window,
5411 cx: &mut Context<Self>,
5412 ) {
5413 self.open_or_update_completions_menu(
5414 Some(CompletionsMenuSource::Words {
5415 ignore_threshold: true,
5416 }),
5417 None,
5418 window,
5419 cx,
5420 );
5421 }
5422
5423 pub fn show_completions(
5424 &mut self,
5425 options: &ShowCompletions,
5426 window: &mut Window,
5427 cx: &mut Context<Self>,
5428 ) {
5429 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5430 }
5431
5432 fn open_or_update_completions_menu(
5433 &mut self,
5434 requested_source: Option<CompletionsMenuSource>,
5435 trigger: Option<&str>,
5436 window: &mut Window,
5437 cx: &mut Context<Self>,
5438 ) {
5439 if self.pending_rename.is_some() {
5440 return;
5441 }
5442
5443 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5444
5445 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5446 // inserted and selected. To handle that case, the start of the selection is used so that
5447 // the menu starts with all choices.
5448 let position = self
5449 .selections
5450 .newest_anchor()
5451 .start
5452 .bias_right(&multibuffer_snapshot);
5453 if position.diff_base_anchor.is_some() {
5454 return;
5455 }
5456 let (buffer, buffer_position) =
5457 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5458 output
5459 } else {
5460 return;
5461 };
5462 let buffer_snapshot = buffer.read(cx).snapshot();
5463
5464 let query: Option<Arc<String>> =
5465 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5466
5467 drop(multibuffer_snapshot);
5468
5469 let mut ignore_word_threshold = false;
5470 let provider = match requested_source {
5471 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5472 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5473 ignore_word_threshold = ignore_threshold;
5474 None
5475 }
5476 Some(CompletionsMenuSource::SnippetChoices) => {
5477 log::error!("bug: SnippetChoices requested_source is not handled");
5478 None
5479 }
5480 };
5481
5482 let sort_completions = provider
5483 .as_ref()
5484 .is_some_and(|provider| provider.sort_completions());
5485
5486 let filter_completions = provider
5487 .as_ref()
5488 .is_none_or(|provider| provider.filter_completions());
5489
5490 let trigger_kind = match trigger {
5491 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5492 CompletionTriggerKind::TRIGGER_CHARACTER
5493 }
5494 _ => CompletionTriggerKind::INVOKED,
5495 };
5496 let completion_context = CompletionContext {
5497 trigger_character: trigger.and_then(|trigger| {
5498 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5499 Some(String::from(trigger))
5500 } else {
5501 None
5502 }
5503 }),
5504 trigger_kind,
5505 };
5506
5507 // Hide the current completions menu when a trigger char is typed. Without this, cached
5508 // completions from before the trigger char may be reused (#32774). Snippet choices could
5509 // involve trigger chars, so this is skipped in that case.
5510 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5511 {
5512 let menu_is_open = matches!(
5513 self.context_menu.borrow().as_ref(),
5514 Some(CodeContextMenu::Completions(_))
5515 );
5516 if menu_is_open {
5517 self.hide_context_menu(window, cx);
5518 }
5519 }
5520
5521 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5522 if filter_completions {
5523 menu.filter(query.clone(), provider.clone(), window, cx);
5524 }
5525 // When `is_incomplete` is false, no need to re-query completions when the current query
5526 // is a suffix of the initial query.
5527 if !menu.is_incomplete {
5528 // If the new query is a suffix of the old query (typing more characters) and
5529 // the previous result was complete, the existing completions can be filtered.
5530 //
5531 // Note that this is always true for snippet completions.
5532 let query_matches = match (&menu.initial_query, &query) {
5533 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5534 (None, _) => true,
5535 _ => false,
5536 };
5537 if query_matches {
5538 let position_matches = if menu.initial_position == position {
5539 true
5540 } else {
5541 let snapshot = self.buffer.read(cx).read(cx);
5542 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5543 };
5544 if position_matches {
5545 return;
5546 }
5547 }
5548 }
5549 };
5550
5551 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5552 buffer_snapshot.surrounding_word(buffer_position, false)
5553 {
5554 let word_to_exclude = buffer_snapshot
5555 .text_for_range(word_range.clone())
5556 .collect::<String>();
5557 (
5558 buffer_snapshot.anchor_before(word_range.start)
5559 ..buffer_snapshot.anchor_after(buffer_position),
5560 Some(word_to_exclude),
5561 )
5562 } else {
5563 (buffer_position..buffer_position, None)
5564 };
5565
5566 let language = buffer_snapshot
5567 .language_at(buffer_position)
5568 .map(|language| language.name());
5569
5570 let completion_settings =
5571 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5572
5573 let show_completion_documentation = buffer_snapshot
5574 .settings_at(buffer_position, cx)
5575 .show_completion_documentation;
5576
5577 // The document can be large, so stay in reasonable bounds when searching for words,
5578 // otherwise completion pop-up might be slow to appear.
5579 const WORD_LOOKUP_ROWS: u32 = 5_000;
5580 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5581 let min_word_search = buffer_snapshot.clip_point(
5582 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5583 Bias::Left,
5584 );
5585 let max_word_search = buffer_snapshot.clip_point(
5586 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5587 Bias::Right,
5588 );
5589 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5590 ..buffer_snapshot.point_to_offset(max_word_search);
5591
5592 let skip_digits = query
5593 .as_ref()
5594 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5595
5596 let omit_word_completions = !self.word_completions_enabled
5597 || (!ignore_word_threshold
5598 && match &query {
5599 Some(query) => query.chars().count() < completion_settings.words_min_length,
5600 None => completion_settings.words_min_length != 0,
5601 });
5602
5603 let (mut words, provider_responses) = match &provider {
5604 Some(provider) => {
5605 let provider_responses = provider.completions(
5606 position.excerpt_id,
5607 &buffer,
5608 buffer_position,
5609 completion_context,
5610 window,
5611 cx,
5612 );
5613
5614 let words = match (omit_word_completions, completion_settings.words) {
5615 (true, _) | (_, WordsCompletionMode::Disabled) => {
5616 Task::ready(BTreeMap::default())
5617 }
5618 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5619 .background_spawn(async move {
5620 buffer_snapshot.words_in_range(WordsQuery {
5621 fuzzy_contents: None,
5622 range: word_search_range,
5623 skip_digits,
5624 })
5625 }),
5626 };
5627
5628 (words, provider_responses)
5629 }
5630 None => {
5631 let words = if omit_word_completions {
5632 Task::ready(BTreeMap::default())
5633 } else {
5634 cx.background_spawn(async move {
5635 buffer_snapshot.words_in_range(WordsQuery {
5636 fuzzy_contents: None,
5637 range: word_search_range,
5638 skip_digits,
5639 })
5640 })
5641 };
5642 (words, Task::ready(Ok(Vec::new())))
5643 }
5644 };
5645
5646 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5647
5648 let id = post_inc(&mut self.next_completion_id);
5649 let task = cx.spawn_in(window, async move |editor, cx| {
5650 let Ok(()) = editor.update(cx, |this, _| {
5651 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5652 }) else {
5653 return;
5654 };
5655
5656 // TODO: Ideally completions from different sources would be selectively re-queried, so
5657 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5658 let mut completions = Vec::new();
5659 let mut is_incomplete = false;
5660 let mut display_options: Option<CompletionDisplayOptions> = None;
5661 if let Some(provider_responses) = provider_responses.await.log_err()
5662 && !provider_responses.is_empty()
5663 {
5664 for response in provider_responses {
5665 completions.extend(response.completions);
5666 is_incomplete = is_incomplete || response.is_incomplete;
5667 match display_options.as_mut() {
5668 None => {
5669 display_options = Some(response.display_options);
5670 }
5671 Some(options) => options.merge(&response.display_options),
5672 }
5673 }
5674 if completion_settings.words == WordsCompletionMode::Fallback {
5675 words = Task::ready(BTreeMap::default());
5676 }
5677 }
5678 let display_options = display_options.unwrap_or_default();
5679
5680 let mut words = words.await;
5681 if let Some(word_to_exclude) = &word_to_exclude {
5682 words.remove(word_to_exclude);
5683 }
5684 for lsp_completion in &completions {
5685 words.remove(&lsp_completion.new_text);
5686 }
5687 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5688 replace_range: word_replace_range.clone(),
5689 new_text: word.clone(),
5690 label: CodeLabel::plain(word, None),
5691 icon_path: None,
5692 documentation: None,
5693 source: CompletionSource::BufferWord {
5694 word_range,
5695 resolved: false,
5696 },
5697 insert_text_mode: Some(InsertTextMode::AS_IS),
5698 confirm: None,
5699 }));
5700
5701 let menu = if completions.is_empty() {
5702 None
5703 } else {
5704 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5705 let languages = editor
5706 .workspace
5707 .as_ref()
5708 .and_then(|(workspace, _)| workspace.upgrade())
5709 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5710 let menu = CompletionsMenu::new(
5711 id,
5712 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5713 sort_completions,
5714 show_completion_documentation,
5715 position,
5716 query.clone(),
5717 is_incomplete,
5718 buffer.clone(),
5719 completions.into(),
5720 display_options,
5721 snippet_sort_order,
5722 languages,
5723 language,
5724 cx,
5725 );
5726
5727 let query = if filter_completions { query } else { None };
5728 let matches_task = if let Some(query) = query {
5729 menu.do_async_filtering(query, cx)
5730 } else {
5731 Task::ready(menu.unfiltered_matches())
5732 };
5733 (menu, matches_task)
5734 }) else {
5735 return;
5736 };
5737
5738 let matches = matches_task.await;
5739
5740 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5741 // Newer menu already set, so exit.
5742 if let Some(CodeContextMenu::Completions(prev_menu)) =
5743 editor.context_menu.borrow().as_ref()
5744 && prev_menu.id > id
5745 {
5746 return;
5747 };
5748
5749 // Only valid to take prev_menu because it the new menu is immediately set
5750 // below, or the menu is hidden.
5751 if let Some(CodeContextMenu::Completions(prev_menu)) =
5752 editor.context_menu.borrow_mut().take()
5753 {
5754 let position_matches =
5755 if prev_menu.initial_position == menu.initial_position {
5756 true
5757 } else {
5758 let snapshot = editor.buffer.read(cx).read(cx);
5759 prev_menu.initial_position.to_offset(&snapshot)
5760 == menu.initial_position.to_offset(&snapshot)
5761 };
5762 if position_matches {
5763 // Preserve markdown cache before `set_filter_results` because it will
5764 // try to populate the documentation cache.
5765 menu.preserve_markdown_cache(prev_menu);
5766 }
5767 };
5768
5769 menu.set_filter_results(matches, provider, window, cx);
5770 }) else {
5771 return;
5772 };
5773
5774 menu.visible().then_some(menu)
5775 };
5776
5777 editor
5778 .update_in(cx, |editor, window, cx| {
5779 if editor.focus_handle.is_focused(window)
5780 && let Some(menu) = menu
5781 {
5782 *editor.context_menu.borrow_mut() =
5783 Some(CodeContextMenu::Completions(menu));
5784
5785 crate::hover_popover::hide_hover(editor, cx);
5786 if editor.show_edit_predictions_in_menu() {
5787 editor.update_visible_edit_prediction(window, cx);
5788 } else {
5789 editor.discard_edit_prediction(false, cx);
5790 }
5791
5792 cx.notify();
5793 return;
5794 }
5795
5796 if editor.completion_tasks.len() <= 1 {
5797 // If there are no more completion tasks and the last menu was empty, we should hide it.
5798 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5799 // If it was already hidden and we don't show edit predictions in the menu,
5800 // we should also show the edit prediction when available.
5801 if was_hidden && editor.show_edit_predictions_in_menu() {
5802 editor.update_visible_edit_prediction(window, cx);
5803 }
5804 }
5805 })
5806 .ok();
5807 });
5808
5809 self.completion_tasks.push((id, task));
5810 }
5811
5812 #[cfg(feature = "test-support")]
5813 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5814 let menu = self.context_menu.borrow();
5815 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5816 let completions = menu.completions.borrow();
5817 Some(completions.to_vec())
5818 } else {
5819 None
5820 }
5821 }
5822
5823 pub fn with_completions_menu_matching_id<R>(
5824 &self,
5825 id: CompletionId,
5826 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5827 ) -> R {
5828 let mut context_menu = self.context_menu.borrow_mut();
5829 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5830 return f(None);
5831 };
5832 if completions_menu.id != id {
5833 return f(None);
5834 }
5835 f(Some(completions_menu))
5836 }
5837
5838 pub fn confirm_completion(
5839 &mut self,
5840 action: &ConfirmCompletion,
5841 window: &mut Window,
5842 cx: &mut Context<Self>,
5843 ) -> Option<Task<Result<()>>> {
5844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5845 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5846 }
5847
5848 pub fn confirm_completion_insert(
5849 &mut self,
5850 _: &ConfirmCompletionInsert,
5851 window: &mut Window,
5852 cx: &mut Context<Self>,
5853 ) -> Option<Task<Result<()>>> {
5854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5855 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5856 }
5857
5858 pub fn confirm_completion_replace(
5859 &mut self,
5860 _: &ConfirmCompletionReplace,
5861 window: &mut Window,
5862 cx: &mut Context<Self>,
5863 ) -> Option<Task<Result<()>>> {
5864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5865 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5866 }
5867
5868 pub fn compose_completion(
5869 &mut self,
5870 action: &ComposeCompletion,
5871 window: &mut Window,
5872 cx: &mut Context<Self>,
5873 ) -> Option<Task<Result<()>>> {
5874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5875 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5876 }
5877
5878 fn do_completion(
5879 &mut self,
5880 item_ix: Option<usize>,
5881 intent: CompletionIntent,
5882 window: &mut Window,
5883 cx: &mut Context<Editor>,
5884 ) -> Option<Task<Result<()>>> {
5885 use language::ToOffset as _;
5886
5887 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5888 else {
5889 return None;
5890 };
5891
5892 let candidate_id = {
5893 let entries = completions_menu.entries.borrow();
5894 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5895 if self.show_edit_predictions_in_menu() {
5896 self.discard_edit_prediction(true, cx);
5897 }
5898 mat.candidate_id
5899 };
5900
5901 let completion = completions_menu
5902 .completions
5903 .borrow()
5904 .get(candidate_id)?
5905 .clone();
5906 cx.stop_propagation();
5907
5908 let buffer_handle = completions_menu.buffer.clone();
5909
5910 let CompletionEdit {
5911 new_text,
5912 snippet,
5913 replace_range,
5914 } = process_completion_for_edit(
5915 &completion,
5916 intent,
5917 &buffer_handle,
5918 &completions_menu.initial_position.text_anchor,
5919 cx,
5920 );
5921
5922 let buffer = buffer_handle.read(cx);
5923 let snapshot = self.buffer.read(cx).snapshot(cx);
5924 let newest_anchor = self.selections.newest_anchor();
5925 let replace_range_multibuffer = {
5926 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5927 let multibuffer_anchor = snapshot
5928 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5929 .unwrap()
5930 ..snapshot
5931 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5932 .unwrap();
5933 multibuffer_anchor.start.to_offset(&snapshot)
5934 ..multibuffer_anchor.end.to_offset(&snapshot)
5935 };
5936 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5937 return None;
5938 }
5939
5940 let old_text = buffer
5941 .text_for_range(replace_range.clone())
5942 .collect::<String>();
5943 let lookbehind = newest_anchor
5944 .start
5945 .text_anchor
5946 .to_offset(buffer)
5947 .saturating_sub(replace_range.start);
5948 let lookahead = replace_range
5949 .end
5950 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5951 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5952 let suffix = &old_text[lookbehind.min(old_text.len())..];
5953
5954 let selections = self.selections.all::<usize>(cx);
5955 let mut ranges = Vec::new();
5956 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5957
5958 for selection in &selections {
5959 let range = if selection.id == newest_anchor.id {
5960 replace_range_multibuffer.clone()
5961 } else {
5962 let mut range = selection.range();
5963
5964 // if prefix is present, don't duplicate it
5965 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5966 range.start = range.start.saturating_sub(lookbehind);
5967
5968 // if suffix is also present, mimic the newest cursor and replace it
5969 if selection.id != newest_anchor.id
5970 && snapshot.contains_str_at(range.end, suffix)
5971 {
5972 range.end += lookahead;
5973 }
5974 }
5975 range
5976 };
5977
5978 ranges.push(range.clone());
5979
5980 if !self.linked_edit_ranges.is_empty() {
5981 let start_anchor = snapshot.anchor_before(range.start);
5982 let end_anchor = snapshot.anchor_after(range.end);
5983 if let Some(ranges) = self
5984 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5985 {
5986 for (buffer, edits) in ranges {
5987 linked_edits
5988 .entry(buffer.clone())
5989 .or_default()
5990 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5991 }
5992 }
5993 }
5994 }
5995
5996 let common_prefix_len = old_text
5997 .chars()
5998 .zip(new_text.chars())
5999 .take_while(|(a, b)| a == b)
6000 .map(|(a, _)| a.len_utf8())
6001 .sum::<usize>();
6002
6003 cx.emit(EditorEvent::InputHandled {
6004 utf16_range_to_replace: None,
6005 text: new_text[common_prefix_len..].into(),
6006 });
6007
6008 self.transact(window, cx, |editor, window, cx| {
6009 if let Some(mut snippet) = snippet {
6010 snippet.text = new_text.to_string();
6011 editor
6012 .insert_snippet(&ranges, snippet, window, cx)
6013 .log_err();
6014 } else {
6015 editor.buffer.update(cx, |multi_buffer, cx| {
6016 let auto_indent = match completion.insert_text_mode {
6017 Some(InsertTextMode::AS_IS) => None,
6018 _ => editor.autoindent_mode.clone(),
6019 };
6020 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6021 multi_buffer.edit(edits, auto_indent, cx);
6022 });
6023 }
6024 for (buffer, edits) in linked_edits {
6025 buffer.update(cx, |buffer, cx| {
6026 let snapshot = buffer.snapshot();
6027 let edits = edits
6028 .into_iter()
6029 .map(|(range, text)| {
6030 use text::ToPoint as TP;
6031 let end_point = TP::to_point(&range.end, &snapshot);
6032 let start_point = TP::to_point(&range.start, &snapshot);
6033 (start_point..end_point, text)
6034 })
6035 .sorted_by_key(|(range, _)| range.start);
6036 buffer.edit(edits, None, cx);
6037 })
6038 }
6039
6040 editor.refresh_edit_prediction(true, false, window, cx);
6041 });
6042 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6043
6044 let show_new_completions_on_confirm = completion
6045 .confirm
6046 .as_ref()
6047 .is_some_and(|confirm| confirm(intent, window, cx));
6048 if show_new_completions_on_confirm {
6049 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6050 }
6051
6052 let provider = self.completion_provider.as_ref()?;
6053 drop(completion);
6054 let apply_edits = provider.apply_additional_edits_for_completion(
6055 buffer_handle,
6056 completions_menu.completions.clone(),
6057 candidate_id,
6058 true,
6059 cx,
6060 );
6061
6062 let editor_settings = EditorSettings::get_global(cx);
6063 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6064 // After the code completion is finished, users often want to know what signatures are needed.
6065 // so we should automatically call signature_help
6066 self.show_signature_help(&ShowSignatureHelp, window, cx);
6067 }
6068
6069 Some(cx.foreground_executor().spawn(async move {
6070 apply_edits.await?;
6071 Ok(())
6072 }))
6073 }
6074
6075 pub fn toggle_code_actions(
6076 &mut self,
6077 action: &ToggleCodeActions,
6078 window: &mut Window,
6079 cx: &mut Context<Self>,
6080 ) {
6081 let quick_launch = action.quick_launch;
6082 let mut context_menu = self.context_menu.borrow_mut();
6083 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6084 if code_actions.deployed_from == action.deployed_from {
6085 // Toggle if we're selecting the same one
6086 *context_menu = None;
6087 cx.notify();
6088 return;
6089 } else {
6090 // Otherwise, clear it and start a new one
6091 *context_menu = None;
6092 cx.notify();
6093 }
6094 }
6095 drop(context_menu);
6096 let snapshot = self.snapshot(window, cx);
6097 let deployed_from = action.deployed_from.clone();
6098 let action = action.clone();
6099 self.completion_tasks.clear();
6100 self.discard_edit_prediction(false, cx);
6101
6102 let multibuffer_point = match &action.deployed_from {
6103 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6104 DisplayPoint::new(*row, 0).to_point(&snapshot)
6105 }
6106 _ => self.selections.newest::<Point>(cx).head(),
6107 };
6108 let Some((buffer, buffer_row)) = snapshot
6109 .buffer_snapshot
6110 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6111 .and_then(|(buffer_snapshot, range)| {
6112 self.buffer()
6113 .read(cx)
6114 .buffer(buffer_snapshot.remote_id())
6115 .map(|buffer| (buffer, range.start.row))
6116 })
6117 else {
6118 return;
6119 };
6120 let buffer_id = buffer.read(cx).remote_id();
6121 let tasks = self
6122 .tasks
6123 .get(&(buffer_id, buffer_row))
6124 .map(|t| Arc::new(t.to_owned()));
6125
6126 if !self.focus_handle.is_focused(window) {
6127 return;
6128 }
6129 let project = self.project.clone();
6130
6131 let code_actions_task = match deployed_from {
6132 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6133 _ => self.code_actions(buffer_row, window, cx),
6134 };
6135
6136 let runnable_task = match deployed_from {
6137 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6138 _ => {
6139 let mut task_context_task = Task::ready(None);
6140 if let Some(tasks) = &tasks
6141 && let Some(project) = project
6142 {
6143 task_context_task =
6144 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6145 }
6146
6147 cx.spawn_in(window, {
6148 let buffer = buffer.clone();
6149 async move |editor, cx| {
6150 let task_context = task_context_task.await;
6151
6152 let resolved_tasks =
6153 tasks
6154 .zip(task_context.clone())
6155 .map(|(tasks, task_context)| ResolvedTasks {
6156 templates: tasks.resolve(&task_context).collect(),
6157 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6158 multibuffer_point.row,
6159 tasks.column,
6160 )),
6161 });
6162 let debug_scenarios = editor
6163 .update(cx, |editor, cx| {
6164 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6165 })?
6166 .await;
6167 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6168 }
6169 })
6170 }
6171 };
6172
6173 cx.spawn_in(window, async move |editor, cx| {
6174 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6175 let code_actions = code_actions_task.await;
6176 let spawn_straight_away = quick_launch
6177 && resolved_tasks
6178 .as_ref()
6179 .is_some_and(|tasks| tasks.templates.len() == 1)
6180 && code_actions
6181 .as_ref()
6182 .is_none_or(|actions| actions.is_empty())
6183 && debug_scenarios.is_empty();
6184
6185 editor.update_in(cx, |editor, window, cx| {
6186 crate::hover_popover::hide_hover(editor, cx);
6187 let actions = CodeActionContents::new(
6188 resolved_tasks,
6189 code_actions,
6190 debug_scenarios,
6191 task_context.unwrap_or_default(),
6192 );
6193
6194 // Don't show the menu if there are no actions available
6195 if actions.is_empty() {
6196 cx.notify();
6197 return Task::ready(Ok(()));
6198 }
6199
6200 *editor.context_menu.borrow_mut() =
6201 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6202 buffer,
6203 actions,
6204 selected_item: Default::default(),
6205 scroll_handle: UniformListScrollHandle::default(),
6206 deployed_from,
6207 }));
6208 cx.notify();
6209 if spawn_straight_away
6210 && let Some(task) = editor.confirm_code_action(
6211 &ConfirmCodeAction { item_ix: Some(0) },
6212 window,
6213 cx,
6214 )
6215 {
6216 return task;
6217 }
6218
6219 Task::ready(Ok(()))
6220 })
6221 })
6222 .detach_and_log_err(cx);
6223 }
6224
6225 fn debug_scenarios(
6226 &mut self,
6227 resolved_tasks: &Option<ResolvedTasks>,
6228 buffer: &Entity<Buffer>,
6229 cx: &mut App,
6230 ) -> Task<Vec<task::DebugScenario>> {
6231 maybe!({
6232 let project = self.project()?;
6233 let dap_store = project.read(cx).dap_store();
6234 let mut scenarios = vec![];
6235 let resolved_tasks = resolved_tasks.as_ref()?;
6236 let buffer = buffer.read(cx);
6237 let language = buffer.language()?;
6238 let file = buffer.file();
6239 let debug_adapter = language_settings(language.name().into(), file, cx)
6240 .debuggers
6241 .first()
6242 .map(SharedString::from)
6243 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6244
6245 dap_store.update(cx, |dap_store, cx| {
6246 for (_, task) in &resolved_tasks.templates {
6247 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6248 task.original_task().clone(),
6249 debug_adapter.clone().into(),
6250 task.display_label().to_owned().into(),
6251 cx,
6252 );
6253 scenarios.push(maybe_scenario);
6254 }
6255 });
6256 Some(cx.background_spawn(async move {
6257 futures::future::join_all(scenarios)
6258 .await
6259 .into_iter()
6260 .flatten()
6261 .collect::<Vec<_>>()
6262 }))
6263 })
6264 .unwrap_or_else(|| Task::ready(vec![]))
6265 }
6266
6267 fn code_actions(
6268 &mut self,
6269 buffer_row: u32,
6270 window: &mut Window,
6271 cx: &mut Context<Self>,
6272 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6273 let mut task = self.code_actions_task.take();
6274 cx.spawn_in(window, async move |editor, cx| {
6275 while let Some(prev_task) = task {
6276 prev_task.await.log_err();
6277 task = editor
6278 .update(cx, |this, _| this.code_actions_task.take())
6279 .ok()?;
6280 }
6281
6282 editor
6283 .update(cx, |editor, cx| {
6284 editor
6285 .available_code_actions
6286 .clone()
6287 .and_then(|(location, code_actions)| {
6288 let snapshot = location.buffer.read(cx).snapshot();
6289 let point_range = location.range.to_point(&snapshot);
6290 let point_range = point_range.start.row..=point_range.end.row;
6291 if point_range.contains(&buffer_row) {
6292 Some(code_actions)
6293 } else {
6294 None
6295 }
6296 })
6297 })
6298 .ok()
6299 .flatten()
6300 })
6301 }
6302
6303 pub fn confirm_code_action(
6304 &mut self,
6305 action: &ConfirmCodeAction,
6306 window: &mut Window,
6307 cx: &mut Context<Self>,
6308 ) -> Option<Task<Result<()>>> {
6309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6310
6311 let actions_menu =
6312 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6313 menu
6314 } else {
6315 return None;
6316 };
6317
6318 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6319 let action = actions_menu.actions.get(action_ix)?;
6320 let title = action.label();
6321 let buffer = actions_menu.buffer;
6322 let workspace = self.workspace()?;
6323
6324 match action {
6325 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6326 workspace.update(cx, |workspace, cx| {
6327 workspace.schedule_resolved_task(
6328 task_source_kind,
6329 resolved_task,
6330 false,
6331 window,
6332 cx,
6333 );
6334
6335 Some(Task::ready(Ok(())))
6336 })
6337 }
6338 CodeActionsItem::CodeAction {
6339 excerpt_id,
6340 action,
6341 provider,
6342 } => {
6343 let apply_code_action =
6344 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6345 let workspace = workspace.downgrade();
6346 Some(cx.spawn_in(window, async move |editor, cx| {
6347 let project_transaction = apply_code_action.await?;
6348 Self::open_project_transaction(
6349 &editor,
6350 workspace,
6351 project_transaction,
6352 title,
6353 cx,
6354 )
6355 .await
6356 }))
6357 }
6358 CodeActionsItem::DebugScenario(scenario) => {
6359 let context = actions_menu.actions.context;
6360
6361 workspace.update(cx, |workspace, cx| {
6362 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6363 workspace.start_debug_session(
6364 scenario,
6365 context,
6366 Some(buffer),
6367 None,
6368 window,
6369 cx,
6370 );
6371 });
6372 Some(Task::ready(Ok(())))
6373 }
6374 }
6375 }
6376
6377 pub async fn open_project_transaction(
6378 editor: &WeakEntity<Editor>,
6379 workspace: WeakEntity<Workspace>,
6380 transaction: ProjectTransaction,
6381 title: String,
6382 cx: &mut AsyncWindowContext,
6383 ) -> Result<()> {
6384 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6385 cx.update(|_, cx| {
6386 entries.sort_unstable_by_key(|(buffer, _)| {
6387 buffer.read(cx).file().map(|f| f.path().clone())
6388 });
6389 })?;
6390
6391 // If the project transaction's edits are all contained within this editor, then
6392 // avoid opening a new editor to display them.
6393
6394 if let Some((buffer, transaction)) = entries.first() {
6395 if entries.len() == 1 {
6396 let excerpt = editor.update(cx, |editor, cx| {
6397 editor
6398 .buffer()
6399 .read(cx)
6400 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6401 })?;
6402 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6403 && excerpted_buffer == *buffer
6404 {
6405 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6406 let excerpt_range = excerpt_range.to_offset(buffer);
6407 buffer
6408 .edited_ranges_for_transaction::<usize>(transaction)
6409 .all(|range| {
6410 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6411 })
6412 })?;
6413
6414 if all_edits_within_excerpt {
6415 return Ok(());
6416 }
6417 }
6418 }
6419 } else {
6420 return Ok(());
6421 }
6422
6423 let mut ranges_to_highlight = Vec::new();
6424 let excerpt_buffer = cx.new(|cx| {
6425 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6426 for (buffer_handle, transaction) in &entries {
6427 let edited_ranges = buffer_handle
6428 .read(cx)
6429 .edited_ranges_for_transaction::<Point>(transaction)
6430 .collect::<Vec<_>>();
6431 let (ranges, _) = multibuffer.set_excerpts_for_path(
6432 PathKey::for_buffer(buffer_handle, cx),
6433 buffer_handle.clone(),
6434 edited_ranges,
6435 multibuffer_context_lines(cx),
6436 cx,
6437 );
6438
6439 ranges_to_highlight.extend(ranges);
6440 }
6441 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6442 multibuffer
6443 })?;
6444
6445 workspace.update_in(cx, |workspace, window, cx| {
6446 let project = workspace.project().clone();
6447 let editor =
6448 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6449 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6450 editor.update(cx, |editor, cx| {
6451 editor.highlight_background::<Self>(
6452 &ranges_to_highlight,
6453 |theme| theme.colors().editor_highlighted_line_background,
6454 cx,
6455 );
6456 });
6457 })?;
6458
6459 Ok(())
6460 }
6461
6462 pub fn clear_code_action_providers(&mut self) {
6463 self.code_action_providers.clear();
6464 self.available_code_actions.take();
6465 }
6466
6467 pub fn add_code_action_provider(
6468 &mut self,
6469 provider: Rc<dyn CodeActionProvider>,
6470 window: &mut Window,
6471 cx: &mut Context<Self>,
6472 ) {
6473 if self
6474 .code_action_providers
6475 .iter()
6476 .any(|existing_provider| existing_provider.id() == provider.id())
6477 {
6478 return;
6479 }
6480
6481 self.code_action_providers.push(provider);
6482 self.refresh_code_actions(window, cx);
6483 }
6484
6485 pub fn remove_code_action_provider(
6486 &mut self,
6487 id: Arc<str>,
6488 window: &mut Window,
6489 cx: &mut Context<Self>,
6490 ) {
6491 self.code_action_providers
6492 .retain(|provider| provider.id() != id);
6493 self.refresh_code_actions(window, cx);
6494 }
6495
6496 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6497 !self.code_action_providers.is_empty()
6498 && EditorSettings::get_global(cx).toolbar.code_actions
6499 }
6500
6501 pub fn has_available_code_actions(&self) -> bool {
6502 self.available_code_actions
6503 .as_ref()
6504 .is_some_and(|(_, actions)| !actions.is_empty())
6505 }
6506
6507 fn render_inline_code_actions(
6508 &self,
6509 icon_size: ui::IconSize,
6510 display_row: DisplayRow,
6511 is_active: bool,
6512 cx: &mut Context<Self>,
6513 ) -> AnyElement {
6514 let show_tooltip = !self.context_menu_visible();
6515 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6516 .icon_size(icon_size)
6517 .shape(ui::IconButtonShape::Square)
6518 .icon_color(ui::Color::Hidden)
6519 .toggle_state(is_active)
6520 .when(show_tooltip, |this| {
6521 this.tooltip({
6522 let focus_handle = self.focus_handle.clone();
6523 move |window, cx| {
6524 Tooltip::for_action_in(
6525 "Toggle Code Actions",
6526 &ToggleCodeActions {
6527 deployed_from: None,
6528 quick_launch: false,
6529 },
6530 &focus_handle,
6531 window,
6532 cx,
6533 )
6534 }
6535 })
6536 })
6537 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6538 window.focus(&editor.focus_handle(cx));
6539 editor.toggle_code_actions(
6540 &crate::actions::ToggleCodeActions {
6541 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6542 display_row,
6543 )),
6544 quick_launch: false,
6545 },
6546 window,
6547 cx,
6548 );
6549 }))
6550 .into_any_element()
6551 }
6552
6553 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6554 &self.context_menu
6555 }
6556
6557 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6558 let newest_selection = self.selections.newest_anchor().clone();
6559 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6560 let buffer = self.buffer.read(cx);
6561 if newest_selection.head().diff_base_anchor.is_some() {
6562 return None;
6563 }
6564 let (start_buffer, start) =
6565 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6566 let (end_buffer, end) =
6567 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6568 if start_buffer != end_buffer {
6569 return None;
6570 }
6571
6572 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6573 cx.background_executor()
6574 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6575 .await;
6576
6577 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6578 let providers = this.code_action_providers.clone();
6579 let tasks = this
6580 .code_action_providers
6581 .iter()
6582 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6583 .collect::<Vec<_>>();
6584 (providers, tasks)
6585 })?;
6586
6587 let mut actions = Vec::new();
6588 for (provider, provider_actions) in
6589 providers.into_iter().zip(future::join_all(tasks).await)
6590 {
6591 if let Some(provider_actions) = provider_actions.log_err() {
6592 actions.extend(provider_actions.into_iter().map(|action| {
6593 AvailableCodeAction {
6594 excerpt_id: newest_selection.start.excerpt_id,
6595 action,
6596 provider: provider.clone(),
6597 }
6598 }));
6599 }
6600 }
6601
6602 this.update(cx, |this, cx| {
6603 this.available_code_actions = if actions.is_empty() {
6604 None
6605 } else {
6606 Some((
6607 Location {
6608 buffer: start_buffer,
6609 range: start..end,
6610 },
6611 actions.into(),
6612 ))
6613 };
6614 cx.notify();
6615 })
6616 }));
6617 None
6618 }
6619
6620 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6621 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6622 self.show_git_blame_inline = false;
6623
6624 self.show_git_blame_inline_delay_task =
6625 Some(cx.spawn_in(window, async move |this, cx| {
6626 cx.background_executor().timer(delay).await;
6627
6628 this.update(cx, |this, cx| {
6629 this.show_git_blame_inline = true;
6630 cx.notify();
6631 })
6632 .log_err();
6633 }));
6634 }
6635 }
6636
6637 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6638 let snapshot = self.snapshot(window, cx);
6639 let cursor = self.selections.newest::<Point>(cx).head();
6640 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6641 else {
6642 return;
6643 };
6644
6645 let Some(blame) = self.blame.as_ref() else {
6646 return;
6647 };
6648
6649 let row_info = RowInfo {
6650 buffer_id: Some(buffer.remote_id()),
6651 buffer_row: Some(point.row),
6652 ..Default::default()
6653 };
6654 let Some((buffer, blame_entry)) = blame
6655 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6656 .flatten()
6657 else {
6658 return;
6659 };
6660
6661 let anchor = self.selections.newest_anchor().head();
6662 let position = self.to_pixel_point(anchor, &snapshot, window);
6663 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6664 self.show_blame_popover(
6665 buffer,
6666 &blame_entry,
6667 position + last_bounds.origin,
6668 true,
6669 cx,
6670 );
6671 };
6672 }
6673
6674 fn show_blame_popover(
6675 &mut self,
6676 buffer: BufferId,
6677 blame_entry: &BlameEntry,
6678 position: gpui::Point<Pixels>,
6679 ignore_timeout: bool,
6680 cx: &mut Context<Self>,
6681 ) {
6682 if let Some(state) = &mut self.inline_blame_popover {
6683 state.hide_task.take();
6684 } else {
6685 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6686 let blame_entry = blame_entry.clone();
6687 let show_task = cx.spawn(async move |editor, cx| {
6688 if !ignore_timeout {
6689 cx.background_executor()
6690 .timer(std::time::Duration::from_millis(blame_popover_delay))
6691 .await;
6692 }
6693 editor
6694 .update(cx, |editor, cx| {
6695 editor.inline_blame_popover_show_task.take();
6696 let Some(blame) = editor.blame.as_ref() else {
6697 return;
6698 };
6699 let blame = blame.read(cx);
6700 let details = blame.details_for_entry(buffer, &blame_entry);
6701 let markdown = cx.new(|cx| {
6702 Markdown::new(
6703 details
6704 .as_ref()
6705 .map(|message| message.message.clone())
6706 .unwrap_or_default(),
6707 None,
6708 None,
6709 cx,
6710 )
6711 });
6712 editor.inline_blame_popover = Some(InlineBlamePopover {
6713 position,
6714 hide_task: None,
6715 popover_bounds: None,
6716 popover_state: InlineBlamePopoverState {
6717 scroll_handle: ScrollHandle::new(),
6718 commit_message: details,
6719 markdown,
6720 },
6721 keyboard_grace: ignore_timeout,
6722 });
6723 cx.notify();
6724 })
6725 .ok();
6726 });
6727 self.inline_blame_popover_show_task = Some(show_task);
6728 }
6729 }
6730
6731 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6732 self.inline_blame_popover_show_task.take();
6733 if let Some(state) = &mut self.inline_blame_popover {
6734 let hide_task = cx.spawn(async move |editor, cx| {
6735 cx.background_executor()
6736 .timer(std::time::Duration::from_millis(100))
6737 .await;
6738 editor
6739 .update(cx, |editor, cx| {
6740 editor.inline_blame_popover.take();
6741 cx.notify();
6742 })
6743 .ok();
6744 });
6745 state.hide_task = Some(hide_task);
6746 }
6747 }
6748
6749 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6750 if self.pending_rename.is_some() {
6751 return None;
6752 }
6753
6754 let provider = self.semantics_provider.clone()?;
6755 let buffer = self.buffer.read(cx);
6756 let newest_selection = self.selections.newest_anchor().clone();
6757 let cursor_position = newest_selection.head();
6758 let (cursor_buffer, cursor_buffer_position) =
6759 buffer.text_anchor_for_position(cursor_position, cx)?;
6760 let (tail_buffer, tail_buffer_position) =
6761 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6762 if cursor_buffer != tail_buffer {
6763 return None;
6764 }
6765
6766 let snapshot = cursor_buffer.read(cx).snapshot();
6767 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6768 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6769 if start_word_range != end_word_range {
6770 self.document_highlights_task.take();
6771 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6772 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6773 return None;
6774 }
6775
6776 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6777 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6778 cx.background_executor()
6779 .timer(Duration::from_millis(debounce))
6780 .await;
6781
6782 let highlights = if let Some(highlights) = cx
6783 .update(|cx| {
6784 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6785 })
6786 .ok()
6787 .flatten()
6788 {
6789 highlights.await.log_err()
6790 } else {
6791 None
6792 };
6793
6794 if let Some(highlights) = highlights {
6795 this.update(cx, |this, cx| {
6796 if this.pending_rename.is_some() {
6797 return;
6798 }
6799
6800 let buffer = this.buffer.read(cx);
6801 if buffer
6802 .text_anchor_for_position(cursor_position, cx)
6803 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6804 {
6805 return;
6806 }
6807
6808 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6809 let mut write_ranges = Vec::new();
6810 let mut read_ranges = Vec::new();
6811 for highlight in highlights {
6812 let buffer_id = cursor_buffer.read(cx).remote_id();
6813 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6814 {
6815 let start = highlight
6816 .range
6817 .start
6818 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6819 let end = highlight
6820 .range
6821 .end
6822 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6823 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6824 continue;
6825 }
6826
6827 let range = Anchor {
6828 buffer_id: Some(buffer_id),
6829 excerpt_id,
6830 text_anchor: start,
6831 diff_base_anchor: None,
6832 }..Anchor {
6833 buffer_id: Some(buffer_id),
6834 excerpt_id,
6835 text_anchor: end,
6836 diff_base_anchor: None,
6837 };
6838 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6839 write_ranges.push(range);
6840 } else {
6841 read_ranges.push(range);
6842 }
6843 }
6844 }
6845
6846 this.highlight_background::<DocumentHighlightRead>(
6847 &read_ranges,
6848 |theme| theme.colors().editor_document_highlight_read_background,
6849 cx,
6850 );
6851 this.highlight_background::<DocumentHighlightWrite>(
6852 &write_ranges,
6853 |theme| theme.colors().editor_document_highlight_write_background,
6854 cx,
6855 );
6856 cx.notify();
6857 })
6858 .log_err();
6859 }
6860 }));
6861 None
6862 }
6863
6864 fn prepare_highlight_query_from_selection(
6865 &mut self,
6866 cx: &mut Context<Editor>,
6867 ) -> Option<(String, Range<Anchor>)> {
6868 if matches!(self.mode, EditorMode::SingleLine) {
6869 return None;
6870 }
6871 if !EditorSettings::get_global(cx).selection_highlight {
6872 return None;
6873 }
6874 if self.selections.count() != 1 || self.selections.line_mode {
6875 return None;
6876 }
6877 let selection = self.selections.newest::<Point>(cx);
6878 if selection.is_empty() || selection.start.row != selection.end.row {
6879 return None;
6880 }
6881 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6882 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6883 let query = multi_buffer_snapshot
6884 .text_for_range(selection_anchor_range.clone())
6885 .collect::<String>();
6886 if query.trim().is_empty() {
6887 return None;
6888 }
6889 Some((query, selection_anchor_range))
6890 }
6891
6892 fn update_selection_occurrence_highlights(
6893 &mut self,
6894 query_text: String,
6895 query_range: Range<Anchor>,
6896 multi_buffer_range_to_query: Range<Point>,
6897 use_debounce: bool,
6898 window: &mut Window,
6899 cx: &mut Context<Editor>,
6900 ) -> Task<()> {
6901 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6902 cx.spawn_in(window, async move |editor, cx| {
6903 if use_debounce {
6904 cx.background_executor()
6905 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6906 .await;
6907 }
6908 let match_task = cx.background_spawn(async move {
6909 let buffer_ranges = multi_buffer_snapshot
6910 .range_to_buffer_ranges(multi_buffer_range_to_query)
6911 .into_iter()
6912 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6913 let mut match_ranges = Vec::new();
6914 let Ok(regex) = project::search::SearchQuery::text(
6915 query_text.clone(),
6916 false,
6917 false,
6918 false,
6919 Default::default(),
6920 Default::default(),
6921 false,
6922 None,
6923 ) else {
6924 return Vec::default();
6925 };
6926 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6927 match_ranges.extend(
6928 regex
6929 .search(buffer_snapshot, Some(search_range.clone()))
6930 .await
6931 .into_iter()
6932 .filter_map(|match_range| {
6933 let match_start = buffer_snapshot
6934 .anchor_after(search_range.start + match_range.start);
6935 let match_end = buffer_snapshot
6936 .anchor_before(search_range.start + match_range.end);
6937 let match_anchor_range = Anchor::range_in_buffer(
6938 excerpt_id,
6939 buffer_snapshot.remote_id(),
6940 match_start..match_end,
6941 );
6942 (match_anchor_range != query_range).then_some(match_anchor_range)
6943 }),
6944 );
6945 }
6946 match_ranges
6947 });
6948 let match_ranges = match_task.await;
6949 editor
6950 .update_in(cx, |editor, _, cx| {
6951 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6952 if !match_ranges.is_empty() {
6953 editor.highlight_background::<SelectedTextHighlight>(
6954 &match_ranges,
6955 |theme| theme.colors().editor_document_highlight_bracket_background,
6956 cx,
6957 )
6958 }
6959 })
6960 .log_err();
6961 })
6962 }
6963
6964 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6965 struct NewlineFold;
6966 let type_id = std::any::TypeId::of::<NewlineFold>();
6967 if !self.mode.is_single_line() {
6968 return;
6969 }
6970 let snapshot = self.snapshot(window, cx);
6971 if snapshot.buffer_snapshot.max_point().row == 0 {
6972 return;
6973 }
6974 let task = cx.background_spawn(async move {
6975 let new_newlines = snapshot
6976 .buffer_chars_at(0)
6977 .filter_map(|(c, i)| {
6978 if c == '\n' {
6979 Some(
6980 snapshot.buffer_snapshot.anchor_after(i)
6981 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6982 )
6983 } else {
6984 None
6985 }
6986 })
6987 .collect::<Vec<_>>();
6988 let existing_newlines = snapshot
6989 .folds_in_range(0..snapshot.buffer_snapshot.len())
6990 .filter_map(|fold| {
6991 if fold.placeholder.type_tag == Some(type_id) {
6992 Some(fold.range.start..fold.range.end)
6993 } else {
6994 None
6995 }
6996 })
6997 .collect::<Vec<_>>();
6998
6999 (new_newlines, existing_newlines)
7000 });
7001 self.folding_newlines = cx.spawn(async move |this, cx| {
7002 let (new_newlines, existing_newlines) = task.await;
7003 if new_newlines == existing_newlines {
7004 return;
7005 }
7006 let placeholder = FoldPlaceholder {
7007 render: Arc::new(move |_, _, cx| {
7008 div()
7009 .bg(cx.theme().status().hint_background)
7010 .border_b_1()
7011 .size_full()
7012 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7013 .border_color(cx.theme().status().hint)
7014 .child("\\n")
7015 .into_any()
7016 }),
7017 constrain_width: false,
7018 merge_adjacent: false,
7019 type_tag: Some(type_id),
7020 };
7021 let creases = new_newlines
7022 .into_iter()
7023 .map(|range| Crease::simple(range, placeholder.clone()))
7024 .collect();
7025 this.update(cx, |this, cx| {
7026 this.display_map.update(cx, |display_map, cx| {
7027 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7028 display_map.fold(creases, cx);
7029 });
7030 })
7031 .ok();
7032 });
7033 }
7034
7035 fn refresh_selected_text_highlights(
7036 &mut self,
7037 on_buffer_edit: bool,
7038 window: &mut Window,
7039 cx: &mut Context<Editor>,
7040 ) {
7041 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7042 else {
7043 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7044 self.quick_selection_highlight_task.take();
7045 self.debounced_selection_highlight_task.take();
7046 return;
7047 };
7048 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7049 if on_buffer_edit
7050 || self
7051 .quick_selection_highlight_task
7052 .as_ref()
7053 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7054 {
7055 let multi_buffer_visible_start = self
7056 .scroll_manager
7057 .anchor()
7058 .anchor
7059 .to_point(&multi_buffer_snapshot);
7060 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7061 multi_buffer_visible_start
7062 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7063 Bias::Left,
7064 );
7065 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7066 self.quick_selection_highlight_task = Some((
7067 query_range.clone(),
7068 self.update_selection_occurrence_highlights(
7069 query_text.clone(),
7070 query_range.clone(),
7071 multi_buffer_visible_range,
7072 false,
7073 window,
7074 cx,
7075 ),
7076 ));
7077 }
7078 if on_buffer_edit
7079 || self
7080 .debounced_selection_highlight_task
7081 .as_ref()
7082 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7083 {
7084 let multi_buffer_start = multi_buffer_snapshot
7085 .anchor_before(0)
7086 .to_point(&multi_buffer_snapshot);
7087 let multi_buffer_end = multi_buffer_snapshot
7088 .anchor_after(multi_buffer_snapshot.len())
7089 .to_point(&multi_buffer_snapshot);
7090 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7091 self.debounced_selection_highlight_task = Some((
7092 query_range.clone(),
7093 self.update_selection_occurrence_highlights(
7094 query_text,
7095 query_range,
7096 multi_buffer_full_range,
7097 true,
7098 window,
7099 cx,
7100 ),
7101 ));
7102 }
7103 }
7104
7105 pub fn refresh_edit_prediction(
7106 &mut self,
7107 debounce: bool,
7108 user_requested: bool,
7109 window: &mut Window,
7110 cx: &mut Context<Self>,
7111 ) -> Option<()> {
7112 if DisableAiSettings::get_global(cx).disable_ai {
7113 return None;
7114 }
7115
7116 let provider = self.edit_prediction_provider()?;
7117 let cursor = self.selections.newest_anchor().head();
7118 let (buffer, cursor_buffer_position) =
7119 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7120
7121 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7122 self.discard_edit_prediction(false, cx);
7123 return None;
7124 }
7125
7126 if !user_requested
7127 && (!self.should_show_edit_predictions()
7128 || !self.is_focused(window)
7129 || buffer.read(cx).is_empty())
7130 {
7131 self.discard_edit_prediction(false, cx);
7132 return None;
7133 }
7134
7135 self.update_visible_edit_prediction(window, cx);
7136 provider.refresh(
7137 self.project.clone(),
7138 buffer,
7139 cursor_buffer_position,
7140 debounce,
7141 cx,
7142 );
7143 Some(())
7144 }
7145
7146 fn show_edit_predictions_in_menu(&self) -> bool {
7147 match self.edit_prediction_settings {
7148 EditPredictionSettings::Disabled => false,
7149 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7150 }
7151 }
7152
7153 pub fn edit_predictions_enabled(&self) -> bool {
7154 match self.edit_prediction_settings {
7155 EditPredictionSettings::Disabled => false,
7156 EditPredictionSettings::Enabled { .. } => true,
7157 }
7158 }
7159
7160 fn edit_prediction_requires_modifier(&self) -> bool {
7161 match self.edit_prediction_settings {
7162 EditPredictionSettings::Disabled => false,
7163 EditPredictionSettings::Enabled {
7164 preview_requires_modifier,
7165 ..
7166 } => preview_requires_modifier,
7167 }
7168 }
7169
7170 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7171 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7172 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7173 self.discard_edit_prediction(false, cx);
7174 } else {
7175 let selection = self.selections.newest_anchor();
7176 let cursor = selection.head();
7177
7178 if let Some((buffer, cursor_buffer_position)) =
7179 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7180 {
7181 self.edit_prediction_settings =
7182 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7183 }
7184 }
7185 }
7186
7187 fn edit_prediction_settings_at_position(
7188 &self,
7189 buffer: &Entity<Buffer>,
7190 buffer_position: language::Anchor,
7191 cx: &App,
7192 ) -> EditPredictionSettings {
7193 if !self.mode.is_full()
7194 || !self.show_edit_predictions_override.unwrap_or(true)
7195 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7196 {
7197 return EditPredictionSettings::Disabled;
7198 }
7199
7200 let buffer = buffer.read(cx);
7201
7202 let file = buffer.file();
7203
7204 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7205 return EditPredictionSettings::Disabled;
7206 };
7207
7208 let by_provider = matches!(
7209 self.menu_edit_predictions_policy,
7210 MenuEditPredictionsPolicy::ByProvider
7211 );
7212
7213 let show_in_menu = by_provider
7214 && self
7215 .edit_prediction_provider
7216 .as_ref()
7217 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7218
7219 let preview_requires_modifier =
7220 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7221
7222 EditPredictionSettings::Enabled {
7223 show_in_menu,
7224 preview_requires_modifier,
7225 }
7226 }
7227
7228 fn should_show_edit_predictions(&self) -> bool {
7229 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7230 }
7231
7232 pub fn edit_prediction_preview_is_active(&self) -> bool {
7233 matches!(
7234 self.edit_prediction_preview,
7235 EditPredictionPreview::Active { .. }
7236 )
7237 }
7238
7239 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7240 let cursor = self.selections.newest_anchor().head();
7241 if let Some((buffer, cursor_position)) =
7242 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7243 {
7244 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7245 } else {
7246 false
7247 }
7248 }
7249
7250 pub fn supports_minimap(&self, cx: &App) -> bool {
7251 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7252 }
7253
7254 fn edit_predictions_enabled_in_buffer(
7255 &self,
7256 buffer: &Entity<Buffer>,
7257 buffer_position: language::Anchor,
7258 cx: &App,
7259 ) -> bool {
7260 maybe!({
7261 if self.read_only(cx) {
7262 return Some(false);
7263 }
7264 let provider = self.edit_prediction_provider()?;
7265 if !provider.is_enabled(buffer, buffer_position, cx) {
7266 return Some(false);
7267 }
7268 let buffer = buffer.read(cx);
7269 let Some(file) = buffer.file() else {
7270 return Some(true);
7271 };
7272 let settings = all_language_settings(Some(file), cx);
7273 Some(settings.edit_predictions_enabled_for_file(file, cx))
7274 })
7275 .unwrap_or(false)
7276 }
7277
7278 fn cycle_edit_prediction(
7279 &mut self,
7280 direction: Direction,
7281 window: &mut Window,
7282 cx: &mut Context<Self>,
7283 ) -> Option<()> {
7284 let provider = self.edit_prediction_provider()?;
7285 let cursor = self.selections.newest_anchor().head();
7286 let (buffer, cursor_buffer_position) =
7287 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7288 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7289 return None;
7290 }
7291
7292 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7293 self.update_visible_edit_prediction(window, cx);
7294
7295 Some(())
7296 }
7297
7298 pub fn show_edit_prediction(
7299 &mut self,
7300 _: &ShowEditPrediction,
7301 window: &mut Window,
7302 cx: &mut Context<Self>,
7303 ) {
7304 if !self.has_active_edit_prediction() {
7305 self.refresh_edit_prediction(false, true, window, cx);
7306 return;
7307 }
7308
7309 self.update_visible_edit_prediction(window, cx);
7310 }
7311
7312 pub fn display_cursor_names(
7313 &mut self,
7314 _: &DisplayCursorNames,
7315 window: &mut Window,
7316 cx: &mut Context<Self>,
7317 ) {
7318 self.show_cursor_names(window, cx);
7319 }
7320
7321 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7322 self.show_cursor_names = true;
7323 cx.notify();
7324 cx.spawn_in(window, async move |this, cx| {
7325 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7326 this.update(cx, |this, cx| {
7327 this.show_cursor_names = false;
7328 cx.notify()
7329 })
7330 .ok()
7331 })
7332 .detach();
7333 }
7334
7335 pub fn next_edit_prediction(
7336 &mut self,
7337 _: &NextEditPrediction,
7338 window: &mut Window,
7339 cx: &mut Context<Self>,
7340 ) {
7341 if self.has_active_edit_prediction() {
7342 self.cycle_edit_prediction(Direction::Next, window, cx);
7343 } else {
7344 let is_copilot_disabled = self
7345 .refresh_edit_prediction(false, true, window, cx)
7346 .is_none();
7347 if is_copilot_disabled {
7348 cx.propagate();
7349 }
7350 }
7351 }
7352
7353 pub fn previous_edit_prediction(
7354 &mut self,
7355 _: &PreviousEditPrediction,
7356 window: &mut Window,
7357 cx: &mut Context<Self>,
7358 ) {
7359 if self.has_active_edit_prediction() {
7360 self.cycle_edit_prediction(Direction::Prev, window, cx);
7361 } else {
7362 let is_copilot_disabled = self
7363 .refresh_edit_prediction(false, true, window, cx)
7364 .is_none();
7365 if is_copilot_disabled {
7366 cx.propagate();
7367 }
7368 }
7369 }
7370
7371 pub fn accept_edit_prediction(
7372 &mut self,
7373 _: &AcceptEditPrediction,
7374 window: &mut Window,
7375 cx: &mut Context<Self>,
7376 ) {
7377 if self.show_edit_predictions_in_menu() {
7378 self.hide_context_menu(window, cx);
7379 }
7380
7381 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7382 return;
7383 };
7384
7385 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7386
7387 match &active_edit_prediction.completion {
7388 EditPrediction::Move { target, .. } => {
7389 let target = *target;
7390
7391 if let Some(position_map) = &self.last_position_map {
7392 if position_map
7393 .visible_row_range
7394 .contains(&target.to_display_point(&position_map.snapshot).row())
7395 || !self.edit_prediction_requires_modifier()
7396 {
7397 self.unfold_ranges(&[target..target], true, false, cx);
7398 // Note that this is also done in vim's handler of the Tab action.
7399 self.change_selections(
7400 SelectionEffects::scroll(Autoscroll::newest()),
7401 window,
7402 cx,
7403 |selections| {
7404 selections.select_anchor_ranges([target..target]);
7405 },
7406 );
7407 self.clear_row_highlights::<EditPredictionPreview>();
7408
7409 self.edit_prediction_preview
7410 .set_previous_scroll_position(None);
7411 } else {
7412 self.edit_prediction_preview
7413 .set_previous_scroll_position(Some(
7414 position_map.snapshot.scroll_anchor,
7415 ));
7416
7417 self.highlight_rows::<EditPredictionPreview>(
7418 target..target,
7419 cx.theme().colors().editor_highlighted_line_background,
7420 RowHighlightOptions {
7421 autoscroll: true,
7422 ..Default::default()
7423 },
7424 cx,
7425 );
7426 self.request_autoscroll(Autoscroll::fit(), cx);
7427 }
7428 }
7429 }
7430 EditPrediction::Edit { edits, .. } => {
7431 if let Some(provider) = self.edit_prediction_provider() {
7432 provider.accept(cx);
7433 }
7434
7435 // Store the transaction ID and selections before applying the edit
7436 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7437
7438 let snapshot = self.buffer.read(cx).snapshot(cx);
7439 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7440
7441 self.buffer.update(cx, |buffer, cx| {
7442 buffer.edit(edits.iter().cloned(), None, cx)
7443 });
7444
7445 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7446 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7447 });
7448
7449 let selections = self.selections.disjoint_anchors();
7450 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7451 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7452 if has_new_transaction {
7453 self.selection_history
7454 .insert_transaction(transaction_id_now, selections);
7455 }
7456 }
7457
7458 self.update_visible_edit_prediction(window, cx);
7459 if self.active_edit_prediction.is_none() {
7460 self.refresh_edit_prediction(true, true, window, cx);
7461 }
7462
7463 cx.notify();
7464 }
7465 }
7466
7467 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7468 }
7469
7470 pub fn accept_partial_edit_prediction(
7471 &mut self,
7472 _: &AcceptPartialEditPrediction,
7473 window: &mut Window,
7474 cx: &mut Context<Self>,
7475 ) {
7476 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7477 return;
7478 };
7479 if self.selections.count() != 1 {
7480 return;
7481 }
7482
7483 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7484
7485 match &active_edit_prediction.completion {
7486 EditPrediction::Move { target, .. } => {
7487 let target = *target;
7488 self.change_selections(
7489 SelectionEffects::scroll(Autoscroll::newest()),
7490 window,
7491 cx,
7492 |selections| {
7493 selections.select_anchor_ranges([target..target]);
7494 },
7495 );
7496 }
7497 EditPrediction::Edit { edits, .. } => {
7498 // Find an insertion that starts at the cursor position.
7499 let snapshot = self.buffer.read(cx).snapshot(cx);
7500 let cursor_offset = self.selections.newest::<usize>(cx).head();
7501 let insertion = edits.iter().find_map(|(range, text)| {
7502 let range = range.to_offset(&snapshot);
7503 if range.is_empty() && range.start == cursor_offset {
7504 Some(text)
7505 } else {
7506 None
7507 }
7508 });
7509
7510 if let Some(text) = insertion {
7511 let mut partial_completion = text
7512 .chars()
7513 .by_ref()
7514 .take_while(|c| c.is_alphabetic())
7515 .collect::<String>();
7516 if partial_completion.is_empty() {
7517 partial_completion = text
7518 .chars()
7519 .by_ref()
7520 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7521 .collect::<String>();
7522 }
7523
7524 cx.emit(EditorEvent::InputHandled {
7525 utf16_range_to_replace: None,
7526 text: partial_completion.clone().into(),
7527 });
7528
7529 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7530
7531 self.refresh_edit_prediction(true, true, window, cx);
7532 cx.notify();
7533 } else {
7534 self.accept_edit_prediction(&Default::default(), window, cx);
7535 }
7536 }
7537 }
7538 }
7539
7540 fn discard_edit_prediction(
7541 &mut self,
7542 should_report_edit_prediction_event: bool,
7543 cx: &mut Context<Self>,
7544 ) -> bool {
7545 if should_report_edit_prediction_event {
7546 let completion_id = self
7547 .active_edit_prediction
7548 .as_ref()
7549 .and_then(|active_completion| active_completion.completion_id.clone());
7550
7551 self.report_edit_prediction_event(completion_id, false, cx);
7552 }
7553
7554 if let Some(provider) = self.edit_prediction_provider() {
7555 provider.discard(cx);
7556 }
7557
7558 self.take_active_edit_prediction(cx)
7559 }
7560
7561 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7562 let Some(provider) = self.edit_prediction_provider() else {
7563 return;
7564 };
7565
7566 let Some((_, buffer, _)) = self
7567 .buffer
7568 .read(cx)
7569 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7570 else {
7571 return;
7572 };
7573
7574 let extension = buffer
7575 .read(cx)
7576 .file()
7577 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7578
7579 let event_type = match accepted {
7580 true => "Edit Prediction Accepted",
7581 false => "Edit Prediction Discarded",
7582 };
7583 telemetry::event!(
7584 event_type,
7585 provider = provider.name(),
7586 prediction_id = id,
7587 suggestion_accepted = accepted,
7588 file_extension = extension,
7589 );
7590 }
7591
7592 pub fn has_active_edit_prediction(&self) -> bool {
7593 self.active_edit_prediction.is_some()
7594 }
7595
7596 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7597 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7598 return false;
7599 };
7600
7601 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7602 self.clear_highlights::<EditPredictionHighlight>(cx);
7603 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7604 true
7605 }
7606
7607 /// Returns true when we're displaying the edit prediction popover below the cursor
7608 /// like we are not previewing and the LSP autocomplete menu is visible
7609 /// or we are in `when_holding_modifier` mode.
7610 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7611 if self.edit_prediction_preview_is_active()
7612 || !self.show_edit_predictions_in_menu()
7613 || !self.edit_predictions_enabled()
7614 {
7615 return false;
7616 }
7617
7618 if self.has_visible_completions_menu() {
7619 return true;
7620 }
7621
7622 has_completion && self.edit_prediction_requires_modifier()
7623 }
7624
7625 fn handle_modifiers_changed(
7626 &mut self,
7627 modifiers: Modifiers,
7628 position_map: &PositionMap,
7629 window: &mut Window,
7630 cx: &mut Context<Self>,
7631 ) {
7632 if self.show_edit_predictions_in_menu() {
7633 self.update_edit_prediction_preview(&modifiers, window, cx);
7634 }
7635
7636 self.update_selection_mode(&modifiers, position_map, window, cx);
7637
7638 let mouse_position = window.mouse_position();
7639 if !position_map.text_hitbox.is_hovered(window) {
7640 return;
7641 }
7642
7643 self.update_hovered_link(
7644 position_map.point_for_position(mouse_position),
7645 &position_map.snapshot,
7646 modifiers,
7647 window,
7648 cx,
7649 )
7650 }
7651
7652 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7653 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7654 if invert {
7655 match multi_cursor_setting {
7656 MultiCursorModifier::Alt => modifiers.alt,
7657 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7658 }
7659 } else {
7660 match multi_cursor_setting {
7661 MultiCursorModifier::Alt => modifiers.secondary(),
7662 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7663 }
7664 }
7665 }
7666
7667 fn columnar_selection_mode(
7668 modifiers: &Modifiers,
7669 cx: &mut Context<Self>,
7670 ) -> Option<ColumnarMode> {
7671 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7672 if Self::multi_cursor_modifier(false, modifiers, cx) {
7673 Some(ColumnarMode::FromMouse)
7674 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7675 Some(ColumnarMode::FromSelection)
7676 } else {
7677 None
7678 }
7679 } else {
7680 None
7681 }
7682 }
7683
7684 fn update_selection_mode(
7685 &mut self,
7686 modifiers: &Modifiers,
7687 position_map: &PositionMap,
7688 window: &mut Window,
7689 cx: &mut Context<Self>,
7690 ) {
7691 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7692 return;
7693 };
7694 if self.selections.pending.is_none() {
7695 return;
7696 }
7697
7698 let mouse_position = window.mouse_position();
7699 let point_for_position = position_map.point_for_position(mouse_position);
7700 let position = point_for_position.previous_valid;
7701
7702 self.select(
7703 SelectPhase::BeginColumnar {
7704 position,
7705 reset: false,
7706 mode,
7707 goal_column: point_for_position.exact_unclipped.column(),
7708 },
7709 window,
7710 cx,
7711 );
7712 }
7713
7714 fn update_edit_prediction_preview(
7715 &mut self,
7716 modifiers: &Modifiers,
7717 window: &mut Window,
7718 cx: &mut Context<Self>,
7719 ) {
7720 let mut modifiers_held = false;
7721 if let Some(accept_keystroke) = self
7722 .accept_edit_prediction_keybind(false, window, cx)
7723 .keystroke()
7724 {
7725 modifiers_held = modifiers_held
7726 || (accept_keystroke.modifiers() == modifiers
7727 && accept_keystroke.modifiers().modified());
7728 };
7729 if let Some(accept_partial_keystroke) = self
7730 .accept_edit_prediction_keybind(true, window, cx)
7731 .keystroke()
7732 {
7733 modifiers_held = modifiers_held
7734 || (accept_partial_keystroke.modifiers() == modifiers
7735 && accept_partial_keystroke.modifiers().modified());
7736 }
7737
7738 if modifiers_held {
7739 if matches!(
7740 self.edit_prediction_preview,
7741 EditPredictionPreview::Inactive { .. }
7742 ) {
7743 self.edit_prediction_preview = EditPredictionPreview::Active {
7744 previous_scroll_position: None,
7745 since: Instant::now(),
7746 };
7747
7748 self.update_visible_edit_prediction(window, cx);
7749 cx.notify();
7750 }
7751 } else if let EditPredictionPreview::Active {
7752 previous_scroll_position,
7753 since,
7754 } = self.edit_prediction_preview
7755 {
7756 if let (Some(previous_scroll_position), Some(position_map)) =
7757 (previous_scroll_position, self.last_position_map.as_ref())
7758 {
7759 self.set_scroll_position(
7760 previous_scroll_position
7761 .scroll_position(&position_map.snapshot.display_snapshot),
7762 window,
7763 cx,
7764 );
7765 }
7766
7767 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7768 released_too_fast: since.elapsed() < Duration::from_millis(200),
7769 };
7770 self.clear_row_highlights::<EditPredictionPreview>();
7771 self.update_visible_edit_prediction(window, cx);
7772 cx.notify();
7773 }
7774 }
7775
7776 fn update_visible_edit_prediction(
7777 &mut self,
7778 _window: &mut Window,
7779 cx: &mut Context<Self>,
7780 ) -> Option<()> {
7781 if DisableAiSettings::get_global(cx).disable_ai {
7782 return None;
7783 }
7784
7785 if self.ime_transaction.is_some() {
7786 self.discard_edit_prediction(false, cx);
7787 return None;
7788 }
7789
7790 let selection = self.selections.newest_anchor();
7791 let cursor = selection.head();
7792 let multibuffer = self.buffer.read(cx).snapshot(cx);
7793 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7794 let excerpt_id = cursor.excerpt_id;
7795
7796 let show_in_menu = self.show_edit_predictions_in_menu();
7797 let completions_menu_has_precedence = !show_in_menu
7798 && (self.context_menu.borrow().is_some()
7799 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7800
7801 if completions_menu_has_precedence
7802 || !offset_selection.is_empty()
7803 || self
7804 .active_edit_prediction
7805 .as_ref()
7806 .is_some_and(|completion| {
7807 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7808 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7809 !invalidation_range.contains(&offset_selection.head())
7810 })
7811 {
7812 self.discard_edit_prediction(false, cx);
7813 return None;
7814 }
7815
7816 self.take_active_edit_prediction(cx);
7817 let Some(provider) = self.edit_prediction_provider() else {
7818 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7819 return None;
7820 };
7821
7822 let (buffer, cursor_buffer_position) =
7823 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7824
7825 self.edit_prediction_settings =
7826 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7827
7828 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7829 self.discard_edit_prediction(false, cx);
7830 return None;
7831 };
7832
7833 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7834
7835 if self.edit_prediction_indent_conflict {
7836 let cursor_point = cursor.to_point(&multibuffer);
7837
7838 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7839
7840 if let Some((_, indent)) = indents.iter().next()
7841 && indent.len == cursor_point.column
7842 {
7843 self.edit_prediction_indent_conflict = false;
7844 }
7845 }
7846
7847 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7848 let edits = edit_prediction
7849 .edits
7850 .into_iter()
7851 .flat_map(|(range, new_text)| {
7852 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7853 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7854 Some((start..end, new_text))
7855 })
7856 .collect::<Vec<_>>();
7857 if edits.is_empty() {
7858 return None;
7859 }
7860
7861 let first_edit_start = edits.first().unwrap().0.start;
7862 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7863 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7864
7865 let last_edit_end = edits.last().unwrap().0.end;
7866 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7867 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7868
7869 let cursor_row = cursor.to_point(&multibuffer).row;
7870
7871 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7872
7873 let mut inlay_ids = Vec::new();
7874 let invalidation_row_range;
7875 let move_invalidation_row_range = if cursor_row < edit_start_row {
7876 Some(cursor_row..edit_end_row)
7877 } else if cursor_row > edit_end_row {
7878 Some(edit_start_row..cursor_row)
7879 } else {
7880 None
7881 };
7882 let supports_jump = self
7883 .edit_prediction_provider
7884 .as_ref()
7885 .map(|provider| provider.provider.supports_jump_to_edit())
7886 .unwrap_or(true);
7887
7888 let is_move = supports_jump
7889 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7890 let completion = if is_move {
7891 invalidation_row_range =
7892 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7893 let target = first_edit_start;
7894 EditPrediction::Move { target, snapshot }
7895 } else {
7896 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7897 && !self.edit_predictions_hidden_for_vim_mode;
7898
7899 if show_completions_in_buffer {
7900 if edits
7901 .iter()
7902 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7903 {
7904 let mut inlays = Vec::new();
7905 for (range, new_text) in &edits {
7906 let inlay = Inlay::edit_prediction(
7907 post_inc(&mut self.next_inlay_id),
7908 range.start,
7909 new_text.as_str(),
7910 );
7911 inlay_ids.push(inlay.id);
7912 inlays.push(inlay);
7913 }
7914
7915 self.splice_inlays(&[], inlays, cx);
7916 } else {
7917 let background_color = cx.theme().status().deleted_background;
7918 self.highlight_text::<EditPredictionHighlight>(
7919 edits.iter().map(|(range, _)| range.clone()).collect(),
7920 HighlightStyle {
7921 background_color: Some(background_color),
7922 ..Default::default()
7923 },
7924 cx,
7925 );
7926 }
7927 }
7928
7929 invalidation_row_range = edit_start_row..edit_end_row;
7930
7931 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7932 if provider.show_tab_accept_marker() {
7933 EditDisplayMode::TabAccept
7934 } else {
7935 EditDisplayMode::Inline
7936 }
7937 } else {
7938 EditDisplayMode::DiffPopover
7939 };
7940
7941 EditPrediction::Edit {
7942 edits,
7943 edit_preview: edit_prediction.edit_preview,
7944 display_mode,
7945 snapshot,
7946 }
7947 };
7948
7949 let invalidation_range = multibuffer
7950 .anchor_before(Point::new(invalidation_row_range.start, 0))
7951 ..multibuffer.anchor_after(Point::new(
7952 invalidation_row_range.end,
7953 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7954 ));
7955
7956 self.stale_edit_prediction_in_menu = None;
7957 self.active_edit_prediction = Some(EditPredictionState {
7958 inlay_ids,
7959 completion,
7960 completion_id: edit_prediction.id,
7961 invalidation_range,
7962 });
7963
7964 cx.notify();
7965
7966 Some(())
7967 }
7968
7969 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7970 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7971 }
7972
7973 fn clear_tasks(&mut self) {
7974 self.tasks.clear()
7975 }
7976
7977 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7978 if self.tasks.insert(key, value).is_some() {
7979 // This case should hopefully be rare, but just in case...
7980 log::error!(
7981 "multiple different run targets found on a single line, only the last target will be rendered"
7982 )
7983 }
7984 }
7985
7986 /// Get all display points of breakpoints that will be rendered within editor
7987 ///
7988 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7989 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7990 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7991 fn active_breakpoints(
7992 &self,
7993 range: Range<DisplayRow>,
7994 window: &mut Window,
7995 cx: &mut Context<Self>,
7996 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7997 let mut breakpoint_display_points = HashMap::default();
7998
7999 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8000 return breakpoint_display_points;
8001 };
8002
8003 let snapshot = self.snapshot(window, cx);
8004
8005 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8006 let Some(project) = self.project() else {
8007 return breakpoint_display_points;
8008 };
8009
8010 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8011 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8012
8013 for (buffer_snapshot, range, excerpt_id) in
8014 multi_buffer_snapshot.range_to_buffer_ranges(range)
8015 {
8016 let Some(buffer) = project
8017 .read(cx)
8018 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8019 else {
8020 continue;
8021 };
8022 let breakpoints = breakpoint_store.read(cx).breakpoints(
8023 &buffer,
8024 Some(
8025 buffer_snapshot.anchor_before(range.start)
8026 ..buffer_snapshot.anchor_after(range.end),
8027 ),
8028 buffer_snapshot,
8029 cx,
8030 );
8031 for (breakpoint, state) in breakpoints {
8032 let multi_buffer_anchor =
8033 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8034 let position = multi_buffer_anchor
8035 .to_point(multi_buffer_snapshot)
8036 .to_display_point(&snapshot);
8037
8038 breakpoint_display_points.insert(
8039 position.row(),
8040 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8041 );
8042 }
8043 }
8044
8045 breakpoint_display_points
8046 }
8047
8048 fn breakpoint_context_menu(
8049 &self,
8050 anchor: Anchor,
8051 window: &mut Window,
8052 cx: &mut Context<Self>,
8053 ) -> Entity<ui::ContextMenu> {
8054 let weak_editor = cx.weak_entity();
8055 let focus_handle = self.focus_handle(cx);
8056
8057 let row = self
8058 .buffer
8059 .read(cx)
8060 .snapshot(cx)
8061 .summary_for_anchor::<Point>(&anchor)
8062 .row;
8063
8064 let breakpoint = self
8065 .breakpoint_at_row(row, window, cx)
8066 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8067
8068 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8069 "Edit Log Breakpoint"
8070 } else {
8071 "Set Log Breakpoint"
8072 };
8073
8074 let condition_breakpoint_msg = if breakpoint
8075 .as_ref()
8076 .is_some_and(|bp| bp.1.condition.is_some())
8077 {
8078 "Edit Condition Breakpoint"
8079 } else {
8080 "Set Condition Breakpoint"
8081 };
8082
8083 let hit_condition_breakpoint_msg = if breakpoint
8084 .as_ref()
8085 .is_some_and(|bp| bp.1.hit_condition.is_some())
8086 {
8087 "Edit Hit Condition Breakpoint"
8088 } else {
8089 "Set Hit Condition Breakpoint"
8090 };
8091
8092 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8093 "Unset Breakpoint"
8094 } else {
8095 "Set Breakpoint"
8096 };
8097
8098 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8099
8100 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8101 BreakpointState::Enabled => Some("Disable"),
8102 BreakpointState::Disabled => Some("Enable"),
8103 });
8104
8105 let (anchor, breakpoint) =
8106 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8107
8108 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8109 menu.on_blur_subscription(Subscription::new(|| {}))
8110 .context(focus_handle)
8111 .when(run_to_cursor, |this| {
8112 let weak_editor = weak_editor.clone();
8113 this.entry("Run to cursor", None, move |window, cx| {
8114 weak_editor
8115 .update(cx, |editor, cx| {
8116 editor.change_selections(
8117 SelectionEffects::no_scroll(),
8118 window,
8119 cx,
8120 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8121 );
8122 })
8123 .ok();
8124
8125 window.dispatch_action(Box::new(RunToCursor), cx);
8126 })
8127 .separator()
8128 })
8129 .when_some(toggle_state_msg, |this, msg| {
8130 this.entry(msg, None, {
8131 let weak_editor = weak_editor.clone();
8132 let breakpoint = breakpoint.clone();
8133 move |_window, cx| {
8134 weak_editor
8135 .update(cx, |this, cx| {
8136 this.edit_breakpoint_at_anchor(
8137 anchor,
8138 breakpoint.as_ref().clone(),
8139 BreakpointEditAction::InvertState,
8140 cx,
8141 );
8142 })
8143 .log_err();
8144 }
8145 })
8146 })
8147 .entry(set_breakpoint_msg, None, {
8148 let weak_editor = weak_editor.clone();
8149 let breakpoint = breakpoint.clone();
8150 move |_window, cx| {
8151 weak_editor
8152 .update(cx, |this, cx| {
8153 this.edit_breakpoint_at_anchor(
8154 anchor,
8155 breakpoint.as_ref().clone(),
8156 BreakpointEditAction::Toggle,
8157 cx,
8158 );
8159 })
8160 .log_err();
8161 }
8162 })
8163 .entry(log_breakpoint_msg, None, {
8164 let breakpoint = breakpoint.clone();
8165 let weak_editor = weak_editor.clone();
8166 move |window, cx| {
8167 weak_editor
8168 .update(cx, |this, cx| {
8169 this.add_edit_breakpoint_block(
8170 anchor,
8171 breakpoint.as_ref(),
8172 BreakpointPromptEditAction::Log,
8173 window,
8174 cx,
8175 );
8176 })
8177 .log_err();
8178 }
8179 })
8180 .entry(condition_breakpoint_msg, None, {
8181 let breakpoint = breakpoint.clone();
8182 let weak_editor = weak_editor.clone();
8183 move |window, cx| {
8184 weak_editor
8185 .update(cx, |this, cx| {
8186 this.add_edit_breakpoint_block(
8187 anchor,
8188 breakpoint.as_ref(),
8189 BreakpointPromptEditAction::Condition,
8190 window,
8191 cx,
8192 );
8193 })
8194 .log_err();
8195 }
8196 })
8197 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8198 weak_editor
8199 .update(cx, |this, cx| {
8200 this.add_edit_breakpoint_block(
8201 anchor,
8202 breakpoint.as_ref(),
8203 BreakpointPromptEditAction::HitCondition,
8204 window,
8205 cx,
8206 );
8207 })
8208 .log_err();
8209 })
8210 })
8211 }
8212
8213 fn render_breakpoint(
8214 &self,
8215 position: Anchor,
8216 row: DisplayRow,
8217 breakpoint: &Breakpoint,
8218 state: Option<BreakpointSessionState>,
8219 cx: &mut Context<Self>,
8220 ) -> IconButton {
8221 let is_rejected = state.is_some_and(|s| !s.verified);
8222 // Is it a breakpoint that shows up when hovering over gutter?
8223 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8224 (false, false),
8225 |PhantomBreakpointIndicator {
8226 is_active,
8227 display_row,
8228 collides_with_existing_breakpoint,
8229 }| {
8230 (
8231 is_active && display_row == row,
8232 collides_with_existing_breakpoint,
8233 )
8234 },
8235 );
8236
8237 let (color, icon) = {
8238 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8239 (false, false) => ui::IconName::DebugBreakpoint,
8240 (true, false) => ui::IconName::DebugLogBreakpoint,
8241 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8242 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8243 };
8244
8245 let color = if is_phantom {
8246 Color::Hint
8247 } else if is_rejected {
8248 Color::Disabled
8249 } else {
8250 Color::Debugger
8251 };
8252
8253 (color, icon)
8254 };
8255
8256 let breakpoint = Arc::from(breakpoint.clone());
8257
8258 let alt_as_text = gpui::Keystroke {
8259 modifiers: Modifiers::secondary_key(),
8260 ..Default::default()
8261 };
8262 let primary_action_text = if breakpoint.is_disabled() {
8263 "Enable breakpoint"
8264 } else if is_phantom && !collides_with_existing {
8265 "Set breakpoint"
8266 } else {
8267 "Unset breakpoint"
8268 };
8269 let focus_handle = self.focus_handle.clone();
8270
8271 let meta = if is_rejected {
8272 SharedString::from("No executable code is associated with this line.")
8273 } else if collides_with_existing && !breakpoint.is_disabled() {
8274 SharedString::from(format!(
8275 "{alt_as_text}-click to disable,\nright-click for more options."
8276 ))
8277 } else {
8278 SharedString::from("Right-click for more options.")
8279 };
8280 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8281 .icon_size(IconSize::XSmall)
8282 .size(ui::ButtonSize::None)
8283 .when(is_rejected, |this| {
8284 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8285 })
8286 .icon_color(color)
8287 .style(ButtonStyle::Transparent)
8288 .on_click(cx.listener({
8289 move |editor, event: &ClickEvent, window, cx| {
8290 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8291 BreakpointEditAction::InvertState
8292 } else {
8293 BreakpointEditAction::Toggle
8294 };
8295
8296 window.focus(&editor.focus_handle(cx));
8297 editor.edit_breakpoint_at_anchor(
8298 position,
8299 breakpoint.as_ref().clone(),
8300 edit_action,
8301 cx,
8302 );
8303 }
8304 }))
8305 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8306 editor.set_breakpoint_context_menu(
8307 row,
8308 Some(position),
8309 event.position(),
8310 window,
8311 cx,
8312 );
8313 }))
8314 .tooltip(move |window, cx| {
8315 Tooltip::with_meta_in(
8316 primary_action_text,
8317 Some(&ToggleBreakpoint),
8318 meta.clone(),
8319 &focus_handle,
8320 window,
8321 cx,
8322 )
8323 })
8324 }
8325
8326 fn build_tasks_context(
8327 project: &Entity<Project>,
8328 buffer: &Entity<Buffer>,
8329 buffer_row: u32,
8330 tasks: &Arc<RunnableTasks>,
8331 cx: &mut Context<Self>,
8332 ) -> Task<Option<task::TaskContext>> {
8333 let position = Point::new(buffer_row, tasks.column);
8334 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8335 let location = Location {
8336 buffer: buffer.clone(),
8337 range: range_start..range_start,
8338 };
8339 // Fill in the environmental variables from the tree-sitter captures
8340 let mut captured_task_variables = TaskVariables::default();
8341 for (capture_name, value) in tasks.extra_variables.clone() {
8342 captured_task_variables.insert(
8343 task::VariableName::Custom(capture_name.into()),
8344 value.clone(),
8345 );
8346 }
8347 project.update(cx, |project, cx| {
8348 project.task_store().update(cx, |task_store, cx| {
8349 task_store.task_context_for_location(captured_task_variables, location, cx)
8350 })
8351 })
8352 }
8353
8354 pub fn spawn_nearest_task(
8355 &mut self,
8356 action: &SpawnNearestTask,
8357 window: &mut Window,
8358 cx: &mut Context<Self>,
8359 ) {
8360 let Some((workspace, _)) = self.workspace.clone() else {
8361 return;
8362 };
8363 let Some(project) = self.project.clone() else {
8364 return;
8365 };
8366
8367 // Try to find a closest, enclosing node using tree-sitter that has a task
8368 let Some((buffer, buffer_row, tasks)) = self
8369 .find_enclosing_node_task(cx)
8370 // Or find the task that's closest in row-distance.
8371 .or_else(|| self.find_closest_task(cx))
8372 else {
8373 return;
8374 };
8375
8376 let reveal_strategy = action.reveal;
8377 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8378 cx.spawn_in(window, async move |_, cx| {
8379 let context = task_context.await?;
8380 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8381
8382 let resolved = &mut resolved_task.resolved;
8383 resolved.reveal = reveal_strategy;
8384
8385 workspace
8386 .update_in(cx, |workspace, window, cx| {
8387 workspace.schedule_resolved_task(
8388 task_source_kind,
8389 resolved_task,
8390 false,
8391 window,
8392 cx,
8393 );
8394 })
8395 .ok()
8396 })
8397 .detach();
8398 }
8399
8400 fn find_closest_task(
8401 &mut self,
8402 cx: &mut Context<Self>,
8403 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8404 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8405
8406 let ((buffer_id, row), tasks) = self
8407 .tasks
8408 .iter()
8409 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8410
8411 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8412 let tasks = Arc::new(tasks.to_owned());
8413 Some((buffer, *row, tasks))
8414 }
8415
8416 fn find_enclosing_node_task(
8417 &mut self,
8418 cx: &mut Context<Self>,
8419 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8420 let snapshot = self.buffer.read(cx).snapshot(cx);
8421 let offset = self.selections.newest::<usize>(cx).head();
8422 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8423 let buffer_id = excerpt.buffer().remote_id();
8424
8425 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8426 let mut cursor = layer.node().walk();
8427
8428 while cursor.goto_first_child_for_byte(offset).is_some() {
8429 if cursor.node().end_byte() == offset {
8430 cursor.goto_next_sibling();
8431 }
8432 }
8433
8434 // Ascend to the smallest ancestor that contains the range and has a task.
8435 loop {
8436 let node = cursor.node();
8437 let node_range = node.byte_range();
8438 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8439
8440 // Check if this node contains our offset
8441 if node_range.start <= offset && node_range.end >= offset {
8442 // If it contains offset, check for task
8443 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8444 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8445 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8446 }
8447 }
8448
8449 if !cursor.goto_parent() {
8450 break;
8451 }
8452 }
8453 None
8454 }
8455
8456 fn render_run_indicator(
8457 &self,
8458 _style: &EditorStyle,
8459 is_active: bool,
8460 row: DisplayRow,
8461 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8462 cx: &mut Context<Self>,
8463 ) -> IconButton {
8464 let color = Color::Muted;
8465 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8466
8467 IconButton::new(
8468 ("run_indicator", row.0 as usize),
8469 ui::IconName::PlayOutlined,
8470 )
8471 .shape(ui::IconButtonShape::Square)
8472 .icon_size(IconSize::XSmall)
8473 .icon_color(color)
8474 .toggle_state(is_active)
8475 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8476 let quick_launch = match e {
8477 ClickEvent::Keyboard(_) => true,
8478 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8479 };
8480
8481 window.focus(&editor.focus_handle(cx));
8482 editor.toggle_code_actions(
8483 &ToggleCodeActions {
8484 deployed_from: Some(CodeActionSource::RunMenu(row)),
8485 quick_launch,
8486 },
8487 window,
8488 cx,
8489 );
8490 }))
8491 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8492 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8493 }))
8494 }
8495
8496 pub fn context_menu_visible(&self) -> bool {
8497 !self.edit_prediction_preview_is_active()
8498 && self
8499 .context_menu
8500 .borrow()
8501 .as_ref()
8502 .is_some_and(|menu| menu.visible())
8503 }
8504
8505 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8506 self.context_menu
8507 .borrow()
8508 .as_ref()
8509 .map(|menu| menu.origin())
8510 }
8511
8512 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8513 self.context_menu_options = Some(options);
8514 }
8515
8516 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8517 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8518
8519 fn render_edit_prediction_popover(
8520 &mut self,
8521 text_bounds: &Bounds<Pixels>,
8522 content_origin: gpui::Point<Pixels>,
8523 right_margin: Pixels,
8524 editor_snapshot: &EditorSnapshot,
8525 visible_row_range: Range<DisplayRow>,
8526 scroll_top: f32,
8527 scroll_bottom: f32,
8528 line_layouts: &[LineWithInvisibles],
8529 line_height: Pixels,
8530 scroll_pixel_position: gpui::Point<Pixels>,
8531 newest_selection_head: Option<DisplayPoint>,
8532 editor_width: Pixels,
8533 style: &EditorStyle,
8534 window: &mut Window,
8535 cx: &mut App,
8536 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8537 if self.mode().is_minimap() {
8538 return None;
8539 }
8540 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8541
8542 if self.edit_prediction_visible_in_cursor_popover(true) {
8543 return None;
8544 }
8545
8546 match &active_edit_prediction.completion {
8547 EditPrediction::Move { target, .. } => {
8548 let target_display_point = target.to_display_point(editor_snapshot);
8549
8550 if self.edit_prediction_requires_modifier() {
8551 if !self.edit_prediction_preview_is_active() {
8552 return None;
8553 }
8554
8555 self.render_edit_prediction_modifier_jump_popover(
8556 text_bounds,
8557 content_origin,
8558 visible_row_range,
8559 line_layouts,
8560 line_height,
8561 scroll_pixel_position,
8562 newest_selection_head,
8563 target_display_point,
8564 window,
8565 cx,
8566 )
8567 } else {
8568 self.render_edit_prediction_eager_jump_popover(
8569 text_bounds,
8570 content_origin,
8571 editor_snapshot,
8572 visible_row_range,
8573 scroll_top,
8574 scroll_bottom,
8575 line_height,
8576 scroll_pixel_position,
8577 target_display_point,
8578 editor_width,
8579 window,
8580 cx,
8581 )
8582 }
8583 }
8584 EditPrediction::Edit {
8585 display_mode: EditDisplayMode::Inline,
8586 ..
8587 } => None,
8588 EditPrediction::Edit {
8589 display_mode: EditDisplayMode::TabAccept,
8590 edits,
8591 ..
8592 } => {
8593 let range = &edits.first()?.0;
8594 let target_display_point = range.end.to_display_point(editor_snapshot);
8595
8596 self.render_edit_prediction_end_of_line_popover(
8597 "Accept",
8598 editor_snapshot,
8599 visible_row_range,
8600 target_display_point,
8601 line_height,
8602 scroll_pixel_position,
8603 content_origin,
8604 editor_width,
8605 window,
8606 cx,
8607 )
8608 }
8609 EditPrediction::Edit {
8610 edits,
8611 edit_preview,
8612 display_mode: EditDisplayMode::DiffPopover,
8613 snapshot,
8614 } => self.render_edit_prediction_diff_popover(
8615 text_bounds,
8616 content_origin,
8617 right_margin,
8618 editor_snapshot,
8619 visible_row_range,
8620 line_layouts,
8621 line_height,
8622 scroll_pixel_position,
8623 newest_selection_head,
8624 editor_width,
8625 style,
8626 edits,
8627 edit_preview,
8628 snapshot,
8629 window,
8630 cx,
8631 ),
8632 }
8633 }
8634
8635 fn render_edit_prediction_modifier_jump_popover(
8636 &mut self,
8637 text_bounds: &Bounds<Pixels>,
8638 content_origin: gpui::Point<Pixels>,
8639 visible_row_range: Range<DisplayRow>,
8640 line_layouts: &[LineWithInvisibles],
8641 line_height: Pixels,
8642 scroll_pixel_position: gpui::Point<Pixels>,
8643 newest_selection_head: Option<DisplayPoint>,
8644 target_display_point: DisplayPoint,
8645 window: &mut Window,
8646 cx: &mut App,
8647 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8648 let scrolled_content_origin =
8649 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8650
8651 const SCROLL_PADDING_Y: Pixels = px(12.);
8652
8653 if target_display_point.row() < visible_row_range.start {
8654 return self.render_edit_prediction_scroll_popover(
8655 |_| SCROLL_PADDING_Y,
8656 IconName::ArrowUp,
8657 visible_row_range,
8658 line_layouts,
8659 newest_selection_head,
8660 scrolled_content_origin,
8661 window,
8662 cx,
8663 );
8664 } else if target_display_point.row() >= visible_row_range.end {
8665 return self.render_edit_prediction_scroll_popover(
8666 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8667 IconName::ArrowDown,
8668 visible_row_range,
8669 line_layouts,
8670 newest_selection_head,
8671 scrolled_content_origin,
8672 window,
8673 cx,
8674 );
8675 }
8676
8677 const POLE_WIDTH: Pixels = px(2.);
8678
8679 let line_layout =
8680 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8681 let target_column = target_display_point.column() as usize;
8682
8683 let target_x = line_layout.x_for_index(target_column);
8684 let target_y =
8685 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8686
8687 let flag_on_right = target_x < text_bounds.size.width / 2.;
8688
8689 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8690 border_color.l += 0.001;
8691
8692 let mut element = v_flex()
8693 .items_end()
8694 .when(flag_on_right, |el| el.items_start())
8695 .child(if flag_on_right {
8696 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8697 .rounded_bl(px(0.))
8698 .rounded_tl(px(0.))
8699 .border_l_2()
8700 .border_color(border_color)
8701 } else {
8702 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8703 .rounded_br(px(0.))
8704 .rounded_tr(px(0.))
8705 .border_r_2()
8706 .border_color(border_color)
8707 })
8708 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8709 .into_any();
8710
8711 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8712
8713 let mut origin = scrolled_content_origin + point(target_x, target_y)
8714 - point(
8715 if flag_on_right {
8716 POLE_WIDTH
8717 } else {
8718 size.width - POLE_WIDTH
8719 },
8720 size.height - line_height,
8721 );
8722
8723 origin.x = origin.x.max(content_origin.x);
8724
8725 element.prepaint_at(origin, window, cx);
8726
8727 Some((element, origin))
8728 }
8729
8730 fn render_edit_prediction_scroll_popover(
8731 &mut self,
8732 to_y: impl Fn(Size<Pixels>) -> Pixels,
8733 scroll_icon: IconName,
8734 visible_row_range: Range<DisplayRow>,
8735 line_layouts: &[LineWithInvisibles],
8736 newest_selection_head: Option<DisplayPoint>,
8737 scrolled_content_origin: gpui::Point<Pixels>,
8738 window: &mut Window,
8739 cx: &mut App,
8740 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8741 let mut element = self
8742 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8743 .into_any();
8744
8745 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8746
8747 let cursor = newest_selection_head?;
8748 let cursor_row_layout =
8749 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8750 let cursor_column = cursor.column() as usize;
8751
8752 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8753
8754 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8755
8756 element.prepaint_at(origin, window, cx);
8757 Some((element, origin))
8758 }
8759
8760 fn render_edit_prediction_eager_jump_popover(
8761 &mut self,
8762 text_bounds: &Bounds<Pixels>,
8763 content_origin: gpui::Point<Pixels>,
8764 editor_snapshot: &EditorSnapshot,
8765 visible_row_range: Range<DisplayRow>,
8766 scroll_top: f32,
8767 scroll_bottom: f32,
8768 line_height: Pixels,
8769 scroll_pixel_position: gpui::Point<Pixels>,
8770 target_display_point: DisplayPoint,
8771 editor_width: Pixels,
8772 window: &mut Window,
8773 cx: &mut App,
8774 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8775 if target_display_point.row().as_f32() < scroll_top {
8776 let mut element = self
8777 .render_edit_prediction_line_popover(
8778 "Jump to Edit",
8779 Some(IconName::ArrowUp),
8780 window,
8781 cx,
8782 )?
8783 .into_any();
8784
8785 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8786 let offset = point(
8787 (text_bounds.size.width - size.width) / 2.,
8788 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8789 );
8790
8791 let origin = text_bounds.origin + offset;
8792 element.prepaint_at(origin, window, cx);
8793 Some((element, origin))
8794 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8795 let mut element = self
8796 .render_edit_prediction_line_popover(
8797 "Jump to Edit",
8798 Some(IconName::ArrowDown),
8799 window,
8800 cx,
8801 )?
8802 .into_any();
8803
8804 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8805 let offset = point(
8806 (text_bounds.size.width - size.width) / 2.,
8807 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8808 );
8809
8810 let origin = text_bounds.origin + offset;
8811 element.prepaint_at(origin, window, cx);
8812 Some((element, origin))
8813 } else {
8814 self.render_edit_prediction_end_of_line_popover(
8815 "Jump to Edit",
8816 editor_snapshot,
8817 visible_row_range,
8818 target_display_point,
8819 line_height,
8820 scroll_pixel_position,
8821 content_origin,
8822 editor_width,
8823 window,
8824 cx,
8825 )
8826 }
8827 }
8828
8829 fn render_edit_prediction_end_of_line_popover(
8830 self: &mut Editor,
8831 label: &'static str,
8832 editor_snapshot: &EditorSnapshot,
8833 visible_row_range: Range<DisplayRow>,
8834 target_display_point: DisplayPoint,
8835 line_height: Pixels,
8836 scroll_pixel_position: gpui::Point<Pixels>,
8837 content_origin: gpui::Point<Pixels>,
8838 editor_width: Pixels,
8839 window: &mut Window,
8840 cx: &mut App,
8841 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8842 let target_line_end = DisplayPoint::new(
8843 target_display_point.row(),
8844 editor_snapshot.line_len(target_display_point.row()),
8845 );
8846
8847 let mut element = self
8848 .render_edit_prediction_line_popover(label, None, window, cx)?
8849 .into_any();
8850
8851 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8852
8853 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8854
8855 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8856 let mut origin = start_point
8857 + line_origin
8858 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8859 origin.x = origin.x.max(content_origin.x);
8860
8861 let max_x = content_origin.x + editor_width - size.width;
8862
8863 if origin.x > max_x {
8864 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8865
8866 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8867 origin.y += offset;
8868 IconName::ArrowUp
8869 } else {
8870 origin.y -= offset;
8871 IconName::ArrowDown
8872 };
8873
8874 element = self
8875 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8876 .into_any();
8877
8878 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8879
8880 origin.x = content_origin.x + editor_width - size.width - px(2.);
8881 }
8882
8883 element.prepaint_at(origin, window, cx);
8884 Some((element, origin))
8885 }
8886
8887 fn render_edit_prediction_diff_popover(
8888 self: &Editor,
8889 text_bounds: &Bounds<Pixels>,
8890 content_origin: gpui::Point<Pixels>,
8891 right_margin: Pixels,
8892 editor_snapshot: &EditorSnapshot,
8893 visible_row_range: Range<DisplayRow>,
8894 line_layouts: &[LineWithInvisibles],
8895 line_height: Pixels,
8896 scroll_pixel_position: gpui::Point<Pixels>,
8897 newest_selection_head: Option<DisplayPoint>,
8898 editor_width: Pixels,
8899 style: &EditorStyle,
8900 edits: &Vec<(Range<Anchor>, String)>,
8901 edit_preview: &Option<language::EditPreview>,
8902 snapshot: &language::BufferSnapshot,
8903 window: &mut Window,
8904 cx: &mut App,
8905 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8906 let edit_start = edits
8907 .first()
8908 .unwrap()
8909 .0
8910 .start
8911 .to_display_point(editor_snapshot);
8912 let edit_end = edits
8913 .last()
8914 .unwrap()
8915 .0
8916 .end
8917 .to_display_point(editor_snapshot);
8918
8919 let is_visible = visible_row_range.contains(&edit_start.row())
8920 || visible_row_range.contains(&edit_end.row());
8921 if !is_visible {
8922 return None;
8923 }
8924
8925 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8926 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8927 } else {
8928 // Fallback for providers without edit_preview
8929 crate::edit_prediction_fallback_text(edits, cx)
8930 };
8931
8932 let styled_text = highlighted_edits.to_styled_text(&style.text);
8933 let line_count = highlighted_edits.text.lines().count();
8934
8935 const BORDER_WIDTH: Pixels = px(1.);
8936
8937 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8938 let has_keybind = keybind.is_some();
8939
8940 let mut element = h_flex()
8941 .items_start()
8942 .child(
8943 h_flex()
8944 .bg(cx.theme().colors().editor_background)
8945 .border(BORDER_WIDTH)
8946 .shadow_xs()
8947 .border_color(cx.theme().colors().border)
8948 .rounded_l_lg()
8949 .when(line_count > 1, |el| el.rounded_br_lg())
8950 .pr_1()
8951 .child(styled_text),
8952 )
8953 .child(
8954 h_flex()
8955 .h(line_height + BORDER_WIDTH * 2.)
8956 .px_1p5()
8957 .gap_1()
8958 // Workaround: For some reason, there's a gap if we don't do this
8959 .ml(-BORDER_WIDTH)
8960 .shadow(vec![gpui::BoxShadow {
8961 color: gpui::black().opacity(0.05),
8962 offset: point(px(1.), px(1.)),
8963 blur_radius: px(2.),
8964 spread_radius: px(0.),
8965 }])
8966 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8967 .border(BORDER_WIDTH)
8968 .border_color(cx.theme().colors().border)
8969 .rounded_r_lg()
8970 .id("edit_prediction_diff_popover_keybind")
8971 .when(!has_keybind, |el| {
8972 let status_colors = cx.theme().status();
8973
8974 el.bg(status_colors.error_background)
8975 .border_color(status_colors.error.opacity(0.6))
8976 .child(Icon::new(IconName::Info).color(Color::Error))
8977 .cursor_default()
8978 .hoverable_tooltip(move |_window, cx| {
8979 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8980 })
8981 })
8982 .children(keybind),
8983 )
8984 .into_any();
8985
8986 let longest_row =
8987 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8988 let longest_line_width = if visible_row_range.contains(&longest_row) {
8989 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8990 } else {
8991 layout_line(
8992 longest_row,
8993 editor_snapshot,
8994 style,
8995 editor_width,
8996 |_| false,
8997 window,
8998 cx,
8999 )
9000 .width
9001 };
9002
9003 let viewport_bounds =
9004 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9005 right: -right_margin,
9006 ..Default::default()
9007 });
9008
9009 let x_after_longest =
9010 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9011 - scroll_pixel_position.x;
9012
9013 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9014
9015 // Fully visible if it can be displayed within the window (allow overlapping other
9016 // panes). However, this is only allowed if the popover starts within text_bounds.
9017 let can_position_to_the_right = x_after_longest < text_bounds.right()
9018 && x_after_longest + element_bounds.width < viewport_bounds.right();
9019
9020 let mut origin = if can_position_to_the_right {
9021 point(
9022 x_after_longest,
9023 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9024 - scroll_pixel_position.y,
9025 )
9026 } else {
9027 let cursor_row = newest_selection_head.map(|head| head.row());
9028 let above_edit = edit_start
9029 .row()
9030 .0
9031 .checked_sub(line_count as u32)
9032 .map(DisplayRow);
9033 let below_edit = Some(edit_end.row() + 1);
9034 let above_cursor =
9035 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9036 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9037
9038 // Place the edit popover adjacent to the edit if there is a location
9039 // available that is onscreen and does not obscure the cursor. Otherwise,
9040 // place it adjacent to the cursor.
9041 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9042 .into_iter()
9043 .flatten()
9044 .find(|&start_row| {
9045 let end_row = start_row + line_count as u32;
9046 visible_row_range.contains(&start_row)
9047 && visible_row_range.contains(&end_row)
9048 && cursor_row
9049 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9050 })?;
9051
9052 content_origin
9053 + point(
9054 -scroll_pixel_position.x,
9055 row_target.as_f32() * line_height - scroll_pixel_position.y,
9056 )
9057 };
9058
9059 origin.x -= BORDER_WIDTH;
9060
9061 window.defer_draw(element, origin, 1);
9062
9063 // Do not return an element, since it will already be drawn due to defer_draw.
9064 None
9065 }
9066
9067 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9068 px(30.)
9069 }
9070
9071 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9072 if self.read_only(cx) {
9073 cx.theme().players().read_only()
9074 } else {
9075 self.style.as_ref().unwrap().local_player
9076 }
9077 }
9078
9079 fn render_edit_prediction_accept_keybind(
9080 &self,
9081 window: &mut Window,
9082 cx: &App,
9083 ) -> Option<AnyElement> {
9084 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9085 let accept_keystroke = accept_binding.keystroke()?;
9086
9087 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9088
9089 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9090 Color::Accent
9091 } else {
9092 Color::Muted
9093 };
9094
9095 h_flex()
9096 .px_0p5()
9097 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9098 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9099 .text_size(TextSize::XSmall.rems(cx))
9100 .child(h_flex().children(ui::render_modifiers(
9101 accept_keystroke.modifiers(),
9102 PlatformStyle::platform(),
9103 Some(modifiers_color),
9104 Some(IconSize::XSmall.rems().into()),
9105 true,
9106 )))
9107 .when(is_platform_style_mac, |parent| {
9108 parent.child(accept_keystroke.key().to_string())
9109 })
9110 .when(!is_platform_style_mac, |parent| {
9111 parent.child(
9112 Key::new(
9113 util::capitalize(accept_keystroke.key()),
9114 Some(Color::Default),
9115 )
9116 .size(Some(IconSize::XSmall.rems().into())),
9117 )
9118 })
9119 .into_any()
9120 .into()
9121 }
9122
9123 fn render_edit_prediction_line_popover(
9124 &self,
9125 label: impl Into<SharedString>,
9126 icon: Option<IconName>,
9127 window: &mut Window,
9128 cx: &App,
9129 ) -> Option<Stateful<Div>> {
9130 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9131
9132 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9133 let has_keybind = keybind.is_some();
9134
9135 let result = h_flex()
9136 .id("ep-line-popover")
9137 .py_0p5()
9138 .pl_1()
9139 .pr(padding_right)
9140 .gap_1()
9141 .rounded_md()
9142 .border_1()
9143 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9144 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9145 .shadow_xs()
9146 .when(!has_keybind, |el| {
9147 let status_colors = cx.theme().status();
9148
9149 el.bg(status_colors.error_background)
9150 .border_color(status_colors.error.opacity(0.6))
9151 .pl_2()
9152 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9153 .cursor_default()
9154 .hoverable_tooltip(move |_window, cx| {
9155 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9156 })
9157 })
9158 .children(keybind)
9159 .child(
9160 Label::new(label)
9161 .size(LabelSize::Small)
9162 .when(!has_keybind, |el| {
9163 el.color(cx.theme().status().error.into()).strikethrough()
9164 }),
9165 )
9166 .when(!has_keybind, |el| {
9167 el.child(
9168 h_flex().ml_1().child(
9169 Icon::new(IconName::Info)
9170 .size(IconSize::Small)
9171 .color(cx.theme().status().error.into()),
9172 ),
9173 )
9174 })
9175 .when_some(icon, |element, icon| {
9176 element.child(
9177 div()
9178 .mt(px(1.5))
9179 .child(Icon::new(icon).size(IconSize::Small)),
9180 )
9181 });
9182
9183 Some(result)
9184 }
9185
9186 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9187 let accent_color = cx.theme().colors().text_accent;
9188 let editor_bg_color = cx.theme().colors().editor_background;
9189 editor_bg_color.blend(accent_color.opacity(0.1))
9190 }
9191
9192 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9193 let accent_color = cx.theme().colors().text_accent;
9194 let editor_bg_color = cx.theme().colors().editor_background;
9195 editor_bg_color.blend(accent_color.opacity(0.6))
9196 }
9197 fn get_prediction_provider_icon_name(
9198 provider: &Option<RegisteredEditPredictionProvider>,
9199 ) -> IconName {
9200 match provider {
9201 Some(provider) => match provider.provider.name() {
9202 "copilot" => IconName::Copilot,
9203 "supermaven" => IconName::Supermaven,
9204 _ => IconName::ZedPredict,
9205 },
9206 None => IconName::ZedPredict,
9207 }
9208 }
9209
9210 fn render_edit_prediction_cursor_popover(
9211 &self,
9212 min_width: Pixels,
9213 max_width: Pixels,
9214 cursor_point: Point,
9215 style: &EditorStyle,
9216 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9217 _window: &Window,
9218 cx: &mut Context<Editor>,
9219 ) -> Option<AnyElement> {
9220 let provider = self.edit_prediction_provider.as_ref()?;
9221 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9222
9223 let is_refreshing = provider.provider.is_refreshing(cx);
9224
9225 fn pending_completion_container(icon: IconName) -> Div {
9226 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9227 }
9228
9229 let completion = match &self.active_edit_prediction {
9230 Some(prediction) => {
9231 if !self.has_visible_completions_menu() {
9232 const RADIUS: Pixels = px(6.);
9233 const BORDER_WIDTH: Pixels = px(1.);
9234
9235 return Some(
9236 h_flex()
9237 .elevation_2(cx)
9238 .border(BORDER_WIDTH)
9239 .border_color(cx.theme().colors().border)
9240 .when(accept_keystroke.is_none(), |el| {
9241 el.border_color(cx.theme().status().error)
9242 })
9243 .rounded(RADIUS)
9244 .rounded_tl(px(0.))
9245 .overflow_hidden()
9246 .child(div().px_1p5().child(match &prediction.completion {
9247 EditPrediction::Move { target, snapshot } => {
9248 use text::ToPoint as _;
9249 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9250 {
9251 Icon::new(IconName::ZedPredictDown)
9252 } else {
9253 Icon::new(IconName::ZedPredictUp)
9254 }
9255 }
9256 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9257 }))
9258 .child(
9259 h_flex()
9260 .gap_1()
9261 .py_1()
9262 .px_2()
9263 .rounded_r(RADIUS - BORDER_WIDTH)
9264 .border_l_1()
9265 .border_color(cx.theme().colors().border)
9266 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9267 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9268 el.child(
9269 Label::new("Hold")
9270 .size(LabelSize::Small)
9271 .when(accept_keystroke.is_none(), |el| {
9272 el.strikethrough()
9273 })
9274 .line_height_style(LineHeightStyle::UiLabel),
9275 )
9276 })
9277 .id("edit_prediction_cursor_popover_keybind")
9278 .when(accept_keystroke.is_none(), |el| {
9279 let status_colors = cx.theme().status();
9280
9281 el.bg(status_colors.error_background)
9282 .border_color(status_colors.error.opacity(0.6))
9283 .child(Icon::new(IconName::Info).color(Color::Error))
9284 .cursor_default()
9285 .hoverable_tooltip(move |_window, cx| {
9286 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9287 .into()
9288 })
9289 })
9290 .when_some(
9291 accept_keystroke.as_ref(),
9292 |el, accept_keystroke| {
9293 el.child(h_flex().children(ui::render_modifiers(
9294 accept_keystroke.modifiers(),
9295 PlatformStyle::platform(),
9296 Some(Color::Default),
9297 Some(IconSize::XSmall.rems().into()),
9298 false,
9299 )))
9300 },
9301 ),
9302 )
9303 .into_any(),
9304 );
9305 }
9306
9307 self.render_edit_prediction_cursor_popover_preview(
9308 prediction,
9309 cursor_point,
9310 style,
9311 cx,
9312 )?
9313 }
9314
9315 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9316 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9317 stale_completion,
9318 cursor_point,
9319 style,
9320 cx,
9321 )?,
9322
9323 None => pending_completion_container(provider_icon)
9324 .child(Label::new("...").size(LabelSize::Small)),
9325 },
9326
9327 None => pending_completion_container(provider_icon)
9328 .child(Label::new("...").size(LabelSize::Small)),
9329 };
9330
9331 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9332 completion
9333 .with_animation(
9334 "loading-completion",
9335 Animation::new(Duration::from_secs(2))
9336 .repeat()
9337 .with_easing(pulsating_between(0.4, 0.8)),
9338 |label, delta| label.opacity(delta),
9339 )
9340 .into_any_element()
9341 } else {
9342 completion.into_any_element()
9343 };
9344
9345 let has_completion = self.active_edit_prediction.is_some();
9346
9347 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9348 Some(
9349 h_flex()
9350 .min_w(min_width)
9351 .max_w(max_width)
9352 .flex_1()
9353 .elevation_2(cx)
9354 .border_color(cx.theme().colors().border)
9355 .child(
9356 div()
9357 .flex_1()
9358 .py_1()
9359 .px_2()
9360 .overflow_hidden()
9361 .child(completion),
9362 )
9363 .when_some(accept_keystroke, |el, accept_keystroke| {
9364 if !accept_keystroke.modifiers().modified() {
9365 return el;
9366 }
9367
9368 el.child(
9369 h_flex()
9370 .h_full()
9371 .border_l_1()
9372 .rounded_r_lg()
9373 .border_color(cx.theme().colors().border)
9374 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9375 .gap_1()
9376 .py_1()
9377 .px_2()
9378 .child(
9379 h_flex()
9380 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9381 .when(is_platform_style_mac, |parent| parent.gap_1())
9382 .child(h_flex().children(ui::render_modifiers(
9383 accept_keystroke.modifiers(),
9384 PlatformStyle::platform(),
9385 Some(if !has_completion {
9386 Color::Muted
9387 } else {
9388 Color::Default
9389 }),
9390 None,
9391 false,
9392 ))),
9393 )
9394 .child(Label::new("Preview").into_any_element())
9395 .opacity(if has_completion { 1.0 } else { 0.4 }),
9396 )
9397 })
9398 .into_any(),
9399 )
9400 }
9401
9402 fn render_edit_prediction_cursor_popover_preview(
9403 &self,
9404 completion: &EditPredictionState,
9405 cursor_point: Point,
9406 style: &EditorStyle,
9407 cx: &mut Context<Editor>,
9408 ) -> Option<Div> {
9409 use text::ToPoint as _;
9410
9411 fn render_relative_row_jump(
9412 prefix: impl Into<String>,
9413 current_row: u32,
9414 target_row: u32,
9415 ) -> Div {
9416 let (row_diff, arrow) = if target_row < current_row {
9417 (current_row - target_row, IconName::ArrowUp)
9418 } else {
9419 (target_row - current_row, IconName::ArrowDown)
9420 };
9421
9422 h_flex()
9423 .child(
9424 Label::new(format!("{}{}", prefix.into(), row_diff))
9425 .color(Color::Muted)
9426 .size(LabelSize::Small),
9427 )
9428 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9429 }
9430
9431 let supports_jump = self
9432 .edit_prediction_provider
9433 .as_ref()
9434 .map(|provider| provider.provider.supports_jump_to_edit())
9435 .unwrap_or(true);
9436
9437 match &completion.completion {
9438 EditPrediction::Move {
9439 target, snapshot, ..
9440 } => {
9441 if !supports_jump {
9442 return None;
9443 }
9444
9445 Some(
9446 h_flex()
9447 .px_2()
9448 .gap_2()
9449 .flex_1()
9450 .child(
9451 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9452 Icon::new(IconName::ZedPredictDown)
9453 } else {
9454 Icon::new(IconName::ZedPredictUp)
9455 },
9456 )
9457 .child(Label::new("Jump to Edit")),
9458 )
9459 }
9460
9461 EditPrediction::Edit {
9462 edits,
9463 edit_preview,
9464 snapshot,
9465 display_mode: _,
9466 } => {
9467 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9468
9469 let (highlighted_edits, has_more_lines) =
9470 if let Some(edit_preview) = edit_preview.as_ref() {
9471 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9472 .first_line_preview()
9473 } else {
9474 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9475 };
9476
9477 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9478 .with_default_highlights(&style.text, highlighted_edits.highlights);
9479
9480 let preview = h_flex()
9481 .gap_1()
9482 .min_w_16()
9483 .child(styled_text)
9484 .when(has_more_lines, |parent| parent.child("…"));
9485
9486 let left = if supports_jump && first_edit_row != cursor_point.row {
9487 render_relative_row_jump("", cursor_point.row, first_edit_row)
9488 .into_any_element()
9489 } else {
9490 let icon_name =
9491 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9492 Icon::new(icon_name).into_any_element()
9493 };
9494
9495 Some(
9496 h_flex()
9497 .h_full()
9498 .flex_1()
9499 .gap_2()
9500 .pr_1()
9501 .overflow_x_hidden()
9502 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9503 .child(left)
9504 .child(preview),
9505 )
9506 }
9507 }
9508 }
9509
9510 pub fn render_context_menu(
9511 &self,
9512 style: &EditorStyle,
9513 max_height_in_lines: u32,
9514 window: &mut Window,
9515 cx: &mut Context<Editor>,
9516 ) -> Option<AnyElement> {
9517 let menu = self.context_menu.borrow();
9518 let menu = menu.as_ref()?;
9519 if !menu.visible() {
9520 return None;
9521 };
9522 Some(menu.render(style, max_height_in_lines, window, cx))
9523 }
9524
9525 fn render_context_menu_aside(
9526 &mut self,
9527 max_size: Size<Pixels>,
9528 window: &mut Window,
9529 cx: &mut Context<Editor>,
9530 ) -> Option<AnyElement> {
9531 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9532 if menu.visible() {
9533 menu.render_aside(max_size, window, cx)
9534 } else {
9535 None
9536 }
9537 })
9538 }
9539
9540 fn hide_context_menu(
9541 &mut self,
9542 window: &mut Window,
9543 cx: &mut Context<Self>,
9544 ) -> Option<CodeContextMenu> {
9545 cx.notify();
9546 self.completion_tasks.clear();
9547 let context_menu = self.context_menu.borrow_mut().take();
9548 self.stale_edit_prediction_in_menu.take();
9549 self.update_visible_edit_prediction(window, cx);
9550 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9551 && let Some(completion_provider) = &self.completion_provider
9552 {
9553 completion_provider.selection_changed(None, window, cx);
9554 }
9555 context_menu
9556 }
9557
9558 fn show_snippet_choices(
9559 &mut self,
9560 choices: &Vec<String>,
9561 selection: Range<Anchor>,
9562 cx: &mut Context<Self>,
9563 ) {
9564 let Some((_, buffer, _)) = self
9565 .buffer()
9566 .read(cx)
9567 .excerpt_containing(selection.start, cx)
9568 else {
9569 return;
9570 };
9571 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9572 else {
9573 return;
9574 };
9575 if buffer != end_buffer {
9576 log::error!("expected anchor range to have matching buffer IDs");
9577 return;
9578 }
9579
9580 let id = post_inc(&mut self.next_completion_id);
9581 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9582 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9583 CompletionsMenu::new_snippet_choices(
9584 id,
9585 true,
9586 choices,
9587 selection,
9588 buffer,
9589 snippet_sort_order,
9590 ),
9591 ));
9592 }
9593
9594 pub fn insert_snippet(
9595 &mut self,
9596 insertion_ranges: &[Range<usize>],
9597 snippet: Snippet,
9598 window: &mut Window,
9599 cx: &mut Context<Self>,
9600 ) -> Result<()> {
9601 struct Tabstop<T> {
9602 is_end_tabstop: bool,
9603 ranges: Vec<Range<T>>,
9604 choices: Option<Vec<String>>,
9605 }
9606
9607 let tabstops = self.buffer.update(cx, |buffer, cx| {
9608 let snippet_text: Arc<str> = snippet.text.clone().into();
9609 let edits = insertion_ranges
9610 .iter()
9611 .cloned()
9612 .map(|range| (range, snippet_text.clone()));
9613 let autoindent_mode = AutoindentMode::Block {
9614 original_indent_columns: Vec::new(),
9615 };
9616 buffer.edit(edits, Some(autoindent_mode), cx);
9617
9618 let snapshot = &*buffer.read(cx);
9619 let snippet = &snippet;
9620 snippet
9621 .tabstops
9622 .iter()
9623 .map(|tabstop| {
9624 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9625 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9626 });
9627 let mut tabstop_ranges = tabstop
9628 .ranges
9629 .iter()
9630 .flat_map(|tabstop_range| {
9631 let mut delta = 0_isize;
9632 insertion_ranges.iter().map(move |insertion_range| {
9633 let insertion_start = insertion_range.start as isize + delta;
9634 delta +=
9635 snippet.text.len() as isize - insertion_range.len() as isize;
9636
9637 let start = ((insertion_start + tabstop_range.start) as usize)
9638 .min(snapshot.len());
9639 let end = ((insertion_start + tabstop_range.end) as usize)
9640 .min(snapshot.len());
9641 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9642 })
9643 })
9644 .collect::<Vec<_>>();
9645 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9646
9647 Tabstop {
9648 is_end_tabstop,
9649 ranges: tabstop_ranges,
9650 choices: tabstop.choices.clone(),
9651 }
9652 })
9653 .collect::<Vec<_>>()
9654 });
9655 if let Some(tabstop) = tabstops.first() {
9656 self.change_selections(Default::default(), window, cx, |s| {
9657 // Reverse order so that the first range is the newest created selection.
9658 // Completions will use it and autoscroll will prioritize it.
9659 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9660 });
9661
9662 if let Some(choices) = &tabstop.choices
9663 && let Some(selection) = tabstop.ranges.first()
9664 {
9665 self.show_snippet_choices(choices, selection.clone(), cx)
9666 }
9667
9668 // If we're already at the last tabstop and it's at the end of the snippet,
9669 // we're done, we don't need to keep the state around.
9670 if !tabstop.is_end_tabstop {
9671 let choices = tabstops
9672 .iter()
9673 .map(|tabstop| tabstop.choices.clone())
9674 .collect();
9675
9676 let ranges = tabstops
9677 .into_iter()
9678 .map(|tabstop| tabstop.ranges)
9679 .collect::<Vec<_>>();
9680
9681 self.snippet_stack.push(SnippetState {
9682 active_index: 0,
9683 ranges,
9684 choices,
9685 });
9686 }
9687
9688 // Check whether the just-entered snippet ends with an auto-closable bracket.
9689 if self.autoclose_regions.is_empty() {
9690 let snapshot = self.buffer.read(cx).snapshot(cx);
9691 let mut all_selections = self.selections.all::<Point>(cx);
9692 for selection in &mut all_selections {
9693 let selection_head = selection.head();
9694 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9695 continue;
9696 };
9697
9698 let mut bracket_pair = None;
9699 let max_lookup_length = scope
9700 .brackets()
9701 .map(|(pair, _)| {
9702 pair.start
9703 .as_str()
9704 .chars()
9705 .count()
9706 .max(pair.end.as_str().chars().count())
9707 })
9708 .max();
9709 if let Some(max_lookup_length) = max_lookup_length {
9710 let next_text = snapshot
9711 .chars_at(selection_head)
9712 .take(max_lookup_length)
9713 .collect::<String>();
9714 let prev_text = snapshot
9715 .reversed_chars_at(selection_head)
9716 .take(max_lookup_length)
9717 .collect::<String>();
9718
9719 for (pair, enabled) in scope.brackets() {
9720 if enabled
9721 && pair.close
9722 && prev_text.starts_with(pair.start.as_str())
9723 && next_text.starts_with(pair.end.as_str())
9724 {
9725 bracket_pair = Some(pair.clone());
9726 break;
9727 }
9728 }
9729 }
9730
9731 if let Some(pair) = bracket_pair {
9732 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9733 let autoclose_enabled =
9734 self.use_autoclose && snapshot_settings.use_autoclose;
9735 if autoclose_enabled {
9736 let start = snapshot.anchor_after(selection_head);
9737 let end = snapshot.anchor_after(selection_head);
9738 self.autoclose_regions.push(AutocloseRegion {
9739 selection_id: selection.id,
9740 range: start..end,
9741 pair,
9742 });
9743 }
9744 }
9745 }
9746 }
9747 }
9748 Ok(())
9749 }
9750
9751 pub fn move_to_next_snippet_tabstop(
9752 &mut self,
9753 window: &mut Window,
9754 cx: &mut Context<Self>,
9755 ) -> bool {
9756 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9757 }
9758
9759 pub fn move_to_prev_snippet_tabstop(
9760 &mut self,
9761 window: &mut Window,
9762 cx: &mut Context<Self>,
9763 ) -> bool {
9764 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9765 }
9766
9767 pub fn move_to_snippet_tabstop(
9768 &mut self,
9769 bias: Bias,
9770 window: &mut Window,
9771 cx: &mut Context<Self>,
9772 ) -> bool {
9773 if let Some(mut snippet) = self.snippet_stack.pop() {
9774 match bias {
9775 Bias::Left => {
9776 if snippet.active_index > 0 {
9777 snippet.active_index -= 1;
9778 } else {
9779 self.snippet_stack.push(snippet);
9780 return false;
9781 }
9782 }
9783 Bias::Right => {
9784 if snippet.active_index + 1 < snippet.ranges.len() {
9785 snippet.active_index += 1;
9786 } else {
9787 self.snippet_stack.push(snippet);
9788 return false;
9789 }
9790 }
9791 }
9792 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9793 self.change_selections(Default::default(), window, cx, |s| {
9794 // Reverse order so that the first range is the newest created selection.
9795 // Completions will use it and autoscroll will prioritize it.
9796 s.select_ranges(current_ranges.iter().rev().cloned())
9797 });
9798
9799 if let Some(choices) = &snippet.choices[snippet.active_index]
9800 && let Some(selection) = current_ranges.first()
9801 {
9802 self.show_snippet_choices(choices, selection.clone(), cx);
9803 }
9804
9805 // If snippet state is not at the last tabstop, push it back on the stack
9806 if snippet.active_index + 1 < snippet.ranges.len() {
9807 self.snippet_stack.push(snippet);
9808 }
9809 return true;
9810 }
9811 }
9812
9813 false
9814 }
9815
9816 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9817 self.transact(window, cx, |this, window, cx| {
9818 this.select_all(&SelectAll, window, cx);
9819 this.insert("", window, cx);
9820 });
9821 }
9822
9823 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9824 if self.read_only(cx) {
9825 return;
9826 }
9827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9828 self.transact(window, cx, |this, window, cx| {
9829 this.select_autoclose_pair(window, cx);
9830 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9831 if !this.linked_edit_ranges.is_empty() {
9832 let selections = this.selections.all::<MultiBufferPoint>(cx);
9833 let snapshot = this.buffer.read(cx).snapshot(cx);
9834
9835 for selection in selections.iter() {
9836 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9837 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9838 if selection_start.buffer_id != selection_end.buffer_id {
9839 continue;
9840 }
9841 if let Some(ranges) =
9842 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9843 {
9844 for (buffer, entries) in ranges {
9845 linked_ranges.entry(buffer).or_default().extend(entries);
9846 }
9847 }
9848 }
9849 }
9850
9851 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9852 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9853 for selection in &mut selections {
9854 if selection.is_empty() {
9855 let old_head = selection.head();
9856 let mut new_head =
9857 movement::left(&display_map, old_head.to_display_point(&display_map))
9858 .to_point(&display_map);
9859 if let Some((buffer, line_buffer_range)) = display_map
9860 .buffer_snapshot
9861 .buffer_line_for_row(MultiBufferRow(old_head.row))
9862 {
9863 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9864 let indent_len = match indent_size.kind {
9865 IndentKind::Space => {
9866 buffer.settings_at(line_buffer_range.start, cx).tab_size
9867 }
9868 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9869 };
9870 if old_head.column <= indent_size.len && old_head.column > 0 {
9871 let indent_len = indent_len.get();
9872 new_head = cmp::min(
9873 new_head,
9874 MultiBufferPoint::new(
9875 old_head.row,
9876 ((old_head.column - 1) / indent_len) * indent_len,
9877 ),
9878 );
9879 }
9880 }
9881
9882 selection.set_head(new_head, SelectionGoal::None);
9883 }
9884 }
9885
9886 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9887 this.insert("", window, cx);
9888 let empty_str: Arc<str> = Arc::from("");
9889 for (buffer, edits) in linked_ranges {
9890 let snapshot = buffer.read(cx).snapshot();
9891 use text::ToPoint as TP;
9892
9893 let edits = edits
9894 .into_iter()
9895 .map(|range| {
9896 let end_point = TP::to_point(&range.end, &snapshot);
9897 let mut start_point = TP::to_point(&range.start, &snapshot);
9898
9899 if end_point == start_point {
9900 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9901 .saturating_sub(1);
9902 start_point =
9903 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9904 };
9905
9906 (start_point..end_point, empty_str.clone())
9907 })
9908 .sorted_by_key(|(range, _)| range.start)
9909 .collect::<Vec<_>>();
9910 buffer.update(cx, |this, cx| {
9911 this.edit(edits, None, cx);
9912 })
9913 }
9914 this.refresh_edit_prediction(true, false, window, cx);
9915 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9916 });
9917 }
9918
9919 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9920 if self.read_only(cx) {
9921 return;
9922 }
9923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9924 self.transact(window, cx, |this, window, cx| {
9925 this.change_selections(Default::default(), window, cx, |s| {
9926 s.move_with(|map, selection| {
9927 if selection.is_empty() {
9928 let cursor = movement::right(map, selection.head());
9929 selection.end = cursor;
9930 selection.reversed = true;
9931 selection.goal = SelectionGoal::None;
9932 }
9933 })
9934 });
9935 this.insert("", window, cx);
9936 this.refresh_edit_prediction(true, false, window, cx);
9937 });
9938 }
9939
9940 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9941 if self.mode.is_single_line() {
9942 cx.propagate();
9943 return;
9944 }
9945
9946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9947 if self.move_to_prev_snippet_tabstop(window, cx) {
9948 return;
9949 }
9950 self.outdent(&Outdent, window, cx);
9951 }
9952
9953 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9954 if self.mode.is_single_line() {
9955 cx.propagate();
9956 return;
9957 }
9958
9959 if self.move_to_next_snippet_tabstop(window, cx) {
9960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9961 return;
9962 }
9963 if self.read_only(cx) {
9964 return;
9965 }
9966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9967 let mut selections = self.selections.all_adjusted(cx);
9968 let buffer = self.buffer.read(cx);
9969 let snapshot = buffer.snapshot(cx);
9970 let rows_iter = selections.iter().map(|s| s.head().row);
9971 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9972
9973 let has_some_cursor_in_whitespace = selections
9974 .iter()
9975 .filter(|selection| selection.is_empty())
9976 .any(|selection| {
9977 let cursor = selection.head();
9978 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9979 cursor.column < current_indent.len
9980 });
9981
9982 let mut edits = Vec::new();
9983 let mut prev_edited_row = 0;
9984 let mut row_delta = 0;
9985 for selection in &mut selections {
9986 if selection.start.row != prev_edited_row {
9987 row_delta = 0;
9988 }
9989 prev_edited_row = selection.end.row;
9990
9991 // If the selection is non-empty, then increase the indentation of the selected lines.
9992 if !selection.is_empty() {
9993 row_delta =
9994 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9995 continue;
9996 }
9997
9998 let cursor = selection.head();
9999 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10000 if let Some(suggested_indent) =
10001 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10002 {
10003 // Don't do anything if already at suggested indent
10004 // and there is any other cursor which is not
10005 if has_some_cursor_in_whitespace
10006 && cursor.column == current_indent.len
10007 && current_indent.len == suggested_indent.len
10008 {
10009 continue;
10010 }
10011
10012 // Adjust line and move cursor to suggested indent
10013 // if cursor is not at suggested indent
10014 if cursor.column < suggested_indent.len
10015 && cursor.column <= current_indent.len
10016 && current_indent.len <= suggested_indent.len
10017 {
10018 selection.start = Point::new(cursor.row, suggested_indent.len);
10019 selection.end = selection.start;
10020 if row_delta == 0 {
10021 edits.extend(Buffer::edit_for_indent_size_adjustment(
10022 cursor.row,
10023 current_indent,
10024 suggested_indent,
10025 ));
10026 row_delta = suggested_indent.len - current_indent.len;
10027 }
10028 continue;
10029 }
10030
10031 // If current indent is more than suggested indent
10032 // only move cursor to current indent and skip indent
10033 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10034 selection.start = Point::new(cursor.row, current_indent.len);
10035 selection.end = selection.start;
10036 continue;
10037 }
10038 }
10039
10040 // Otherwise, insert a hard or soft tab.
10041 let settings = buffer.language_settings_at(cursor, cx);
10042 let tab_size = if settings.hard_tabs {
10043 IndentSize::tab()
10044 } else {
10045 let tab_size = settings.tab_size.get();
10046 let indent_remainder = snapshot
10047 .text_for_range(Point::new(cursor.row, 0)..cursor)
10048 .flat_map(str::chars)
10049 .fold(row_delta % tab_size, |counter: u32, c| {
10050 if c == '\t' {
10051 0
10052 } else {
10053 (counter + 1) % tab_size
10054 }
10055 });
10056
10057 let chars_to_next_tab_stop = tab_size - indent_remainder;
10058 IndentSize::spaces(chars_to_next_tab_stop)
10059 };
10060 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10061 selection.end = selection.start;
10062 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10063 row_delta += tab_size.len;
10064 }
10065
10066 self.transact(window, cx, |this, window, cx| {
10067 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10068 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10069 this.refresh_edit_prediction(true, false, window, cx);
10070 });
10071 }
10072
10073 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10074 if self.read_only(cx) {
10075 return;
10076 }
10077 if self.mode.is_single_line() {
10078 cx.propagate();
10079 return;
10080 }
10081
10082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10083 let mut selections = self.selections.all::<Point>(cx);
10084 let mut prev_edited_row = 0;
10085 let mut row_delta = 0;
10086 let mut edits = Vec::new();
10087 let buffer = self.buffer.read(cx);
10088 let snapshot = buffer.snapshot(cx);
10089 for selection in &mut selections {
10090 if selection.start.row != prev_edited_row {
10091 row_delta = 0;
10092 }
10093 prev_edited_row = selection.end.row;
10094
10095 row_delta =
10096 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10097 }
10098
10099 self.transact(window, cx, |this, window, cx| {
10100 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10101 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10102 });
10103 }
10104
10105 fn indent_selection(
10106 buffer: &MultiBuffer,
10107 snapshot: &MultiBufferSnapshot,
10108 selection: &mut Selection<Point>,
10109 edits: &mut Vec<(Range<Point>, String)>,
10110 delta_for_start_row: u32,
10111 cx: &App,
10112 ) -> u32 {
10113 let settings = buffer.language_settings_at(selection.start, cx);
10114 let tab_size = settings.tab_size.get();
10115 let indent_kind = if settings.hard_tabs {
10116 IndentKind::Tab
10117 } else {
10118 IndentKind::Space
10119 };
10120 let mut start_row = selection.start.row;
10121 let mut end_row = selection.end.row + 1;
10122
10123 // If a selection ends at the beginning of a line, don't indent
10124 // that last line.
10125 if selection.end.column == 0 && selection.end.row > selection.start.row {
10126 end_row -= 1;
10127 }
10128
10129 // Avoid re-indenting a row that has already been indented by a
10130 // previous selection, but still update this selection's column
10131 // to reflect that indentation.
10132 if delta_for_start_row > 0 {
10133 start_row += 1;
10134 selection.start.column += delta_for_start_row;
10135 if selection.end.row == selection.start.row {
10136 selection.end.column += delta_for_start_row;
10137 }
10138 }
10139
10140 let mut delta_for_end_row = 0;
10141 let has_multiple_rows = start_row + 1 != end_row;
10142 for row in start_row..end_row {
10143 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10144 let indent_delta = match (current_indent.kind, indent_kind) {
10145 (IndentKind::Space, IndentKind::Space) => {
10146 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10147 IndentSize::spaces(columns_to_next_tab_stop)
10148 }
10149 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10150 (_, IndentKind::Tab) => IndentSize::tab(),
10151 };
10152
10153 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10154 0
10155 } else {
10156 selection.start.column
10157 };
10158 let row_start = Point::new(row, start);
10159 edits.push((
10160 row_start..row_start,
10161 indent_delta.chars().collect::<String>(),
10162 ));
10163
10164 // Update this selection's endpoints to reflect the indentation.
10165 if row == selection.start.row {
10166 selection.start.column += indent_delta.len;
10167 }
10168 if row == selection.end.row {
10169 selection.end.column += indent_delta.len;
10170 delta_for_end_row = indent_delta.len;
10171 }
10172 }
10173
10174 if selection.start.row == selection.end.row {
10175 delta_for_start_row + delta_for_end_row
10176 } else {
10177 delta_for_end_row
10178 }
10179 }
10180
10181 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10182 if self.read_only(cx) {
10183 return;
10184 }
10185 if self.mode.is_single_line() {
10186 cx.propagate();
10187 return;
10188 }
10189
10190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10192 let selections = self.selections.all::<Point>(cx);
10193 let mut deletion_ranges = Vec::new();
10194 let mut last_outdent = None;
10195 {
10196 let buffer = self.buffer.read(cx);
10197 let snapshot = buffer.snapshot(cx);
10198 for selection in &selections {
10199 let settings = buffer.language_settings_at(selection.start, cx);
10200 let tab_size = settings.tab_size.get();
10201 let mut rows = selection.spanned_rows(false, &display_map);
10202
10203 // Avoid re-outdenting a row that has already been outdented by a
10204 // previous selection.
10205 if let Some(last_row) = last_outdent
10206 && last_row == rows.start
10207 {
10208 rows.start = rows.start.next_row();
10209 }
10210 let has_multiple_rows = rows.len() > 1;
10211 for row in rows.iter_rows() {
10212 let indent_size = snapshot.indent_size_for_line(row);
10213 if indent_size.len > 0 {
10214 let deletion_len = match indent_size.kind {
10215 IndentKind::Space => {
10216 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10217 if columns_to_prev_tab_stop == 0 {
10218 tab_size
10219 } else {
10220 columns_to_prev_tab_stop
10221 }
10222 }
10223 IndentKind::Tab => 1,
10224 };
10225 let start = if has_multiple_rows
10226 || deletion_len > selection.start.column
10227 || indent_size.len < selection.start.column
10228 {
10229 0
10230 } else {
10231 selection.start.column - deletion_len
10232 };
10233 deletion_ranges.push(
10234 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10235 );
10236 last_outdent = Some(row);
10237 }
10238 }
10239 }
10240 }
10241
10242 self.transact(window, cx, |this, window, cx| {
10243 this.buffer.update(cx, |buffer, cx| {
10244 let empty_str: Arc<str> = Arc::default();
10245 buffer.edit(
10246 deletion_ranges
10247 .into_iter()
10248 .map(|range| (range, empty_str.clone())),
10249 None,
10250 cx,
10251 );
10252 });
10253 let selections = this.selections.all::<usize>(cx);
10254 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10255 });
10256 }
10257
10258 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10259 if self.read_only(cx) {
10260 return;
10261 }
10262 if self.mode.is_single_line() {
10263 cx.propagate();
10264 return;
10265 }
10266
10267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10268 let selections = self
10269 .selections
10270 .all::<usize>(cx)
10271 .into_iter()
10272 .map(|s| s.range());
10273
10274 self.transact(window, cx, |this, window, cx| {
10275 this.buffer.update(cx, |buffer, cx| {
10276 buffer.autoindent_ranges(selections, cx);
10277 });
10278 let selections = this.selections.all::<usize>(cx);
10279 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10280 });
10281 }
10282
10283 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10284 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10286 let selections = self.selections.all::<Point>(cx);
10287
10288 let mut new_cursors = Vec::new();
10289 let mut edit_ranges = Vec::new();
10290 let mut selections = selections.iter().peekable();
10291 while let Some(selection) = selections.next() {
10292 let mut rows = selection.spanned_rows(false, &display_map);
10293 let goal_display_column = selection.head().to_display_point(&display_map).column();
10294
10295 // Accumulate contiguous regions of rows that we want to delete.
10296 while let Some(next_selection) = selections.peek() {
10297 let next_rows = next_selection.spanned_rows(false, &display_map);
10298 if next_rows.start <= rows.end {
10299 rows.end = next_rows.end;
10300 selections.next().unwrap();
10301 } else {
10302 break;
10303 }
10304 }
10305
10306 let buffer = &display_map.buffer_snapshot;
10307 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10308 let edit_end;
10309 let cursor_buffer_row;
10310 if buffer.max_point().row >= rows.end.0 {
10311 // If there's a line after the range, delete the \n from the end of the row range
10312 // and position the cursor on the next line.
10313 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10314 cursor_buffer_row = rows.end;
10315 } else {
10316 // If there isn't a line after the range, delete the \n from the line before the
10317 // start of the row range and position the cursor there.
10318 edit_start = edit_start.saturating_sub(1);
10319 edit_end = buffer.len();
10320 cursor_buffer_row = rows.start.previous_row();
10321 }
10322
10323 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10324 *cursor.column_mut() =
10325 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10326
10327 new_cursors.push((
10328 selection.id,
10329 buffer.anchor_after(cursor.to_point(&display_map)),
10330 ));
10331 edit_ranges.push(edit_start..edit_end);
10332 }
10333
10334 self.transact(window, cx, |this, window, cx| {
10335 let buffer = this.buffer.update(cx, |buffer, cx| {
10336 let empty_str: Arc<str> = Arc::default();
10337 buffer.edit(
10338 edit_ranges
10339 .into_iter()
10340 .map(|range| (range, empty_str.clone())),
10341 None,
10342 cx,
10343 );
10344 buffer.snapshot(cx)
10345 });
10346 let new_selections = new_cursors
10347 .into_iter()
10348 .map(|(id, cursor)| {
10349 let cursor = cursor.to_point(&buffer);
10350 Selection {
10351 id,
10352 start: cursor,
10353 end: cursor,
10354 reversed: false,
10355 goal: SelectionGoal::None,
10356 }
10357 })
10358 .collect();
10359
10360 this.change_selections(Default::default(), window, cx, |s| {
10361 s.select(new_selections);
10362 });
10363 });
10364 }
10365
10366 pub fn join_lines_impl(
10367 &mut self,
10368 insert_whitespace: bool,
10369 window: &mut Window,
10370 cx: &mut Context<Self>,
10371 ) {
10372 if self.read_only(cx) {
10373 return;
10374 }
10375 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10376 for selection in self.selections.all::<Point>(cx) {
10377 let start = MultiBufferRow(selection.start.row);
10378 // Treat single line selections as if they include the next line. Otherwise this action
10379 // would do nothing for single line selections individual cursors.
10380 let end = if selection.start.row == selection.end.row {
10381 MultiBufferRow(selection.start.row + 1)
10382 } else {
10383 MultiBufferRow(selection.end.row)
10384 };
10385
10386 if let Some(last_row_range) = row_ranges.last_mut()
10387 && start <= last_row_range.end
10388 {
10389 last_row_range.end = end;
10390 continue;
10391 }
10392 row_ranges.push(start..end);
10393 }
10394
10395 let snapshot = self.buffer.read(cx).snapshot(cx);
10396 let mut cursor_positions = Vec::new();
10397 for row_range in &row_ranges {
10398 let anchor = snapshot.anchor_before(Point::new(
10399 row_range.end.previous_row().0,
10400 snapshot.line_len(row_range.end.previous_row()),
10401 ));
10402 cursor_positions.push(anchor..anchor);
10403 }
10404
10405 self.transact(window, cx, |this, window, cx| {
10406 for row_range in row_ranges.into_iter().rev() {
10407 for row in row_range.iter_rows().rev() {
10408 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10409 let next_line_row = row.next_row();
10410 let indent = snapshot.indent_size_for_line(next_line_row);
10411 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10412
10413 let replace =
10414 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10415 " "
10416 } else {
10417 ""
10418 };
10419
10420 this.buffer.update(cx, |buffer, cx| {
10421 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10422 });
10423 }
10424 }
10425
10426 this.change_selections(Default::default(), window, cx, |s| {
10427 s.select_anchor_ranges(cursor_positions)
10428 });
10429 });
10430 }
10431
10432 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10433 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10434 self.join_lines_impl(true, window, cx);
10435 }
10436
10437 pub fn sort_lines_case_sensitive(
10438 &mut self,
10439 _: &SortLinesCaseSensitive,
10440 window: &mut Window,
10441 cx: &mut Context<Self>,
10442 ) {
10443 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10444 }
10445
10446 pub fn sort_lines_by_length(
10447 &mut self,
10448 _: &SortLinesByLength,
10449 window: &mut Window,
10450 cx: &mut Context<Self>,
10451 ) {
10452 self.manipulate_immutable_lines(window, cx, |lines| {
10453 lines.sort_by_key(|&line| line.chars().count())
10454 })
10455 }
10456
10457 pub fn sort_lines_case_insensitive(
10458 &mut self,
10459 _: &SortLinesCaseInsensitive,
10460 window: &mut Window,
10461 cx: &mut Context<Self>,
10462 ) {
10463 self.manipulate_immutable_lines(window, cx, |lines| {
10464 lines.sort_by_key(|line| line.to_lowercase())
10465 })
10466 }
10467
10468 pub fn unique_lines_case_insensitive(
10469 &mut self,
10470 _: &UniqueLinesCaseInsensitive,
10471 window: &mut Window,
10472 cx: &mut Context<Self>,
10473 ) {
10474 self.manipulate_immutable_lines(window, cx, |lines| {
10475 let mut seen = HashSet::default();
10476 lines.retain(|line| seen.insert(line.to_lowercase()));
10477 })
10478 }
10479
10480 pub fn unique_lines_case_sensitive(
10481 &mut self,
10482 _: &UniqueLinesCaseSensitive,
10483 window: &mut Window,
10484 cx: &mut Context<Self>,
10485 ) {
10486 self.manipulate_immutable_lines(window, cx, |lines| {
10487 let mut seen = HashSet::default();
10488 lines.retain(|line| seen.insert(*line));
10489 })
10490 }
10491
10492 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10493 let snapshot = self.buffer.read(cx).snapshot(cx);
10494 for selection in self.selections.disjoint_anchors().iter() {
10495 if snapshot
10496 .language_at(selection.start)
10497 .and_then(|lang| lang.config().wrap_characters.as_ref())
10498 .is_some()
10499 {
10500 return true;
10501 }
10502 }
10503 false
10504 }
10505
10506 fn wrap_selections_in_tag(
10507 &mut self,
10508 _: &WrapSelectionsInTag,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10513
10514 let snapshot = self.buffer.read(cx).snapshot(cx);
10515
10516 let mut edits = Vec::new();
10517 let mut boundaries = Vec::new();
10518
10519 for selection in self.selections.all::<Point>(cx).iter() {
10520 let Some(wrap_config) = snapshot
10521 .language_at(selection.start)
10522 .and_then(|lang| lang.config().wrap_characters.clone())
10523 else {
10524 continue;
10525 };
10526
10527 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10528 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10529
10530 let start_before = snapshot.anchor_before(selection.start);
10531 let end_after = snapshot.anchor_after(selection.end);
10532
10533 edits.push((start_before..start_before, open_tag));
10534 edits.push((end_after..end_after, close_tag));
10535
10536 boundaries.push((
10537 start_before,
10538 end_after,
10539 wrap_config.start_prefix.len(),
10540 wrap_config.end_suffix.len(),
10541 ));
10542 }
10543
10544 if edits.is_empty() {
10545 return;
10546 }
10547
10548 self.transact(window, cx, |this, window, cx| {
10549 let buffer = this.buffer.update(cx, |buffer, cx| {
10550 buffer.edit(edits, None, cx);
10551 buffer.snapshot(cx)
10552 });
10553
10554 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10555 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10556 boundaries.into_iter()
10557 {
10558 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10559 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10560 new_selections.push(open_offset..open_offset);
10561 new_selections.push(close_offset..close_offset);
10562 }
10563
10564 this.change_selections(Default::default(), window, cx, |s| {
10565 s.select_ranges(new_selections);
10566 });
10567
10568 this.request_autoscroll(Autoscroll::fit(), cx);
10569 });
10570 }
10571
10572 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10573 let Some(project) = self.project.clone() else {
10574 return;
10575 };
10576 self.reload(project, window, cx)
10577 .detach_and_notify_err(window, cx);
10578 }
10579
10580 pub fn restore_file(
10581 &mut self,
10582 _: &::git::RestoreFile,
10583 window: &mut Window,
10584 cx: &mut Context<Self>,
10585 ) {
10586 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10587 let mut buffer_ids = HashSet::default();
10588 let snapshot = self.buffer().read(cx).snapshot(cx);
10589 for selection in self.selections.all::<usize>(cx) {
10590 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10591 }
10592
10593 let buffer = self.buffer().read(cx);
10594 let ranges = buffer_ids
10595 .into_iter()
10596 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10597 .collect::<Vec<_>>();
10598
10599 self.restore_hunks_in_ranges(ranges, window, cx);
10600 }
10601
10602 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10603 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10604 let selections = self
10605 .selections
10606 .all(cx)
10607 .into_iter()
10608 .map(|s| s.range())
10609 .collect();
10610 self.restore_hunks_in_ranges(selections, window, cx);
10611 }
10612
10613 pub fn restore_hunks_in_ranges(
10614 &mut self,
10615 ranges: Vec<Range<Point>>,
10616 window: &mut Window,
10617 cx: &mut Context<Editor>,
10618 ) {
10619 let mut revert_changes = HashMap::default();
10620 let chunk_by = self
10621 .snapshot(window, cx)
10622 .hunks_for_ranges(ranges)
10623 .into_iter()
10624 .chunk_by(|hunk| hunk.buffer_id);
10625 for (buffer_id, hunks) in &chunk_by {
10626 let hunks = hunks.collect::<Vec<_>>();
10627 for hunk in &hunks {
10628 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10629 }
10630 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10631 }
10632 drop(chunk_by);
10633 if !revert_changes.is_empty() {
10634 self.transact(window, cx, |editor, window, cx| {
10635 editor.restore(revert_changes, window, cx);
10636 });
10637 }
10638 }
10639
10640 pub fn open_active_item_in_terminal(
10641 &mut self,
10642 _: &OpenInTerminal,
10643 window: &mut Window,
10644 cx: &mut Context<Self>,
10645 ) {
10646 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10647 let project_path = buffer.read(cx).project_path(cx)?;
10648 let project = self.project()?.read(cx);
10649 let entry = project.entry_for_path(&project_path, cx)?;
10650 let parent = match &entry.canonical_path {
10651 Some(canonical_path) => canonical_path.to_path_buf(),
10652 None => project.absolute_path(&project_path, cx)?,
10653 }
10654 .parent()?
10655 .to_path_buf();
10656 Some(parent)
10657 }) {
10658 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10659 }
10660 }
10661
10662 fn set_breakpoint_context_menu(
10663 &mut self,
10664 display_row: DisplayRow,
10665 position: Option<Anchor>,
10666 clicked_point: gpui::Point<Pixels>,
10667 window: &mut Window,
10668 cx: &mut Context<Self>,
10669 ) {
10670 let source = self
10671 .buffer
10672 .read(cx)
10673 .snapshot(cx)
10674 .anchor_before(Point::new(display_row.0, 0u32));
10675
10676 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10677
10678 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10679 self,
10680 source,
10681 clicked_point,
10682 context_menu,
10683 window,
10684 cx,
10685 );
10686 }
10687
10688 fn add_edit_breakpoint_block(
10689 &mut self,
10690 anchor: Anchor,
10691 breakpoint: &Breakpoint,
10692 edit_action: BreakpointPromptEditAction,
10693 window: &mut Window,
10694 cx: &mut Context<Self>,
10695 ) {
10696 let weak_editor = cx.weak_entity();
10697 let bp_prompt = cx.new(|cx| {
10698 BreakpointPromptEditor::new(
10699 weak_editor,
10700 anchor,
10701 breakpoint.clone(),
10702 edit_action,
10703 window,
10704 cx,
10705 )
10706 });
10707
10708 let height = bp_prompt.update(cx, |this, cx| {
10709 this.prompt
10710 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10711 });
10712 let cloned_prompt = bp_prompt.clone();
10713 let blocks = vec![BlockProperties {
10714 style: BlockStyle::Sticky,
10715 placement: BlockPlacement::Above(anchor),
10716 height: Some(height),
10717 render: Arc::new(move |cx| {
10718 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10719 cloned_prompt.clone().into_any_element()
10720 }),
10721 priority: 0,
10722 }];
10723
10724 let focus_handle = bp_prompt.focus_handle(cx);
10725 window.focus(&focus_handle);
10726
10727 let block_ids = self.insert_blocks(blocks, None, cx);
10728 bp_prompt.update(cx, |prompt, _| {
10729 prompt.add_block_ids(block_ids);
10730 });
10731 }
10732
10733 pub(crate) fn breakpoint_at_row(
10734 &self,
10735 row: u32,
10736 window: &mut Window,
10737 cx: &mut Context<Self>,
10738 ) -> Option<(Anchor, Breakpoint)> {
10739 let snapshot = self.snapshot(window, cx);
10740 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10741
10742 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10743 }
10744
10745 pub(crate) fn breakpoint_at_anchor(
10746 &self,
10747 breakpoint_position: Anchor,
10748 snapshot: &EditorSnapshot,
10749 cx: &mut Context<Self>,
10750 ) -> Option<(Anchor, Breakpoint)> {
10751 let buffer = self
10752 .buffer
10753 .read(cx)
10754 .buffer_for_anchor(breakpoint_position, cx)?;
10755
10756 let enclosing_excerpt = breakpoint_position.excerpt_id;
10757 let buffer_snapshot = buffer.read(cx).snapshot();
10758
10759 let row = buffer_snapshot
10760 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10761 .row;
10762
10763 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10764 let anchor_end = snapshot
10765 .buffer_snapshot
10766 .anchor_after(Point::new(row, line_len));
10767
10768 self.breakpoint_store
10769 .as_ref()?
10770 .read_with(cx, |breakpoint_store, cx| {
10771 breakpoint_store
10772 .breakpoints(
10773 &buffer,
10774 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10775 &buffer_snapshot,
10776 cx,
10777 )
10778 .next()
10779 .and_then(|(bp, _)| {
10780 let breakpoint_row = buffer_snapshot
10781 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10782 .row;
10783
10784 if breakpoint_row == row {
10785 snapshot
10786 .buffer_snapshot
10787 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10788 .map(|position| (position, bp.bp.clone()))
10789 } else {
10790 None
10791 }
10792 })
10793 })
10794 }
10795
10796 pub fn edit_log_breakpoint(
10797 &mut self,
10798 _: &EditLogBreakpoint,
10799 window: &mut Window,
10800 cx: &mut Context<Self>,
10801 ) {
10802 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10803 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10804 message: None,
10805 state: BreakpointState::Enabled,
10806 condition: None,
10807 hit_condition: None,
10808 });
10809
10810 self.add_edit_breakpoint_block(
10811 anchor,
10812 &breakpoint,
10813 BreakpointPromptEditAction::Log,
10814 window,
10815 cx,
10816 );
10817 }
10818 }
10819
10820 fn breakpoints_at_cursors(
10821 &self,
10822 window: &mut Window,
10823 cx: &mut Context<Self>,
10824 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10825 let snapshot = self.snapshot(window, cx);
10826 let cursors = self
10827 .selections
10828 .disjoint_anchors()
10829 .iter()
10830 .map(|selection| {
10831 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10832
10833 let breakpoint_position = self
10834 .breakpoint_at_row(cursor_position.row, window, cx)
10835 .map(|bp| bp.0)
10836 .unwrap_or_else(|| {
10837 snapshot
10838 .display_snapshot
10839 .buffer_snapshot
10840 .anchor_after(Point::new(cursor_position.row, 0))
10841 });
10842
10843 let breakpoint = self
10844 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10845 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10846
10847 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10848 })
10849 // 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.
10850 .collect::<HashMap<Anchor, _>>();
10851
10852 cursors.into_iter().collect()
10853 }
10854
10855 pub fn enable_breakpoint(
10856 &mut self,
10857 _: &crate::actions::EnableBreakpoint,
10858 window: &mut Window,
10859 cx: &mut Context<Self>,
10860 ) {
10861 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10862 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10863 continue;
10864 };
10865 self.edit_breakpoint_at_anchor(
10866 anchor,
10867 breakpoint,
10868 BreakpointEditAction::InvertState,
10869 cx,
10870 );
10871 }
10872 }
10873
10874 pub fn disable_breakpoint(
10875 &mut self,
10876 _: &crate::actions::DisableBreakpoint,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10881 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10882 continue;
10883 };
10884 self.edit_breakpoint_at_anchor(
10885 anchor,
10886 breakpoint,
10887 BreakpointEditAction::InvertState,
10888 cx,
10889 );
10890 }
10891 }
10892
10893 pub fn toggle_breakpoint(
10894 &mut self,
10895 _: &crate::actions::ToggleBreakpoint,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10900 if let Some(breakpoint) = breakpoint {
10901 self.edit_breakpoint_at_anchor(
10902 anchor,
10903 breakpoint,
10904 BreakpointEditAction::Toggle,
10905 cx,
10906 );
10907 } else {
10908 self.edit_breakpoint_at_anchor(
10909 anchor,
10910 Breakpoint::new_standard(),
10911 BreakpointEditAction::Toggle,
10912 cx,
10913 );
10914 }
10915 }
10916 }
10917
10918 pub fn edit_breakpoint_at_anchor(
10919 &mut self,
10920 breakpoint_position: Anchor,
10921 breakpoint: Breakpoint,
10922 edit_action: BreakpointEditAction,
10923 cx: &mut Context<Self>,
10924 ) {
10925 let Some(breakpoint_store) = &self.breakpoint_store else {
10926 return;
10927 };
10928
10929 let Some(buffer) = self
10930 .buffer
10931 .read(cx)
10932 .buffer_for_anchor(breakpoint_position, cx)
10933 else {
10934 return;
10935 };
10936
10937 breakpoint_store.update(cx, |breakpoint_store, cx| {
10938 breakpoint_store.toggle_breakpoint(
10939 buffer,
10940 BreakpointWithPosition {
10941 position: breakpoint_position.text_anchor,
10942 bp: breakpoint,
10943 },
10944 edit_action,
10945 cx,
10946 );
10947 });
10948
10949 cx.notify();
10950 }
10951
10952 #[cfg(any(test, feature = "test-support"))]
10953 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10954 self.breakpoint_store.clone()
10955 }
10956
10957 pub fn prepare_restore_change(
10958 &self,
10959 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10960 hunk: &MultiBufferDiffHunk,
10961 cx: &mut App,
10962 ) -> Option<()> {
10963 if hunk.is_created_file() {
10964 return None;
10965 }
10966 let buffer = self.buffer.read(cx);
10967 let diff = buffer.diff_for(hunk.buffer_id)?;
10968 let buffer = buffer.buffer(hunk.buffer_id)?;
10969 let buffer = buffer.read(cx);
10970 let original_text = diff
10971 .read(cx)
10972 .base_text()
10973 .as_rope()
10974 .slice(hunk.diff_base_byte_range.clone());
10975 let buffer_snapshot = buffer.snapshot();
10976 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10977 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10978 probe
10979 .0
10980 .start
10981 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10982 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10983 }) {
10984 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10985 Some(())
10986 } else {
10987 None
10988 }
10989 }
10990
10991 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10992 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10993 }
10994
10995 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10996 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
10997 }
10998
10999 fn manipulate_lines<M>(
11000 &mut self,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 mut manipulate: M,
11004 ) where
11005 M: FnMut(&str) -> LineManipulationResult,
11006 {
11007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11008
11009 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11010 let buffer = self.buffer.read(cx).snapshot(cx);
11011
11012 let mut edits = Vec::new();
11013
11014 let selections = self.selections.all::<Point>(cx);
11015 let mut selections = selections.iter().peekable();
11016 let mut contiguous_row_selections = Vec::new();
11017 let mut new_selections = Vec::new();
11018 let mut added_lines = 0;
11019 let mut removed_lines = 0;
11020
11021 while let Some(selection) = selections.next() {
11022 let (start_row, end_row) = consume_contiguous_rows(
11023 &mut contiguous_row_selections,
11024 selection,
11025 &display_map,
11026 &mut selections,
11027 );
11028
11029 let start_point = Point::new(start_row.0, 0);
11030 let end_point = Point::new(
11031 end_row.previous_row().0,
11032 buffer.line_len(end_row.previous_row()),
11033 );
11034 let text = buffer
11035 .text_for_range(start_point..end_point)
11036 .collect::<String>();
11037
11038 let LineManipulationResult {
11039 new_text,
11040 line_count_before,
11041 line_count_after,
11042 } = manipulate(&text);
11043
11044 edits.push((start_point..end_point, new_text));
11045
11046 // Selections must change based on added and removed line count
11047 let start_row =
11048 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11049 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11050 new_selections.push(Selection {
11051 id: selection.id,
11052 start: start_row,
11053 end: end_row,
11054 goal: SelectionGoal::None,
11055 reversed: selection.reversed,
11056 });
11057
11058 if line_count_after > line_count_before {
11059 added_lines += line_count_after - line_count_before;
11060 } else if line_count_before > line_count_after {
11061 removed_lines += line_count_before - line_count_after;
11062 }
11063 }
11064
11065 self.transact(window, cx, |this, window, cx| {
11066 let buffer = this.buffer.update(cx, |buffer, cx| {
11067 buffer.edit(edits, None, cx);
11068 buffer.snapshot(cx)
11069 });
11070
11071 // Recalculate offsets on newly edited buffer
11072 let new_selections = new_selections
11073 .iter()
11074 .map(|s| {
11075 let start_point = Point::new(s.start.0, 0);
11076 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11077 Selection {
11078 id: s.id,
11079 start: buffer.point_to_offset(start_point),
11080 end: buffer.point_to_offset(end_point),
11081 goal: s.goal,
11082 reversed: s.reversed,
11083 }
11084 })
11085 .collect();
11086
11087 this.change_selections(Default::default(), window, cx, |s| {
11088 s.select(new_selections);
11089 });
11090
11091 this.request_autoscroll(Autoscroll::fit(), cx);
11092 });
11093 }
11094
11095 fn manipulate_immutable_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<&str>),
11102 {
11103 self.manipulate_lines(window, cx, |text| {
11104 let mut lines: Vec<&str> = text.split('\n').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 fn manipulate_mutable_lines<Fn>(
11118 &mut self,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 mut callback: Fn,
11122 ) where
11123 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11124 {
11125 self.manipulate_lines(window, cx, |text| {
11126 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11127 let line_count_before = lines.len();
11128
11129 callback(&mut lines);
11130
11131 LineManipulationResult {
11132 new_text: lines.join("\n"),
11133 line_count_before,
11134 line_count_after: lines.len(),
11135 }
11136 });
11137 }
11138
11139 pub fn convert_indentation_to_spaces(
11140 &mut self,
11141 _: &ConvertIndentationToSpaces,
11142 window: &mut Window,
11143 cx: &mut Context<Self>,
11144 ) {
11145 let settings = self.buffer.read(cx).language_settings(cx);
11146 let tab_size = settings.tab_size.get() as usize;
11147
11148 self.manipulate_mutable_lines(window, cx, |lines| {
11149 // Allocates a reasonably sized scratch buffer once for the whole loop
11150 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11151 // Avoids recomputing spaces that could be inserted many times
11152 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11153 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11154 .collect();
11155
11156 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11157 let mut chars = line.as_ref().chars();
11158 let mut col = 0;
11159 let mut changed = false;
11160
11161 for ch in chars.by_ref() {
11162 match ch {
11163 ' ' => {
11164 reindented_line.push(' ');
11165 col += 1;
11166 }
11167 '\t' => {
11168 // \t are converted to spaces depending on the current column
11169 let spaces_len = tab_size - (col % tab_size);
11170 reindented_line.extend(&space_cache[spaces_len - 1]);
11171 col += spaces_len;
11172 changed = true;
11173 }
11174 _ => {
11175 // If we dont append before break, the character is consumed
11176 reindented_line.push(ch);
11177 break;
11178 }
11179 }
11180 }
11181
11182 if !changed {
11183 reindented_line.clear();
11184 continue;
11185 }
11186 // Append the rest of the line and replace old reference with new one
11187 reindented_line.extend(chars);
11188 *line = Cow::Owned(reindented_line.clone());
11189 reindented_line.clear();
11190 }
11191 });
11192 }
11193
11194 pub fn convert_indentation_to_tabs(
11195 &mut self,
11196 _: &ConvertIndentationToTabs,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 let settings = self.buffer.read(cx).language_settings(cx);
11201 let tab_size = settings.tab_size.get() as usize;
11202
11203 self.manipulate_mutable_lines(window, cx, |lines| {
11204 // Allocates a reasonably sized buffer once for the whole loop
11205 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11206 // Avoids recomputing spaces that could be inserted many times
11207 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11208 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11209 .collect();
11210
11211 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11212 let mut chars = line.chars();
11213 let mut spaces_count = 0;
11214 let mut first_non_indent_char = None;
11215 let mut changed = false;
11216
11217 for ch in chars.by_ref() {
11218 match ch {
11219 ' ' => {
11220 // Keep track of spaces. Append \t when we reach tab_size
11221 spaces_count += 1;
11222 changed = true;
11223 if spaces_count == tab_size {
11224 reindented_line.push('\t');
11225 spaces_count = 0;
11226 }
11227 }
11228 '\t' => {
11229 reindented_line.push('\t');
11230 spaces_count = 0;
11231 }
11232 _ => {
11233 // Dont append it yet, we might have remaining spaces
11234 first_non_indent_char = Some(ch);
11235 break;
11236 }
11237 }
11238 }
11239
11240 if !changed {
11241 reindented_line.clear();
11242 continue;
11243 }
11244 // Remaining spaces that didn't make a full tab stop
11245 if spaces_count > 0 {
11246 reindented_line.extend(&space_cache[spaces_count - 1]);
11247 }
11248 // If we consume an extra character that was not indentation, add it back
11249 if let Some(extra_char) = first_non_indent_char {
11250 reindented_line.push(extra_char);
11251 }
11252 // Append the rest of the line and replace old reference with new one
11253 reindented_line.extend(chars);
11254 *line = Cow::Owned(reindented_line.clone());
11255 reindented_line.clear();
11256 }
11257 });
11258 }
11259
11260 pub fn convert_to_upper_case(
11261 &mut self,
11262 _: &ConvertToUpperCase,
11263 window: &mut Window,
11264 cx: &mut Context<Self>,
11265 ) {
11266 self.manipulate_text(window, cx, |text| text.to_uppercase())
11267 }
11268
11269 pub fn convert_to_lower_case(
11270 &mut self,
11271 _: &ConvertToLowerCase,
11272 window: &mut Window,
11273 cx: &mut Context<Self>,
11274 ) {
11275 self.manipulate_text(window, cx, |text| text.to_lowercase())
11276 }
11277
11278 pub fn convert_to_title_case(
11279 &mut self,
11280 _: &ConvertToTitleCase,
11281 window: &mut Window,
11282 cx: &mut Context<Self>,
11283 ) {
11284 self.manipulate_text(window, cx, |text| {
11285 text.split('\n')
11286 .map(|line| line.to_case(Case::Title))
11287 .join("\n")
11288 })
11289 }
11290
11291 pub fn convert_to_snake_case(
11292 &mut self,
11293 _: &ConvertToSnakeCase,
11294 window: &mut Window,
11295 cx: &mut Context<Self>,
11296 ) {
11297 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11298 }
11299
11300 pub fn convert_to_kebab_case(
11301 &mut self,
11302 _: &ConvertToKebabCase,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11307 }
11308
11309 pub fn convert_to_upper_camel_case(
11310 &mut self,
11311 _: &ConvertToUpperCamelCase,
11312 window: &mut Window,
11313 cx: &mut Context<Self>,
11314 ) {
11315 self.manipulate_text(window, cx, |text| {
11316 text.split('\n')
11317 .map(|line| line.to_case(Case::UpperCamel))
11318 .join("\n")
11319 })
11320 }
11321
11322 pub fn convert_to_lower_camel_case(
11323 &mut self,
11324 _: &ConvertToLowerCamelCase,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11329 }
11330
11331 pub fn convert_to_opposite_case(
11332 &mut self,
11333 _: &ConvertToOppositeCase,
11334 window: &mut Window,
11335 cx: &mut Context<Self>,
11336 ) {
11337 self.manipulate_text(window, cx, |text| {
11338 text.chars()
11339 .fold(String::with_capacity(text.len()), |mut t, c| {
11340 if c.is_uppercase() {
11341 t.extend(c.to_lowercase());
11342 } else {
11343 t.extend(c.to_uppercase());
11344 }
11345 t
11346 })
11347 })
11348 }
11349
11350 pub fn convert_to_sentence_case(
11351 &mut self,
11352 _: &ConvertToSentenceCase,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11357 }
11358
11359 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11360 self.manipulate_text(window, cx, |text| {
11361 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11362 if has_upper_case_characters {
11363 text.to_lowercase()
11364 } else {
11365 text.to_uppercase()
11366 }
11367 })
11368 }
11369
11370 pub fn convert_to_rot13(
11371 &mut self,
11372 _: &ConvertToRot13,
11373 window: &mut Window,
11374 cx: &mut Context<Self>,
11375 ) {
11376 self.manipulate_text(window, cx, |text| {
11377 text.chars()
11378 .map(|c| match c {
11379 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11380 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11381 _ => c,
11382 })
11383 .collect()
11384 })
11385 }
11386
11387 pub fn convert_to_rot47(
11388 &mut self,
11389 _: &ConvertToRot47,
11390 window: &mut Window,
11391 cx: &mut Context<Self>,
11392 ) {
11393 self.manipulate_text(window, cx, |text| {
11394 text.chars()
11395 .map(|c| {
11396 let code_point = c as u32;
11397 if code_point >= 33 && code_point <= 126 {
11398 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11399 }
11400 c
11401 })
11402 .collect()
11403 })
11404 }
11405
11406 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11407 where
11408 Fn: FnMut(&str) -> String,
11409 {
11410 let buffer = self.buffer.read(cx).snapshot(cx);
11411
11412 let mut new_selections = Vec::new();
11413 let mut edits = Vec::new();
11414 let mut selection_adjustment = 0i32;
11415
11416 for selection in self.selections.all_adjusted(cx) {
11417 let selection_is_empty = selection.is_empty();
11418
11419 let (start, end) = if selection_is_empty {
11420 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11421 (word_range.start, word_range.end)
11422 } else {
11423 (
11424 buffer.point_to_offset(selection.start),
11425 buffer.point_to_offset(selection.end),
11426 )
11427 };
11428
11429 let text = buffer.text_for_range(start..end).collect::<String>();
11430 let old_length = text.len() as i32;
11431 let text = callback(&text);
11432
11433 new_selections.push(Selection {
11434 start: (start as i32 - selection_adjustment) as usize,
11435 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11436 goal: SelectionGoal::None,
11437 id: selection.id,
11438 reversed: selection.reversed,
11439 });
11440
11441 selection_adjustment += old_length - text.len() as i32;
11442
11443 edits.push((start..end, text));
11444 }
11445
11446 self.transact(window, cx, |this, window, cx| {
11447 this.buffer.update(cx, |buffer, cx| {
11448 buffer.edit(edits, None, cx);
11449 });
11450
11451 this.change_selections(Default::default(), window, cx, |s| {
11452 s.select(new_selections);
11453 });
11454
11455 this.request_autoscroll(Autoscroll::fit(), cx);
11456 });
11457 }
11458
11459 pub fn move_selection_on_drop(
11460 &mut self,
11461 selection: &Selection<Anchor>,
11462 target: DisplayPoint,
11463 is_cut: bool,
11464 window: &mut Window,
11465 cx: &mut Context<Self>,
11466 ) {
11467 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11468 let buffer = &display_map.buffer_snapshot;
11469 let mut edits = Vec::new();
11470 let insert_point = display_map
11471 .clip_point(target, Bias::Left)
11472 .to_point(&display_map);
11473 let text = buffer
11474 .text_for_range(selection.start..selection.end)
11475 .collect::<String>();
11476 if is_cut {
11477 edits.push(((selection.start..selection.end), String::new()));
11478 }
11479 let insert_anchor = buffer.anchor_before(insert_point);
11480 edits.push(((insert_anchor..insert_anchor), text));
11481 let last_edit_start = insert_anchor.bias_left(buffer);
11482 let last_edit_end = insert_anchor.bias_right(buffer);
11483 self.transact(window, cx, |this, window, cx| {
11484 this.buffer.update(cx, |buffer, cx| {
11485 buffer.edit(edits, None, cx);
11486 });
11487 this.change_selections(Default::default(), window, cx, |s| {
11488 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11489 });
11490 });
11491 }
11492
11493 pub fn clear_selection_drag_state(&mut self) {
11494 self.selection_drag_state = SelectionDragState::None;
11495 }
11496
11497 pub fn duplicate(
11498 &mut self,
11499 upwards: bool,
11500 whole_lines: bool,
11501 window: &mut Window,
11502 cx: &mut Context<Self>,
11503 ) {
11504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11505
11506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11507 let buffer = &display_map.buffer_snapshot;
11508 let selections = self.selections.all::<Point>(cx);
11509
11510 let mut edits = Vec::new();
11511 let mut selections_iter = selections.iter().peekable();
11512 while let Some(selection) = selections_iter.next() {
11513 let mut rows = selection.spanned_rows(false, &display_map);
11514 // duplicate line-wise
11515 if whole_lines || selection.start == selection.end {
11516 // Avoid duplicating the same lines twice.
11517 while let Some(next_selection) = selections_iter.peek() {
11518 let next_rows = next_selection.spanned_rows(false, &display_map);
11519 if next_rows.start < rows.end {
11520 rows.end = next_rows.end;
11521 selections_iter.next().unwrap();
11522 } else {
11523 break;
11524 }
11525 }
11526
11527 // Copy the text from the selected row region and splice it either at the start
11528 // or end of the region.
11529 let start = Point::new(rows.start.0, 0);
11530 let end = Point::new(
11531 rows.end.previous_row().0,
11532 buffer.line_len(rows.end.previous_row()),
11533 );
11534 let text = buffer
11535 .text_for_range(start..end)
11536 .chain(Some("\n"))
11537 .collect::<String>();
11538 let insert_location = if upwards {
11539 Point::new(rows.end.0, 0)
11540 } else {
11541 start
11542 };
11543 edits.push((insert_location..insert_location, text));
11544 } else {
11545 // duplicate character-wise
11546 let start = selection.start;
11547 let end = selection.end;
11548 let text = buffer.text_for_range(start..end).collect::<String>();
11549 edits.push((selection.end..selection.end, text));
11550 }
11551 }
11552
11553 self.transact(window, cx, |this, _, cx| {
11554 this.buffer.update(cx, |buffer, cx| {
11555 buffer.edit(edits, None, cx);
11556 });
11557
11558 this.request_autoscroll(Autoscroll::fit(), cx);
11559 });
11560 }
11561
11562 pub fn duplicate_line_up(
11563 &mut self,
11564 _: &DuplicateLineUp,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 ) {
11568 self.duplicate(true, true, window, cx);
11569 }
11570
11571 pub fn duplicate_line_down(
11572 &mut self,
11573 _: &DuplicateLineDown,
11574 window: &mut Window,
11575 cx: &mut Context<Self>,
11576 ) {
11577 self.duplicate(false, true, window, cx);
11578 }
11579
11580 pub fn duplicate_selection(
11581 &mut self,
11582 _: &DuplicateSelection,
11583 window: &mut Window,
11584 cx: &mut Context<Self>,
11585 ) {
11586 self.duplicate(false, false, window, cx);
11587 }
11588
11589 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11591 if self.mode.is_single_line() {
11592 cx.propagate();
11593 return;
11594 }
11595
11596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11597 let buffer = self.buffer.read(cx).snapshot(cx);
11598
11599 let mut edits = Vec::new();
11600 let mut unfold_ranges = Vec::new();
11601 let mut refold_creases = Vec::new();
11602
11603 let selections = self.selections.all::<Point>(cx);
11604 let mut selections = selections.iter().peekable();
11605 let mut contiguous_row_selections = Vec::new();
11606 let mut new_selections = Vec::new();
11607
11608 while let Some(selection) = selections.next() {
11609 // Find all the selections that span a contiguous row range
11610 let (start_row, end_row) = consume_contiguous_rows(
11611 &mut contiguous_row_selections,
11612 selection,
11613 &display_map,
11614 &mut selections,
11615 );
11616
11617 // Move the text spanned by the row range to be before the line preceding the row range
11618 if start_row.0 > 0 {
11619 let range_to_move = Point::new(
11620 start_row.previous_row().0,
11621 buffer.line_len(start_row.previous_row()),
11622 )
11623 ..Point::new(
11624 end_row.previous_row().0,
11625 buffer.line_len(end_row.previous_row()),
11626 );
11627 let insertion_point = display_map
11628 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11629 .0;
11630
11631 // Don't move lines across excerpts
11632 if buffer
11633 .excerpt_containing(insertion_point..range_to_move.end)
11634 .is_some()
11635 {
11636 let text = buffer
11637 .text_for_range(range_to_move.clone())
11638 .flat_map(|s| s.chars())
11639 .skip(1)
11640 .chain(['\n'])
11641 .collect::<String>();
11642
11643 edits.push((
11644 buffer.anchor_after(range_to_move.start)
11645 ..buffer.anchor_before(range_to_move.end),
11646 String::new(),
11647 ));
11648 let insertion_anchor = buffer.anchor_after(insertion_point);
11649 edits.push((insertion_anchor..insertion_anchor, text));
11650
11651 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11652
11653 // Move selections up
11654 new_selections.extend(contiguous_row_selections.drain(..).map(
11655 |mut selection| {
11656 selection.start.row -= row_delta;
11657 selection.end.row -= row_delta;
11658 selection
11659 },
11660 ));
11661
11662 // Move folds up
11663 unfold_ranges.push(range_to_move.clone());
11664 for fold in display_map.folds_in_range(
11665 buffer.anchor_before(range_to_move.start)
11666 ..buffer.anchor_after(range_to_move.end),
11667 ) {
11668 let mut start = fold.range.start.to_point(&buffer);
11669 let mut end = fold.range.end.to_point(&buffer);
11670 start.row -= row_delta;
11671 end.row -= row_delta;
11672 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11673 }
11674 }
11675 }
11676
11677 // If we didn't move line(s), preserve the existing selections
11678 new_selections.append(&mut contiguous_row_selections);
11679 }
11680
11681 self.transact(window, cx, |this, window, cx| {
11682 this.unfold_ranges(&unfold_ranges, true, true, cx);
11683 this.buffer.update(cx, |buffer, cx| {
11684 for (range, text) in edits {
11685 buffer.edit([(range, text)], None, cx);
11686 }
11687 });
11688 this.fold_creases(refold_creases, true, window, cx);
11689 this.change_selections(Default::default(), window, cx, |s| {
11690 s.select(new_selections);
11691 })
11692 });
11693 }
11694
11695 pub fn move_line_down(
11696 &mut self,
11697 _: &MoveLineDown,
11698 window: &mut Window,
11699 cx: &mut Context<Self>,
11700 ) {
11701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11702 if self.mode.is_single_line() {
11703 cx.propagate();
11704 return;
11705 }
11706
11707 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11708 let buffer = self.buffer.read(cx).snapshot(cx);
11709
11710 let mut edits = Vec::new();
11711 let mut unfold_ranges = Vec::new();
11712 let mut refold_creases = Vec::new();
11713
11714 let selections = self.selections.all::<Point>(cx);
11715 let mut selections = selections.iter().peekable();
11716 let mut contiguous_row_selections = Vec::new();
11717 let mut new_selections = Vec::new();
11718
11719 while let Some(selection) = selections.next() {
11720 // Find all the selections that span a contiguous row range
11721 let (start_row, end_row) = consume_contiguous_rows(
11722 &mut contiguous_row_selections,
11723 selection,
11724 &display_map,
11725 &mut selections,
11726 );
11727
11728 // Move the text spanned by the row range to be after the last line of the row range
11729 if end_row.0 <= buffer.max_point().row {
11730 let range_to_move =
11731 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11732 let insertion_point = display_map
11733 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11734 .0;
11735
11736 // Don't move lines across excerpt boundaries
11737 if buffer
11738 .excerpt_containing(range_to_move.start..insertion_point)
11739 .is_some()
11740 {
11741 let mut text = String::from("\n");
11742 text.extend(buffer.text_for_range(range_to_move.clone()));
11743 text.pop(); // Drop trailing newline
11744 edits.push((
11745 buffer.anchor_after(range_to_move.start)
11746 ..buffer.anchor_before(range_to_move.end),
11747 String::new(),
11748 ));
11749 let insertion_anchor = buffer.anchor_after(insertion_point);
11750 edits.push((insertion_anchor..insertion_anchor, text));
11751
11752 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11753
11754 // Move selections down
11755 new_selections.extend(contiguous_row_selections.drain(..).map(
11756 |mut selection| {
11757 selection.start.row += row_delta;
11758 selection.end.row += row_delta;
11759 selection
11760 },
11761 ));
11762
11763 // Move folds down
11764 unfold_ranges.push(range_to_move.clone());
11765 for fold in display_map.folds_in_range(
11766 buffer.anchor_before(range_to_move.start)
11767 ..buffer.anchor_after(range_to_move.end),
11768 ) {
11769 let mut start = fold.range.start.to_point(&buffer);
11770 let mut end = fold.range.end.to_point(&buffer);
11771 start.row += row_delta;
11772 end.row += row_delta;
11773 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11774 }
11775 }
11776 }
11777
11778 // If we didn't move line(s), preserve the existing selections
11779 new_selections.append(&mut contiguous_row_selections);
11780 }
11781
11782 self.transact(window, cx, |this, window, cx| {
11783 this.unfold_ranges(&unfold_ranges, true, true, cx);
11784 this.buffer.update(cx, |buffer, cx| {
11785 for (range, text) in edits {
11786 buffer.edit([(range, text)], None, cx);
11787 }
11788 });
11789 this.fold_creases(refold_creases, true, window, cx);
11790 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11791 });
11792 }
11793
11794 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11796 let text_layout_details = &self.text_layout_details(window);
11797 self.transact(window, cx, |this, window, cx| {
11798 let edits = this.change_selections(Default::default(), window, cx, |s| {
11799 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11800 s.move_with(|display_map, selection| {
11801 if !selection.is_empty() {
11802 return;
11803 }
11804
11805 let mut head = selection.head();
11806 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11807 if head.column() == display_map.line_len(head.row()) {
11808 transpose_offset = display_map
11809 .buffer_snapshot
11810 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11811 }
11812
11813 if transpose_offset == 0 {
11814 return;
11815 }
11816
11817 *head.column_mut() += 1;
11818 head = display_map.clip_point(head, Bias::Right);
11819 let goal = SelectionGoal::HorizontalPosition(
11820 display_map
11821 .x_for_display_point(head, text_layout_details)
11822 .into(),
11823 );
11824 selection.collapse_to(head, goal);
11825
11826 let transpose_start = display_map
11827 .buffer_snapshot
11828 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11829 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11830 let transpose_end = display_map
11831 .buffer_snapshot
11832 .clip_offset(transpose_offset + 1, Bias::Right);
11833 if let Some(ch) =
11834 display_map.buffer_snapshot.chars_at(transpose_start).next()
11835 {
11836 edits.push((transpose_start..transpose_offset, String::new()));
11837 edits.push((transpose_end..transpose_end, ch.to_string()));
11838 }
11839 }
11840 });
11841 edits
11842 });
11843 this.buffer
11844 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11845 let selections = this.selections.all::<usize>(cx);
11846 this.change_selections(Default::default(), window, cx, |s| {
11847 s.select(selections);
11848 });
11849 });
11850 }
11851
11852 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11854 if self.mode.is_single_line() {
11855 cx.propagate();
11856 return;
11857 }
11858
11859 self.rewrap_impl(RewrapOptions::default(), cx)
11860 }
11861
11862 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11863 let buffer = self.buffer.read(cx).snapshot(cx);
11864 let selections = self.selections.all::<Point>(cx);
11865
11866 #[derive(Clone, Debug, PartialEq)]
11867 enum CommentFormat {
11868 /// single line comment, with prefix for line
11869 Line(String),
11870 /// single line within a block comment, with prefix for line
11871 BlockLine(String),
11872 /// a single line of a block comment that includes the initial delimiter
11873 BlockCommentWithStart(BlockCommentConfig),
11874 /// a single line of a block comment that includes the ending delimiter
11875 BlockCommentWithEnd(BlockCommentConfig),
11876 }
11877
11878 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11879 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11880 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11881 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11882 .peekable();
11883
11884 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11885 row
11886 } else {
11887 return Vec::new();
11888 };
11889
11890 let language_settings = buffer.language_settings_at(selection.head(), cx);
11891 let language_scope = buffer.language_scope_at(selection.head());
11892
11893 let indent_and_prefix_for_row =
11894 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11895 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11896 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11897 &language_scope
11898 {
11899 let indent_end = Point::new(row, indent.len);
11900 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11901 let line_text_after_indent = buffer
11902 .text_for_range(indent_end..line_end)
11903 .collect::<String>();
11904
11905 let is_within_comment_override = buffer
11906 .language_scope_at(indent_end)
11907 .is_some_and(|scope| scope.override_name() == Some("comment"));
11908 let comment_delimiters = if is_within_comment_override {
11909 // we are within a comment syntax node, but we don't
11910 // yet know what kind of comment: block, doc or line
11911 match (
11912 language_scope.documentation_comment(),
11913 language_scope.block_comment(),
11914 ) {
11915 (Some(config), _) | (_, Some(config))
11916 if buffer.contains_str_at(indent_end, &config.start) =>
11917 {
11918 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11919 }
11920 (Some(config), _) | (_, Some(config))
11921 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11922 {
11923 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11924 }
11925 (Some(config), _) | (_, Some(config))
11926 if buffer.contains_str_at(indent_end, &config.prefix) =>
11927 {
11928 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11929 }
11930 (_, _) => language_scope
11931 .line_comment_prefixes()
11932 .iter()
11933 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11934 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11935 }
11936 } else {
11937 // we not in an overridden comment node, but we may
11938 // be within a non-overridden line comment node
11939 language_scope
11940 .line_comment_prefixes()
11941 .iter()
11942 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11943 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11944 };
11945
11946 let rewrap_prefix = language_scope
11947 .rewrap_prefixes()
11948 .iter()
11949 .find_map(|prefix_regex| {
11950 prefix_regex.find(&line_text_after_indent).map(|mat| {
11951 if mat.start() == 0 {
11952 Some(mat.as_str().to_string())
11953 } else {
11954 None
11955 }
11956 })
11957 })
11958 .flatten();
11959 (comment_delimiters, rewrap_prefix)
11960 } else {
11961 (None, None)
11962 };
11963 (indent, comment_prefix, rewrap_prefix)
11964 };
11965
11966 let mut ranges = Vec::new();
11967 let from_empty_selection = selection.is_empty();
11968
11969 let mut current_range_start = first_row;
11970 let mut prev_row = first_row;
11971 let (
11972 mut current_range_indent,
11973 mut current_range_comment_delimiters,
11974 mut current_range_rewrap_prefix,
11975 ) = indent_and_prefix_for_row(first_row);
11976
11977 for row in non_blank_rows_iter.skip(1) {
11978 let has_paragraph_break = row > prev_row + 1;
11979
11980 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
11981 indent_and_prefix_for_row(row);
11982
11983 let has_indent_change = row_indent != current_range_indent;
11984 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
11985
11986 let has_boundary_change = has_comment_change
11987 || row_rewrap_prefix.is_some()
11988 || (has_indent_change && current_range_comment_delimiters.is_some());
11989
11990 if has_paragraph_break || has_boundary_change {
11991 ranges.push((
11992 language_settings.clone(),
11993 Point::new(current_range_start, 0)
11994 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11995 current_range_indent,
11996 current_range_comment_delimiters.clone(),
11997 current_range_rewrap_prefix.clone(),
11998 from_empty_selection,
11999 ));
12000 current_range_start = row;
12001 current_range_indent = row_indent;
12002 current_range_comment_delimiters = row_comment_delimiters;
12003 current_range_rewrap_prefix = row_rewrap_prefix;
12004 }
12005 prev_row = row;
12006 }
12007
12008 ranges.push((
12009 language_settings.clone(),
12010 Point::new(current_range_start, 0)
12011 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12012 current_range_indent,
12013 current_range_comment_delimiters,
12014 current_range_rewrap_prefix,
12015 from_empty_selection,
12016 ));
12017
12018 ranges
12019 });
12020
12021 let mut edits = Vec::new();
12022 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12023
12024 for (
12025 language_settings,
12026 wrap_range,
12027 mut indent_size,
12028 comment_prefix,
12029 rewrap_prefix,
12030 from_empty_selection,
12031 ) in wrap_ranges
12032 {
12033 let mut start_row = wrap_range.start.row;
12034 let mut end_row = wrap_range.end.row;
12035
12036 // Skip selections that overlap with a range that has already been rewrapped.
12037 let selection_range = start_row..end_row;
12038 if rewrapped_row_ranges
12039 .iter()
12040 .any(|range| range.overlaps(&selection_range))
12041 {
12042 continue;
12043 }
12044
12045 let tab_size = language_settings.tab_size;
12046
12047 let (line_prefix, inside_comment) = match &comment_prefix {
12048 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12049 (Some(prefix.as_str()), true)
12050 }
12051 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12052 (Some(prefix.as_ref()), true)
12053 }
12054 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12055 start: _,
12056 end: _,
12057 prefix,
12058 tab_size,
12059 })) => {
12060 indent_size.len += tab_size;
12061 (Some(prefix.as_ref()), true)
12062 }
12063 None => (None, false),
12064 };
12065 let indent_prefix = indent_size.chars().collect::<String>();
12066 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12067
12068 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12069 RewrapBehavior::InComments => inside_comment,
12070 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12071 RewrapBehavior::Anywhere => true,
12072 };
12073
12074 let should_rewrap = options.override_language_settings
12075 || allow_rewrap_based_on_language
12076 || self.hard_wrap.is_some();
12077 if !should_rewrap {
12078 continue;
12079 }
12080
12081 if from_empty_selection {
12082 'expand_upwards: while start_row > 0 {
12083 let prev_row = start_row - 1;
12084 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12085 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12086 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12087 {
12088 start_row = prev_row;
12089 } else {
12090 break 'expand_upwards;
12091 }
12092 }
12093
12094 'expand_downwards: while end_row < buffer.max_point().row {
12095 let next_row = end_row + 1;
12096 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12097 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12098 && !buffer.is_line_blank(MultiBufferRow(next_row))
12099 {
12100 end_row = next_row;
12101 } else {
12102 break 'expand_downwards;
12103 }
12104 }
12105 }
12106
12107 let start = Point::new(start_row, 0);
12108 let start_offset = start.to_offset(&buffer);
12109 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12110 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12111 let mut first_line_delimiter = None;
12112 let mut last_line_delimiter = None;
12113 let Some(lines_without_prefixes) = selection_text
12114 .lines()
12115 .enumerate()
12116 .map(|(ix, line)| {
12117 let line_trimmed = line.trim_start();
12118 if rewrap_prefix.is_some() && ix > 0 {
12119 Ok(line_trimmed)
12120 } else if let Some(
12121 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12122 start,
12123 prefix,
12124 end,
12125 tab_size,
12126 })
12127 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12128 start,
12129 prefix,
12130 end,
12131 tab_size,
12132 }),
12133 ) = &comment_prefix
12134 {
12135 let line_trimmed = line_trimmed
12136 .strip_prefix(start.as_ref())
12137 .map(|s| {
12138 let mut indent_size = indent_size;
12139 indent_size.len -= tab_size;
12140 let indent_prefix: String = indent_size.chars().collect();
12141 first_line_delimiter = Some((indent_prefix, start));
12142 s.trim_start()
12143 })
12144 .unwrap_or(line_trimmed);
12145 let line_trimmed = line_trimmed
12146 .strip_suffix(end.as_ref())
12147 .map(|s| {
12148 last_line_delimiter = Some(end);
12149 s.trim_end()
12150 })
12151 .unwrap_or(line_trimmed);
12152 let line_trimmed = line_trimmed
12153 .strip_prefix(prefix.as_ref())
12154 .unwrap_or(line_trimmed);
12155 Ok(line_trimmed)
12156 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12157 line_trimmed.strip_prefix(prefix).with_context(|| {
12158 format!("line did not start with prefix {prefix:?}: {line:?}")
12159 })
12160 } else {
12161 line_trimmed
12162 .strip_prefix(&line_prefix.trim_start())
12163 .with_context(|| {
12164 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12165 })
12166 }
12167 })
12168 .collect::<Result<Vec<_>, _>>()
12169 .log_err()
12170 else {
12171 continue;
12172 };
12173
12174 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12175 buffer
12176 .language_settings_at(Point::new(start_row, 0), cx)
12177 .preferred_line_length as usize
12178 });
12179
12180 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12181 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12182 } else {
12183 line_prefix.clone()
12184 };
12185
12186 let wrapped_text = {
12187 let mut wrapped_text = wrap_with_prefix(
12188 line_prefix,
12189 subsequent_lines_prefix,
12190 lines_without_prefixes.join("\n"),
12191 wrap_column,
12192 tab_size,
12193 options.preserve_existing_whitespace,
12194 );
12195
12196 if let Some((indent, delimiter)) = first_line_delimiter {
12197 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12198 }
12199 if let Some(last_line) = last_line_delimiter {
12200 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12201 }
12202
12203 wrapped_text
12204 };
12205
12206 // TODO: should always use char-based diff while still supporting cursor behavior that
12207 // matches vim.
12208 let mut diff_options = DiffOptions::default();
12209 if options.override_language_settings {
12210 diff_options.max_word_diff_len = 0;
12211 diff_options.max_word_diff_line_count = 0;
12212 } else {
12213 diff_options.max_word_diff_len = usize::MAX;
12214 diff_options.max_word_diff_line_count = usize::MAX;
12215 }
12216
12217 for (old_range, new_text) in
12218 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12219 {
12220 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12221 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12222 edits.push((edit_start..edit_end, new_text));
12223 }
12224
12225 rewrapped_row_ranges.push(start_row..=end_row);
12226 }
12227
12228 self.buffer
12229 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12230 }
12231
12232 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12233 let mut text = String::new();
12234 let buffer = self.buffer.read(cx).snapshot(cx);
12235 let mut selections = self.selections.all::<Point>(cx);
12236 let mut clipboard_selections = Vec::with_capacity(selections.len());
12237 {
12238 let max_point = buffer.max_point();
12239 let mut is_first = true;
12240 for selection in &mut selections {
12241 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12242 if is_entire_line {
12243 selection.start = Point::new(selection.start.row, 0);
12244 if !selection.is_empty() && selection.end.column == 0 {
12245 selection.end = cmp::min(max_point, selection.end);
12246 } else {
12247 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12248 }
12249 selection.goal = SelectionGoal::None;
12250 }
12251 if is_first {
12252 is_first = false;
12253 } else {
12254 text += "\n";
12255 }
12256 let mut len = 0;
12257 for chunk in buffer.text_for_range(selection.start..selection.end) {
12258 text.push_str(chunk);
12259 len += chunk.len();
12260 }
12261 clipboard_selections.push(ClipboardSelection {
12262 len,
12263 is_entire_line,
12264 first_line_indent: buffer
12265 .indent_size_for_line(MultiBufferRow(selection.start.row))
12266 .len,
12267 });
12268 }
12269 }
12270
12271 self.transact(window, cx, |this, window, cx| {
12272 this.change_selections(Default::default(), window, cx, |s| {
12273 s.select(selections);
12274 });
12275 this.insert("", window, cx);
12276 });
12277 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12278 }
12279
12280 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12282 let item = self.cut_common(window, cx);
12283 cx.write_to_clipboard(item);
12284 }
12285
12286 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12288 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12289 s.move_with(|snapshot, sel| {
12290 if sel.is_empty() {
12291 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12292 }
12293 });
12294 });
12295 let item = self.cut_common(window, cx);
12296 cx.set_global(KillRing(item))
12297 }
12298
12299 pub fn kill_ring_yank(
12300 &mut self,
12301 _: &KillRingYank,
12302 window: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12306 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12307 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12308 (kill_ring.text().to_string(), kill_ring.metadata_json())
12309 } else {
12310 return;
12311 }
12312 } else {
12313 return;
12314 };
12315 self.do_paste(&text, metadata, false, window, cx);
12316 }
12317
12318 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12319 self.do_copy(true, cx);
12320 }
12321
12322 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12323 self.do_copy(false, cx);
12324 }
12325
12326 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12327 let selections = self.selections.all::<Point>(cx);
12328 let buffer = self.buffer.read(cx).read(cx);
12329 let mut text = String::new();
12330
12331 let mut clipboard_selections = Vec::with_capacity(selections.len());
12332 {
12333 let max_point = buffer.max_point();
12334 let mut is_first = true;
12335 for selection in &selections {
12336 let mut start = selection.start;
12337 let mut end = selection.end;
12338 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12339 if is_entire_line {
12340 start = Point::new(start.row, 0);
12341 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12342 }
12343
12344 let mut trimmed_selections = Vec::new();
12345 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12346 let row = MultiBufferRow(start.row);
12347 let first_indent = buffer.indent_size_for_line(row);
12348 if first_indent.len == 0 || start.column > first_indent.len {
12349 trimmed_selections.push(start..end);
12350 } else {
12351 trimmed_selections.push(
12352 Point::new(row.0, first_indent.len)
12353 ..Point::new(row.0, buffer.line_len(row)),
12354 );
12355 for row in start.row + 1..=end.row {
12356 let mut line_len = buffer.line_len(MultiBufferRow(row));
12357 if row == end.row {
12358 line_len = end.column;
12359 }
12360 if line_len == 0 {
12361 trimmed_selections
12362 .push(Point::new(row, 0)..Point::new(row, line_len));
12363 continue;
12364 }
12365 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12366 if row_indent_size.len >= first_indent.len {
12367 trimmed_selections.push(
12368 Point::new(row, first_indent.len)..Point::new(row, line_len),
12369 );
12370 } else {
12371 trimmed_selections.clear();
12372 trimmed_selections.push(start..end);
12373 break;
12374 }
12375 }
12376 }
12377 } else {
12378 trimmed_selections.push(start..end);
12379 }
12380
12381 for trimmed_range in trimmed_selections {
12382 if is_first {
12383 is_first = false;
12384 } else {
12385 text += "\n";
12386 }
12387 let mut len = 0;
12388 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12389 text.push_str(chunk);
12390 len += chunk.len();
12391 }
12392 clipboard_selections.push(ClipboardSelection {
12393 len,
12394 is_entire_line,
12395 first_line_indent: buffer
12396 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12397 .len,
12398 });
12399 }
12400 }
12401 }
12402
12403 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12404 text,
12405 clipboard_selections,
12406 ));
12407 }
12408
12409 pub fn do_paste(
12410 &mut self,
12411 text: &String,
12412 clipboard_selections: Option<Vec<ClipboardSelection>>,
12413 handle_entire_lines: bool,
12414 window: &mut Window,
12415 cx: &mut Context<Self>,
12416 ) {
12417 if self.read_only(cx) {
12418 return;
12419 }
12420
12421 let clipboard_text = Cow::Borrowed(text);
12422
12423 self.transact(window, cx, |this, window, cx| {
12424 let had_active_edit_prediction = this.has_active_edit_prediction();
12425
12426 if let Some(mut clipboard_selections) = clipboard_selections {
12427 let old_selections = this.selections.all::<usize>(cx);
12428 let all_selections_were_entire_line =
12429 clipboard_selections.iter().all(|s| s.is_entire_line);
12430 let first_selection_indent_column =
12431 clipboard_selections.first().map(|s| s.first_line_indent);
12432 if clipboard_selections.len() != old_selections.len() {
12433 clipboard_selections.drain(..);
12434 }
12435 let cursor_offset = this.selections.last::<usize>(cx).head();
12436 let mut auto_indent_on_paste = true;
12437
12438 this.buffer.update(cx, |buffer, cx| {
12439 let snapshot = buffer.read(cx);
12440 auto_indent_on_paste = snapshot
12441 .language_settings_at(cursor_offset, cx)
12442 .auto_indent_on_paste;
12443
12444 let mut start_offset = 0;
12445 let mut edits = Vec::new();
12446 let mut original_indent_columns = Vec::new();
12447 for (ix, selection) in old_selections.iter().enumerate() {
12448 let to_insert;
12449 let entire_line;
12450 let original_indent_column;
12451 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12452 let end_offset = start_offset + clipboard_selection.len;
12453 to_insert = &clipboard_text[start_offset..end_offset];
12454 entire_line = clipboard_selection.is_entire_line;
12455 start_offset = end_offset + 1;
12456 original_indent_column = Some(clipboard_selection.first_line_indent);
12457 } else {
12458 to_insert = clipboard_text.as_str();
12459 entire_line = all_selections_were_entire_line;
12460 original_indent_column = first_selection_indent_column
12461 }
12462
12463 // If the corresponding selection was empty when this slice of the
12464 // clipboard text was written, then the entire line containing the
12465 // selection was copied. If this selection is also currently empty,
12466 // then paste the line before the current line of the buffer.
12467 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12468 let column = selection.start.to_point(&snapshot).column as usize;
12469 let line_start = selection.start - column;
12470 line_start..line_start
12471 } else {
12472 selection.range()
12473 };
12474
12475 edits.push((range, to_insert));
12476 original_indent_columns.push(original_indent_column);
12477 }
12478 drop(snapshot);
12479
12480 buffer.edit(
12481 edits,
12482 if auto_indent_on_paste {
12483 Some(AutoindentMode::Block {
12484 original_indent_columns,
12485 })
12486 } else {
12487 None
12488 },
12489 cx,
12490 );
12491 });
12492
12493 let selections = this.selections.all::<usize>(cx);
12494 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12495 } else {
12496 this.insert(&clipboard_text, window, cx);
12497 }
12498
12499 let trigger_in_words =
12500 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12501
12502 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12503 });
12504 }
12505
12506 pub fn diff_clipboard_with_selection(
12507 &mut self,
12508 _: &DiffClipboardWithSelection,
12509 window: &mut Window,
12510 cx: &mut Context<Self>,
12511 ) {
12512 let selections = self.selections.all::<usize>(cx);
12513
12514 if selections.is_empty() {
12515 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12516 return;
12517 };
12518
12519 let clipboard_text = match cx.read_from_clipboard() {
12520 Some(item) => match item.entries().first() {
12521 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12522 _ => None,
12523 },
12524 None => None,
12525 };
12526
12527 let Some(clipboard_text) = clipboard_text else {
12528 log::warn!("Clipboard doesn't contain text.");
12529 return;
12530 };
12531
12532 window.dispatch_action(
12533 Box::new(DiffClipboardWithSelectionData {
12534 clipboard_text,
12535 editor: cx.entity(),
12536 }),
12537 cx,
12538 );
12539 }
12540
12541 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12542 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12543 if let Some(item) = cx.read_from_clipboard() {
12544 let entries = item.entries();
12545
12546 match entries.first() {
12547 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12548 // of all the pasted entries.
12549 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12550 .do_paste(
12551 clipboard_string.text(),
12552 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12553 true,
12554 window,
12555 cx,
12556 ),
12557 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12558 }
12559 }
12560 }
12561
12562 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12563 if self.read_only(cx) {
12564 return;
12565 }
12566
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12568
12569 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12570 if let Some((selections, _)) =
12571 self.selection_history.transaction(transaction_id).cloned()
12572 {
12573 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12574 s.select_anchors(selections.to_vec());
12575 });
12576 } else {
12577 log::error!(
12578 "No entry in selection_history found for undo. \
12579 This may correspond to a bug where undo does not update the selection. \
12580 If this is occurring, please add details to \
12581 https://github.com/zed-industries/zed/issues/22692"
12582 );
12583 }
12584 self.request_autoscroll(Autoscroll::fit(), cx);
12585 self.unmark_text(window, cx);
12586 self.refresh_edit_prediction(true, false, window, cx);
12587 cx.emit(EditorEvent::Edited { transaction_id });
12588 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12589 }
12590 }
12591
12592 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12593 if self.read_only(cx) {
12594 return;
12595 }
12596
12597 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12598
12599 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12600 if let Some((_, Some(selections))) =
12601 self.selection_history.transaction(transaction_id).cloned()
12602 {
12603 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12604 s.select_anchors(selections.to_vec());
12605 });
12606 } else {
12607 log::error!(
12608 "No entry in selection_history found for redo. \
12609 This may correspond to a bug where undo does not update the selection. \
12610 If this is occurring, please add details to \
12611 https://github.com/zed-industries/zed/issues/22692"
12612 );
12613 }
12614 self.request_autoscroll(Autoscroll::fit(), cx);
12615 self.unmark_text(window, cx);
12616 self.refresh_edit_prediction(true, false, window, cx);
12617 cx.emit(EditorEvent::Edited { transaction_id });
12618 }
12619 }
12620
12621 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12622 self.buffer
12623 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12624 }
12625
12626 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12627 self.buffer
12628 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12629 }
12630
12631 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12633 self.change_selections(Default::default(), window, cx, |s| {
12634 s.move_with(|map, selection| {
12635 let cursor = if selection.is_empty() {
12636 movement::left(map, selection.start)
12637 } else {
12638 selection.start
12639 };
12640 selection.collapse_to(cursor, SelectionGoal::None);
12641 });
12642 })
12643 }
12644
12645 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12646 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12647 self.change_selections(Default::default(), window, cx, |s| {
12648 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12649 })
12650 }
12651
12652 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12654 self.change_selections(Default::default(), window, cx, |s| {
12655 s.move_with(|map, selection| {
12656 let cursor = if selection.is_empty() {
12657 movement::right(map, selection.end)
12658 } else {
12659 selection.end
12660 };
12661 selection.collapse_to(cursor, SelectionGoal::None)
12662 });
12663 })
12664 }
12665
12666 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12668 self.change_selections(Default::default(), window, cx, |s| {
12669 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12670 })
12671 }
12672
12673 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12674 if self.take_rename(true, window, cx).is_some() {
12675 return;
12676 }
12677
12678 if self.mode.is_single_line() {
12679 cx.propagate();
12680 return;
12681 }
12682
12683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12684
12685 let text_layout_details = &self.text_layout_details(window);
12686 let selection_count = self.selections.count();
12687 let first_selection = self.selections.first_anchor();
12688
12689 self.change_selections(Default::default(), window, cx, |s| {
12690 s.move_with(|map, selection| {
12691 if !selection.is_empty() {
12692 selection.goal = SelectionGoal::None;
12693 }
12694 let (cursor, goal) = movement::up(
12695 map,
12696 selection.start,
12697 selection.goal,
12698 false,
12699 text_layout_details,
12700 );
12701 selection.collapse_to(cursor, goal);
12702 });
12703 });
12704
12705 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12706 {
12707 cx.propagate();
12708 }
12709 }
12710
12711 pub fn move_up_by_lines(
12712 &mut self,
12713 action: &MoveUpByLines,
12714 window: &mut Window,
12715 cx: &mut Context<Self>,
12716 ) {
12717 if self.take_rename(true, window, cx).is_some() {
12718 return;
12719 }
12720
12721 if self.mode.is_single_line() {
12722 cx.propagate();
12723 return;
12724 }
12725
12726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12727
12728 let text_layout_details = &self.text_layout_details(window);
12729
12730 self.change_selections(Default::default(), window, cx, |s| {
12731 s.move_with(|map, selection| {
12732 if !selection.is_empty() {
12733 selection.goal = SelectionGoal::None;
12734 }
12735 let (cursor, goal) = movement::up_by_rows(
12736 map,
12737 selection.start,
12738 action.lines,
12739 selection.goal,
12740 false,
12741 text_layout_details,
12742 );
12743 selection.collapse_to(cursor, goal);
12744 });
12745 })
12746 }
12747
12748 pub fn move_down_by_lines(
12749 &mut self,
12750 action: &MoveDownByLines,
12751 window: &mut Window,
12752 cx: &mut Context<Self>,
12753 ) {
12754 if self.take_rename(true, window, cx).is_some() {
12755 return;
12756 }
12757
12758 if self.mode.is_single_line() {
12759 cx.propagate();
12760 return;
12761 }
12762
12763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12764
12765 let text_layout_details = &self.text_layout_details(window);
12766
12767 self.change_selections(Default::default(), window, cx, |s| {
12768 s.move_with(|map, selection| {
12769 if !selection.is_empty() {
12770 selection.goal = SelectionGoal::None;
12771 }
12772 let (cursor, goal) = movement::down_by_rows(
12773 map,
12774 selection.start,
12775 action.lines,
12776 selection.goal,
12777 false,
12778 text_layout_details,
12779 );
12780 selection.collapse_to(cursor, goal);
12781 });
12782 })
12783 }
12784
12785 pub fn select_down_by_lines(
12786 &mut self,
12787 action: &SelectDownByLines,
12788 window: &mut Window,
12789 cx: &mut Context<Self>,
12790 ) {
12791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12792 let text_layout_details = &self.text_layout_details(window);
12793 self.change_selections(Default::default(), window, cx, |s| {
12794 s.move_heads_with(|map, head, goal| {
12795 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12796 })
12797 })
12798 }
12799
12800 pub fn select_up_by_lines(
12801 &mut self,
12802 action: &SelectUpByLines,
12803 window: &mut Window,
12804 cx: &mut Context<Self>,
12805 ) {
12806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12807 let text_layout_details = &self.text_layout_details(window);
12808 self.change_selections(Default::default(), window, cx, |s| {
12809 s.move_heads_with(|map, head, goal| {
12810 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12811 })
12812 })
12813 }
12814
12815 pub fn select_page_up(
12816 &mut self,
12817 _: &SelectPageUp,
12818 window: &mut Window,
12819 cx: &mut Context<Self>,
12820 ) {
12821 let Some(row_count) = self.visible_row_count() else {
12822 return;
12823 };
12824
12825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12826
12827 let text_layout_details = &self.text_layout_details(window);
12828
12829 self.change_selections(Default::default(), window, cx, |s| {
12830 s.move_heads_with(|map, head, goal| {
12831 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12832 })
12833 })
12834 }
12835
12836 pub fn move_page_up(
12837 &mut self,
12838 action: &MovePageUp,
12839 window: &mut Window,
12840 cx: &mut Context<Self>,
12841 ) {
12842 if self.take_rename(true, window, cx).is_some() {
12843 return;
12844 }
12845
12846 if self
12847 .context_menu
12848 .borrow_mut()
12849 .as_mut()
12850 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12851 .unwrap_or(false)
12852 {
12853 return;
12854 }
12855
12856 if matches!(self.mode, EditorMode::SingleLine) {
12857 cx.propagate();
12858 return;
12859 }
12860
12861 let Some(row_count) = self.visible_row_count() else {
12862 return;
12863 };
12864
12865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12866
12867 let effects = if action.center_cursor {
12868 SelectionEffects::scroll(Autoscroll::center())
12869 } else {
12870 SelectionEffects::default()
12871 };
12872
12873 let text_layout_details = &self.text_layout_details(window);
12874
12875 self.change_selections(effects, window, cx, |s| {
12876 s.move_with(|map, selection| {
12877 if !selection.is_empty() {
12878 selection.goal = SelectionGoal::None;
12879 }
12880 let (cursor, goal) = movement::up_by_rows(
12881 map,
12882 selection.end,
12883 row_count,
12884 selection.goal,
12885 false,
12886 text_layout_details,
12887 );
12888 selection.collapse_to(cursor, goal);
12889 });
12890 });
12891 }
12892
12893 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12895 let text_layout_details = &self.text_layout_details(window);
12896 self.change_selections(Default::default(), window, cx, |s| {
12897 s.move_heads_with(|map, head, goal| {
12898 movement::up(map, head, goal, false, text_layout_details)
12899 })
12900 })
12901 }
12902
12903 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12904 self.take_rename(true, window, cx);
12905
12906 if self.mode.is_single_line() {
12907 cx.propagate();
12908 return;
12909 }
12910
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912
12913 let text_layout_details = &self.text_layout_details(window);
12914 let selection_count = self.selections.count();
12915 let first_selection = self.selections.first_anchor();
12916
12917 self.change_selections(Default::default(), window, cx, |s| {
12918 s.move_with(|map, selection| {
12919 if !selection.is_empty() {
12920 selection.goal = SelectionGoal::None;
12921 }
12922 let (cursor, goal) = movement::down(
12923 map,
12924 selection.end,
12925 selection.goal,
12926 false,
12927 text_layout_details,
12928 );
12929 selection.collapse_to(cursor, goal);
12930 });
12931 });
12932
12933 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12934 {
12935 cx.propagate();
12936 }
12937 }
12938
12939 pub fn select_page_down(
12940 &mut self,
12941 _: &SelectPageDown,
12942 window: &mut Window,
12943 cx: &mut Context<Self>,
12944 ) {
12945 let Some(row_count) = self.visible_row_count() else {
12946 return;
12947 };
12948
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950
12951 let text_layout_details = &self.text_layout_details(window);
12952
12953 self.change_selections(Default::default(), window, cx, |s| {
12954 s.move_heads_with(|map, head, goal| {
12955 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12956 })
12957 })
12958 }
12959
12960 pub fn move_page_down(
12961 &mut self,
12962 action: &MovePageDown,
12963 window: &mut Window,
12964 cx: &mut Context<Self>,
12965 ) {
12966 if self.take_rename(true, window, cx).is_some() {
12967 return;
12968 }
12969
12970 if self
12971 .context_menu
12972 .borrow_mut()
12973 .as_mut()
12974 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12975 .unwrap_or(false)
12976 {
12977 return;
12978 }
12979
12980 if matches!(self.mode, EditorMode::SingleLine) {
12981 cx.propagate();
12982 return;
12983 }
12984
12985 let Some(row_count) = self.visible_row_count() else {
12986 return;
12987 };
12988
12989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12990
12991 let effects = if action.center_cursor {
12992 SelectionEffects::scroll(Autoscroll::center())
12993 } else {
12994 SelectionEffects::default()
12995 };
12996
12997 let text_layout_details = &self.text_layout_details(window);
12998 self.change_selections(effects, window, cx, |s| {
12999 s.move_with(|map, selection| {
13000 if !selection.is_empty() {
13001 selection.goal = SelectionGoal::None;
13002 }
13003 let (cursor, goal) = movement::down_by_rows(
13004 map,
13005 selection.end,
13006 row_count,
13007 selection.goal,
13008 false,
13009 text_layout_details,
13010 );
13011 selection.collapse_to(cursor, goal);
13012 });
13013 });
13014 }
13015
13016 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13018 let text_layout_details = &self.text_layout_details(window);
13019 self.change_selections(Default::default(), window, cx, |s| {
13020 s.move_heads_with(|map, head, goal| {
13021 movement::down(map, head, goal, false, text_layout_details)
13022 })
13023 });
13024 }
13025
13026 pub fn context_menu_first(
13027 &mut self,
13028 _: &ContextMenuFirst,
13029 window: &mut Window,
13030 cx: &mut Context<Self>,
13031 ) {
13032 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13033 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13034 }
13035 }
13036
13037 pub fn context_menu_prev(
13038 &mut self,
13039 _: &ContextMenuPrevious,
13040 window: &mut Window,
13041 cx: &mut Context<Self>,
13042 ) {
13043 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13044 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13045 }
13046 }
13047
13048 pub fn context_menu_next(
13049 &mut self,
13050 _: &ContextMenuNext,
13051 window: &mut Window,
13052 cx: &mut Context<Self>,
13053 ) {
13054 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13055 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13056 }
13057 }
13058
13059 pub fn context_menu_last(
13060 &mut self,
13061 _: &ContextMenuLast,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13066 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13067 }
13068 }
13069
13070 pub fn signature_help_prev(
13071 &mut self,
13072 _: &SignatureHelpPrevious,
13073 _: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) {
13076 if let Some(popover) = self.signature_help_state.popover_mut() {
13077 if popover.current_signature == 0 {
13078 popover.current_signature = popover.signatures.len() - 1;
13079 } else {
13080 popover.current_signature -= 1;
13081 }
13082 cx.notify();
13083 }
13084 }
13085
13086 pub fn signature_help_next(
13087 &mut self,
13088 _: &SignatureHelpNext,
13089 _: &mut Window,
13090 cx: &mut Context<Self>,
13091 ) {
13092 if let Some(popover) = self.signature_help_state.popover_mut() {
13093 if popover.current_signature + 1 == popover.signatures.len() {
13094 popover.current_signature = 0;
13095 } else {
13096 popover.current_signature += 1;
13097 }
13098 cx.notify();
13099 }
13100 }
13101
13102 pub fn move_to_previous_word_start(
13103 &mut self,
13104 _: &MoveToPreviousWordStart,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13109 self.change_selections(Default::default(), window, cx, |s| {
13110 s.move_cursors_with(|map, head, _| {
13111 (
13112 movement::previous_word_start(map, head),
13113 SelectionGoal::None,
13114 )
13115 });
13116 })
13117 }
13118
13119 pub fn move_to_previous_subword_start(
13120 &mut self,
13121 _: &MoveToPreviousSubwordStart,
13122 window: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) {
13125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13126 self.change_selections(Default::default(), window, cx, |s| {
13127 s.move_cursors_with(|map, head, _| {
13128 (
13129 movement::previous_subword_start(map, head),
13130 SelectionGoal::None,
13131 )
13132 });
13133 })
13134 }
13135
13136 pub fn select_to_previous_word_start(
13137 &mut self,
13138 _: &SelectToPreviousWordStart,
13139 window: &mut Window,
13140 cx: &mut Context<Self>,
13141 ) {
13142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13143 self.change_selections(Default::default(), window, cx, |s| {
13144 s.move_heads_with(|map, head, _| {
13145 (
13146 movement::previous_word_start(map, head),
13147 SelectionGoal::None,
13148 )
13149 });
13150 })
13151 }
13152
13153 pub fn select_to_previous_subword_start(
13154 &mut self,
13155 _: &SelectToPreviousSubwordStart,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13160 self.change_selections(Default::default(), window, cx, |s| {
13161 s.move_heads_with(|map, head, _| {
13162 (
13163 movement::previous_subword_start(map, head),
13164 SelectionGoal::None,
13165 )
13166 });
13167 })
13168 }
13169
13170 pub fn delete_to_previous_word_start(
13171 &mut self,
13172 action: &DeleteToPreviousWordStart,
13173 window: &mut Window,
13174 cx: &mut Context<Self>,
13175 ) {
13176 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13177 self.transact(window, cx, |this, window, cx| {
13178 this.select_autoclose_pair(window, cx);
13179 this.change_selections(Default::default(), window, cx, |s| {
13180 s.move_with(|map, selection| {
13181 if selection.is_empty() {
13182 let mut cursor = if action.ignore_newlines {
13183 movement::previous_word_start(map, selection.head())
13184 } else {
13185 movement::previous_word_start_or_newline(map, selection.head())
13186 };
13187 cursor = movement::adjust_greedy_deletion(
13188 map,
13189 selection.head(),
13190 cursor,
13191 action.ignore_brackets,
13192 );
13193 selection.set_head(cursor, SelectionGoal::None);
13194 }
13195 });
13196 });
13197 this.insert("", window, cx);
13198 });
13199 }
13200
13201 pub fn delete_to_previous_subword_start(
13202 &mut self,
13203 _: &DeleteToPreviousSubwordStart,
13204 window: &mut Window,
13205 cx: &mut Context<Self>,
13206 ) {
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13208 self.transact(window, cx, |this, window, cx| {
13209 this.select_autoclose_pair(window, cx);
13210 this.change_selections(Default::default(), window, cx, |s| {
13211 s.move_with(|map, selection| {
13212 if selection.is_empty() {
13213 let mut cursor = movement::previous_subword_start(map, selection.head());
13214 cursor =
13215 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13216 selection.set_head(cursor, SelectionGoal::None);
13217 }
13218 });
13219 });
13220 this.insert("", window, cx);
13221 });
13222 }
13223
13224 pub fn move_to_next_word_end(
13225 &mut self,
13226 _: &MoveToNextWordEnd,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_cursors_with(|map, head, _| {
13233 (movement::next_word_end(map, head), SelectionGoal::None)
13234 });
13235 })
13236 }
13237
13238 pub fn move_to_next_subword_end(
13239 &mut self,
13240 _: &MoveToNextSubwordEnd,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) {
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245 self.change_selections(Default::default(), window, cx, |s| {
13246 s.move_cursors_with(|map, head, _| {
13247 (movement::next_subword_end(map, head), SelectionGoal::None)
13248 });
13249 })
13250 }
13251
13252 pub fn select_to_next_word_end(
13253 &mut self,
13254 _: &SelectToNextWordEnd,
13255 window: &mut Window,
13256 cx: &mut Context<Self>,
13257 ) {
13258 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13259 self.change_selections(Default::default(), window, cx, |s| {
13260 s.move_heads_with(|map, head, _| {
13261 (movement::next_word_end(map, head), SelectionGoal::None)
13262 });
13263 })
13264 }
13265
13266 pub fn select_to_next_subword_end(
13267 &mut self,
13268 _: &SelectToNextSubwordEnd,
13269 window: &mut Window,
13270 cx: &mut Context<Self>,
13271 ) {
13272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13273 self.change_selections(Default::default(), window, cx, |s| {
13274 s.move_heads_with(|map, head, _| {
13275 (movement::next_subword_end(map, head), SelectionGoal::None)
13276 });
13277 })
13278 }
13279
13280 pub fn delete_to_next_word_end(
13281 &mut self,
13282 action: &DeleteToNextWordEnd,
13283 window: &mut Window,
13284 cx: &mut Context<Self>,
13285 ) {
13286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13287 self.transact(window, cx, |this, window, cx| {
13288 this.change_selections(Default::default(), window, cx, |s| {
13289 s.move_with(|map, selection| {
13290 if selection.is_empty() {
13291 let mut cursor = if action.ignore_newlines {
13292 movement::next_word_end(map, selection.head())
13293 } else {
13294 movement::next_word_end_or_newline(map, selection.head())
13295 };
13296 cursor = movement::adjust_greedy_deletion(
13297 map,
13298 selection.head(),
13299 cursor,
13300 action.ignore_brackets,
13301 );
13302 selection.set_head(cursor, SelectionGoal::None);
13303 }
13304 });
13305 });
13306 this.insert("", window, cx);
13307 });
13308 }
13309
13310 pub fn delete_to_next_subword_end(
13311 &mut self,
13312 _: &DeleteToNextSubwordEnd,
13313 window: &mut Window,
13314 cx: &mut Context<Self>,
13315 ) {
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13317 self.transact(window, cx, |this, window, cx| {
13318 this.change_selections(Default::default(), window, cx, |s| {
13319 s.move_with(|map, selection| {
13320 if selection.is_empty() {
13321 let mut cursor = movement::next_subword_end(map, selection.head());
13322 cursor =
13323 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13324 selection.set_head(cursor, SelectionGoal::None);
13325 }
13326 });
13327 });
13328 this.insert("", window, cx);
13329 });
13330 }
13331
13332 pub fn move_to_beginning_of_line(
13333 &mut self,
13334 action: &MoveToBeginningOfLine,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13339 self.change_selections(Default::default(), window, cx, |s| {
13340 s.move_cursors_with(|map, head, _| {
13341 (
13342 movement::indented_line_beginning(
13343 map,
13344 head,
13345 action.stop_at_soft_wraps,
13346 action.stop_at_indent,
13347 ),
13348 SelectionGoal::None,
13349 )
13350 });
13351 })
13352 }
13353
13354 pub fn select_to_beginning_of_line(
13355 &mut self,
13356 action: &SelectToBeginningOfLine,
13357 window: &mut Window,
13358 cx: &mut Context<Self>,
13359 ) {
13360 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13361 self.change_selections(Default::default(), window, cx, |s| {
13362 s.move_heads_with(|map, head, _| {
13363 (
13364 movement::indented_line_beginning(
13365 map,
13366 head,
13367 action.stop_at_soft_wraps,
13368 action.stop_at_indent,
13369 ),
13370 SelectionGoal::None,
13371 )
13372 });
13373 });
13374 }
13375
13376 pub fn delete_to_beginning_of_line(
13377 &mut self,
13378 action: &DeleteToBeginningOfLine,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13383 self.transact(window, cx, |this, window, cx| {
13384 this.change_selections(Default::default(), window, cx, |s| {
13385 s.move_with(|_, selection| {
13386 selection.reversed = true;
13387 });
13388 });
13389
13390 this.select_to_beginning_of_line(
13391 &SelectToBeginningOfLine {
13392 stop_at_soft_wraps: false,
13393 stop_at_indent: action.stop_at_indent,
13394 },
13395 window,
13396 cx,
13397 );
13398 this.backspace(&Backspace, window, cx);
13399 });
13400 }
13401
13402 pub fn move_to_end_of_line(
13403 &mut self,
13404 action: &MoveToEndOfLine,
13405 window: &mut Window,
13406 cx: &mut Context<Self>,
13407 ) {
13408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13409 self.change_selections(Default::default(), window, cx, |s| {
13410 s.move_cursors_with(|map, head, _| {
13411 (
13412 movement::line_end(map, head, action.stop_at_soft_wraps),
13413 SelectionGoal::None,
13414 )
13415 });
13416 })
13417 }
13418
13419 pub fn select_to_end_of_line(
13420 &mut self,
13421 action: &SelectToEndOfLine,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13426 self.change_selections(Default::default(), window, cx, |s| {
13427 s.move_heads_with(|map, head, _| {
13428 (
13429 movement::line_end(map, head, action.stop_at_soft_wraps),
13430 SelectionGoal::None,
13431 )
13432 });
13433 })
13434 }
13435
13436 pub fn delete_to_end_of_line(
13437 &mut self,
13438 _: &DeleteToEndOfLine,
13439 window: &mut Window,
13440 cx: &mut Context<Self>,
13441 ) {
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13443 self.transact(window, cx, |this, window, cx| {
13444 this.select_to_end_of_line(
13445 &SelectToEndOfLine {
13446 stop_at_soft_wraps: false,
13447 },
13448 window,
13449 cx,
13450 );
13451 this.delete(&Delete, window, cx);
13452 });
13453 }
13454
13455 pub fn cut_to_end_of_line(
13456 &mut self,
13457 _: &CutToEndOfLine,
13458 window: &mut Window,
13459 cx: &mut Context<Self>,
13460 ) {
13461 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13462 self.transact(window, cx, |this, window, cx| {
13463 this.select_to_end_of_line(
13464 &SelectToEndOfLine {
13465 stop_at_soft_wraps: false,
13466 },
13467 window,
13468 cx,
13469 );
13470 this.cut(&Cut, window, cx);
13471 });
13472 }
13473
13474 pub fn move_to_start_of_paragraph(
13475 &mut self,
13476 _: &MoveToStartOfParagraph,
13477 window: &mut Window,
13478 cx: &mut Context<Self>,
13479 ) {
13480 if matches!(self.mode, EditorMode::SingleLine) {
13481 cx.propagate();
13482 return;
13483 }
13484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13485 self.change_selections(Default::default(), window, cx, |s| {
13486 s.move_with(|map, selection| {
13487 selection.collapse_to(
13488 movement::start_of_paragraph(map, selection.head(), 1),
13489 SelectionGoal::None,
13490 )
13491 });
13492 })
13493 }
13494
13495 pub fn move_to_end_of_paragraph(
13496 &mut self,
13497 _: &MoveToEndOfParagraph,
13498 window: &mut Window,
13499 cx: &mut Context<Self>,
13500 ) {
13501 if matches!(self.mode, EditorMode::SingleLine) {
13502 cx.propagate();
13503 return;
13504 }
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.move_with(|map, selection| {
13508 selection.collapse_to(
13509 movement::end_of_paragraph(map, selection.head(), 1),
13510 SelectionGoal::None,
13511 )
13512 });
13513 })
13514 }
13515
13516 pub fn select_to_start_of_paragraph(
13517 &mut self,
13518 _: &SelectToStartOfParagraph,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 if matches!(self.mode, EditorMode::SingleLine) {
13523 cx.propagate();
13524 return;
13525 }
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_heads_with(|map, head, _| {
13529 (
13530 movement::start_of_paragraph(map, head, 1),
13531 SelectionGoal::None,
13532 )
13533 });
13534 })
13535 }
13536
13537 pub fn select_to_end_of_paragraph(
13538 &mut self,
13539 _: &SelectToEndOfParagraph,
13540 window: &mut Window,
13541 cx: &mut Context<Self>,
13542 ) {
13543 if matches!(self.mode, EditorMode::SingleLine) {
13544 cx.propagate();
13545 return;
13546 }
13547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.move_heads_with(|map, head, _| {
13550 (
13551 movement::end_of_paragraph(map, head, 1),
13552 SelectionGoal::None,
13553 )
13554 });
13555 })
13556 }
13557
13558 pub fn move_to_start_of_excerpt(
13559 &mut self,
13560 _: &MoveToStartOfExcerpt,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 if matches!(self.mode, EditorMode::SingleLine) {
13565 cx.propagate();
13566 return;
13567 }
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_with(|map, selection| {
13571 selection.collapse_to(
13572 movement::start_of_excerpt(
13573 map,
13574 selection.head(),
13575 workspace::searchable::Direction::Prev,
13576 ),
13577 SelectionGoal::None,
13578 )
13579 });
13580 })
13581 }
13582
13583 pub fn move_to_start_of_next_excerpt(
13584 &mut self,
13585 _: &MoveToStartOfNextExcerpt,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 if matches!(self.mode, EditorMode::SingleLine) {
13590 cx.propagate();
13591 return;
13592 }
13593
13594 self.change_selections(Default::default(), window, cx, |s| {
13595 s.move_with(|map, selection| {
13596 selection.collapse_to(
13597 movement::start_of_excerpt(
13598 map,
13599 selection.head(),
13600 workspace::searchable::Direction::Next,
13601 ),
13602 SelectionGoal::None,
13603 )
13604 });
13605 })
13606 }
13607
13608 pub fn move_to_end_of_excerpt(
13609 &mut self,
13610 _: &MoveToEndOfExcerpt,
13611 window: &mut Window,
13612 cx: &mut Context<Self>,
13613 ) {
13614 if matches!(self.mode, EditorMode::SingleLine) {
13615 cx.propagate();
13616 return;
13617 }
13618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13619 self.change_selections(Default::default(), window, cx, |s| {
13620 s.move_with(|map, selection| {
13621 selection.collapse_to(
13622 movement::end_of_excerpt(
13623 map,
13624 selection.head(),
13625 workspace::searchable::Direction::Next,
13626 ),
13627 SelectionGoal::None,
13628 )
13629 });
13630 })
13631 }
13632
13633 pub fn move_to_end_of_previous_excerpt(
13634 &mut self,
13635 _: &MoveToEndOfPreviousExcerpt,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 if matches!(self.mode, EditorMode::SingleLine) {
13640 cx.propagate();
13641 return;
13642 }
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13644 self.change_selections(Default::default(), window, cx, |s| {
13645 s.move_with(|map, selection| {
13646 selection.collapse_to(
13647 movement::end_of_excerpt(
13648 map,
13649 selection.head(),
13650 workspace::searchable::Direction::Prev,
13651 ),
13652 SelectionGoal::None,
13653 )
13654 });
13655 })
13656 }
13657
13658 pub fn select_to_start_of_excerpt(
13659 &mut self,
13660 _: &SelectToStartOfExcerpt,
13661 window: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 if matches!(self.mode, EditorMode::SingleLine) {
13665 cx.propagate();
13666 return;
13667 }
13668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13669 self.change_selections(Default::default(), window, cx, |s| {
13670 s.move_heads_with(|map, head, _| {
13671 (
13672 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13673 SelectionGoal::None,
13674 )
13675 });
13676 })
13677 }
13678
13679 pub fn select_to_start_of_next_excerpt(
13680 &mut self,
13681 _: &SelectToStartOfNextExcerpt,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 if matches!(self.mode, EditorMode::SingleLine) {
13686 cx.propagate();
13687 return;
13688 }
13689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13690 self.change_selections(Default::default(), window, cx, |s| {
13691 s.move_heads_with(|map, head, _| {
13692 (
13693 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13694 SelectionGoal::None,
13695 )
13696 });
13697 })
13698 }
13699
13700 pub fn select_to_end_of_excerpt(
13701 &mut self,
13702 _: &SelectToEndOfExcerpt,
13703 window: &mut Window,
13704 cx: &mut Context<Self>,
13705 ) {
13706 if matches!(self.mode, EditorMode::SingleLine) {
13707 cx.propagate();
13708 return;
13709 }
13710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13711 self.change_selections(Default::default(), window, cx, |s| {
13712 s.move_heads_with(|map, head, _| {
13713 (
13714 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13715 SelectionGoal::None,
13716 )
13717 });
13718 })
13719 }
13720
13721 pub fn select_to_end_of_previous_excerpt(
13722 &mut self,
13723 _: &SelectToEndOfPreviousExcerpt,
13724 window: &mut Window,
13725 cx: &mut Context<Self>,
13726 ) {
13727 if matches!(self.mode, EditorMode::SingleLine) {
13728 cx.propagate();
13729 return;
13730 }
13731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13732 self.change_selections(Default::default(), window, cx, |s| {
13733 s.move_heads_with(|map, head, _| {
13734 (
13735 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13736 SelectionGoal::None,
13737 )
13738 });
13739 })
13740 }
13741
13742 pub fn move_to_beginning(
13743 &mut self,
13744 _: &MoveToBeginning,
13745 window: &mut Window,
13746 cx: &mut Context<Self>,
13747 ) {
13748 if matches!(self.mode, EditorMode::SingleLine) {
13749 cx.propagate();
13750 return;
13751 }
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.select_ranges(vec![0..0]);
13755 });
13756 }
13757
13758 pub fn select_to_beginning(
13759 &mut self,
13760 _: &SelectToBeginning,
13761 window: &mut Window,
13762 cx: &mut Context<Self>,
13763 ) {
13764 let mut selection = self.selections.last::<Point>(cx);
13765 selection.set_head(Point::zero(), SelectionGoal::None);
13766 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13767 self.change_selections(Default::default(), window, cx, |s| {
13768 s.select(vec![selection]);
13769 });
13770 }
13771
13772 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13773 if matches!(self.mode, EditorMode::SingleLine) {
13774 cx.propagate();
13775 return;
13776 }
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778 let cursor = self.buffer.read(cx).read(cx).len();
13779 self.change_selections(Default::default(), window, cx, |s| {
13780 s.select_ranges(vec![cursor..cursor])
13781 });
13782 }
13783
13784 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13785 self.nav_history = nav_history;
13786 }
13787
13788 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13789 self.nav_history.as_ref()
13790 }
13791
13792 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13793 self.push_to_nav_history(
13794 self.selections.newest_anchor().head(),
13795 None,
13796 false,
13797 true,
13798 cx,
13799 );
13800 }
13801
13802 fn push_to_nav_history(
13803 &mut self,
13804 cursor_anchor: Anchor,
13805 new_position: Option<Point>,
13806 is_deactivate: bool,
13807 always: bool,
13808 cx: &mut Context<Self>,
13809 ) {
13810 if let Some(nav_history) = self.nav_history.as_mut() {
13811 let buffer = self.buffer.read(cx).read(cx);
13812 let cursor_position = cursor_anchor.to_point(&buffer);
13813 let scroll_state = self.scroll_manager.anchor();
13814 let scroll_top_row = scroll_state.top_row(&buffer);
13815 drop(buffer);
13816
13817 if let Some(new_position) = new_position {
13818 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13819 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13820 return;
13821 }
13822 }
13823
13824 nav_history.push(
13825 Some(NavigationData {
13826 cursor_anchor,
13827 cursor_position,
13828 scroll_anchor: scroll_state,
13829 scroll_top_row,
13830 }),
13831 cx,
13832 );
13833 cx.emit(EditorEvent::PushedToNavHistory {
13834 anchor: cursor_anchor,
13835 is_deactivate,
13836 })
13837 }
13838 }
13839
13840 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13842 let buffer = self.buffer.read(cx).snapshot(cx);
13843 let mut selection = self.selections.first::<usize>(cx);
13844 selection.set_head(buffer.len(), SelectionGoal::None);
13845 self.change_selections(Default::default(), window, cx, |s| {
13846 s.select(vec![selection]);
13847 });
13848 }
13849
13850 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13852 let end = self.buffer.read(cx).read(cx).len();
13853 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13854 s.select_ranges(vec![0..end]);
13855 });
13856 }
13857
13858 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13860 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13861 let mut selections = self.selections.all::<Point>(cx);
13862 let max_point = display_map.buffer_snapshot.max_point();
13863 for selection in &mut selections {
13864 let rows = selection.spanned_rows(true, &display_map);
13865 selection.start = Point::new(rows.start.0, 0);
13866 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13867 selection.reversed = false;
13868 }
13869 self.change_selections(Default::default(), window, cx, |s| {
13870 s.select(selections);
13871 });
13872 }
13873
13874 pub fn split_selection_into_lines(
13875 &mut self,
13876 action: &SplitSelectionIntoLines,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 let selections = self
13881 .selections
13882 .all::<Point>(cx)
13883 .into_iter()
13884 .map(|selection| selection.start..selection.end)
13885 .collect::<Vec<_>>();
13886 self.unfold_ranges(&selections, true, true, cx);
13887
13888 let mut new_selection_ranges = Vec::new();
13889 {
13890 let buffer = self.buffer.read(cx).read(cx);
13891 for selection in selections {
13892 for row in selection.start.row..selection.end.row {
13893 let line_start = Point::new(row, 0);
13894 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13895
13896 if action.keep_selections {
13897 // Keep the selection range for each line
13898 let selection_start = if row == selection.start.row {
13899 selection.start
13900 } else {
13901 line_start
13902 };
13903 new_selection_ranges.push(selection_start..line_end);
13904 } else {
13905 // Collapse to cursor at end of line
13906 new_selection_ranges.push(line_end..line_end);
13907 }
13908 }
13909
13910 let is_multiline_selection = selection.start.row != selection.end.row;
13911 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13912 // so this action feels more ergonomic when paired with other selection operations
13913 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13914 if !should_skip_last {
13915 if action.keep_selections {
13916 if is_multiline_selection {
13917 let line_start = Point::new(selection.end.row, 0);
13918 new_selection_ranges.push(line_start..selection.end);
13919 } else {
13920 new_selection_ranges.push(selection.start..selection.end);
13921 }
13922 } else {
13923 new_selection_ranges.push(selection.end..selection.end);
13924 }
13925 }
13926 }
13927 }
13928 self.change_selections(Default::default(), window, cx, |s| {
13929 s.select_ranges(new_selection_ranges);
13930 });
13931 }
13932
13933 pub fn add_selection_above(
13934 &mut self,
13935 _: &AddSelectionAbove,
13936 window: &mut Window,
13937 cx: &mut Context<Self>,
13938 ) {
13939 self.add_selection(true, window, cx);
13940 }
13941
13942 pub fn add_selection_below(
13943 &mut self,
13944 _: &AddSelectionBelow,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) {
13948 self.add_selection(false, window, cx);
13949 }
13950
13951 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13952 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13953
13954 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13955 let all_selections = self.selections.all::<Point>(cx);
13956 let text_layout_details = self.text_layout_details(window);
13957
13958 let (mut columnar_selections, new_selections_to_columnarize) = {
13959 if let Some(state) = self.add_selections_state.as_ref() {
13960 let columnar_selection_ids: HashSet<_> = state
13961 .groups
13962 .iter()
13963 .flat_map(|group| group.stack.iter())
13964 .copied()
13965 .collect();
13966
13967 all_selections
13968 .into_iter()
13969 .partition(|s| columnar_selection_ids.contains(&s.id))
13970 } else {
13971 (Vec::new(), all_selections)
13972 }
13973 };
13974
13975 let mut state = self
13976 .add_selections_state
13977 .take()
13978 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13979
13980 for selection in new_selections_to_columnarize {
13981 let range = selection.display_range(&display_map).sorted();
13982 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13983 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13984 let positions = start_x.min(end_x)..start_x.max(end_x);
13985 let mut stack = Vec::new();
13986 for row in range.start.row().0..=range.end.row().0 {
13987 if let Some(selection) = self.selections.build_columnar_selection(
13988 &display_map,
13989 DisplayRow(row),
13990 &positions,
13991 selection.reversed,
13992 &text_layout_details,
13993 ) {
13994 stack.push(selection.id);
13995 columnar_selections.push(selection);
13996 }
13997 }
13998 if !stack.is_empty() {
13999 if above {
14000 stack.reverse();
14001 }
14002 state.groups.push(AddSelectionsGroup { above, stack });
14003 }
14004 }
14005
14006 let mut final_selections = Vec::new();
14007 let end_row = if above {
14008 DisplayRow(0)
14009 } else {
14010 display_map.max_point().row()
14011 };
14012
14013 let mut last_added_item_per_group = HashMap::default();
14014 for group in state.groups.iter_mut() {
14015 if let Some(last_id) = group.stack.last() {
14016 last_added_item_per_group.insert(*last_id, group);
14017 }
14018 }
14019
14020 for selection in columnar_selections {
14021 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14022 if above == group.above {
14023 let range = selection.display_range(&display_map).sorted();
14024 debug_assert_eq!(range.start.row(), range.end.row());
14025 let mut row = range.start.row();
14026 let positions =
14027 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14028 px(start)..px(end)
14029 } else {
14030 let start_x =
14031 display_map.x_for_display_point(range.start, &text_layout_details);
14032 let end_x =
14033 display_map.x_for_display_point(range.end, &text_layout_details);
14034 start_x.min(end_x)..start_x.max(end_x)
14035 };
14036
14037 let mut maybe_new_selection = None;
14038 while row != end_row {
14039 if above {
14040 row.0 -= 1;
14041 } else {
14042 row.0 += 1;
14043 }
14044 if let Some(new_selection) = self.selections.build_columnar_selection(
14045 &display_map,
14046 row,
14047 &positions,
14048 selection.reversed,
14049 &text_layout_details,
14050 ) {
14051 maybe_new_selection = Some(new_selection);
14052 break;
14053 }
14054 }
14055
14056 if let Some(new_selection) = maybe_new_selection {
14057 group.stack.push(new_selection.id);
14058 if above {
14059 final_selections.push(new_selection);
14060 final_selections.push(selection);
14061 } else {
14062 final_selections.push(selection);
14063 final_selections.push(new_selection);
14064 }
14065 } else {
14066 final_selections.push(selection);
14067 }
14068 } else {
14069 group.stack.pop();
14070 }
14071 } else {
14072 final_selections.push(selection);
14073 }
14074 }
14075
14076 self.change_selections(Default::default(), window, cx, |s| {
14077 s.select(final_selections);
14078 });
14079
14080 let final_selection_ids: HashSet<_> = self
14081 .selections
14082 .all::<Point>(cx)
14083 .iter()
14084 .map(|s| s.id)
14085 .collect();
14086 state.groups.retain_mut(|group| {
14087 // selections might get merged above so we remove invalid items from stacks
14088 group.stack.retain(|id| final_selection_ids.contains(id));
14089
14090 // single selection in stack can be treated as initial state
14091 group.stack.len() > 1
14092 });
14093
14094 if !state.groups.is_empty() {
14095 self.add_selections_state = Some(state);
14096 }
14097 }
14098
14099 fn select_match_ranges(
14100 &mut self,
14101 range: Range<usize>,
14102 reversed: bool,
14103 replace_newest: bool,
14104 auto_scroll: Option<Autoscroll>,
14105 window: &mut Window,
14106 cx: &mut Context<Editor>,
14107 ) {
14108 self.unfold_ranges(
14109 std::slice::from_ref(&range),
14110 false,
14111 auto_scroll.is_some(),
14112 cx,
14113 );
14114 let effects = if let Some(scroll) = auto_scroll {
14115 SelectionEffects::scroll(scroll)
14116 } else {
14117 SelectionEffects::no_scroll()
14118 };
14119 self.change_selections(effects, window, cx, |s| {
14120 if replace_newest {
14121 s.delete(s.newest_anchor().id);
14122 }
14123 if reversed {
14124 s.insert_range(range.end..range.start);
14125 } else {
14126 s.insert_range(range);
14127 }
14128 });
14129 }
14130
14131 pub fn select_next_match_internal(
14132 &mut self,
14133 display_map: &DisplaySnapshot,
14134 replace_newest: bool,
14135 autoscroll: Option<Autoscroll>,
14136 window: &mut Window,
14137 cx: &mut Context<Self>,
14138 ) -> Result<()> {
14139 let buffer = &display_map.buffer_snapshot;
14140 let mut selections = self.selections.all::<usize>(cx);
14141 if let Some(mut select_next_state) = self.select_next_state.take() {
14142 let query = &select_next_state.query;
14143 if !select_next_state.done {
14144 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14145 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14146 let mut next_selected_range = None;
14147
14148 let bytes_after_last_selection =
14149 buffer.bytes_in_range(last_selection.end..buffer.len());
14150 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14151 let query_matches = query
14152 .stream_find_iter(bytes_after_last_selection)
14153 .map(|result| (last_selection.end, result))
14154 .chain(
14155 query
14156 .stream_find_iter(bytes_before_first_selection)
14157 .map(|result| (0, result)),
14158 );
14159
14160 for (start_offset, query_match) in query_matches {
14161 let query_match = query_match.unwrap(); // can only fail due to I/O
14162 let offset_range =
14163 start_offset + query_match.start()..start_offset + query_match.end();
14164
14165 if !select_next_state.wordwise
14166 || (!buffer.is_inside_word(offset_range.start, false)
14167 && !buffer.is_inside_word(offset_range.end, false))
14168 {
14169 // TODO: This is n^2, because we might check all the selections
14170 if !selections
14171 .iter()
14172 .any(|selection| selection.range().overlaps(&offset_range))
14173 {
14174 next_selected_range = Some(offset_range);
14175 break;
14176 }
14177 }
14178 }
14179
14180 if let Some(next_selected_range) = next_selected_range {
14181 self.select_match_ranges(
14182 next_selected_range,
14183 last_selection.reversed,
14184 replace_newest,
14185 autoscroll,
14186 window,
14187 cx,
14188 );
14189 } else {
14190 select_next_state.done = true;
14191 }
14192 }
14193
14194 self.select_next_state = Some(select_next_state);
14195 } else {
14196 let mut only_carets = true;
14197 let mut same_text_selected = true;
14198 let mut selected_text = None;
14199
14200 let mut selections_iter = selections.iter().peekable();
14201 while let Some(selection) = selections_iter.next() {
14202 if selection.start != selection.end {
14203 only_carets = false;
14204 }
14205
14206 if same_text_selected {
14207 if selected_text.is_none() {
14208 selected_text =
14209 Some(buffer.text_for_range(selection.range()).collect::<String>());
14210 }
14211
14212 if let Some(next_selection) = selections_iter.peek() {
14213 if next_selection.range().len() == selection.range().len() {
14214 let next_selected_text = buffer
14215 .text_for_range(next_selection.range())
14216 .collect::<String>();
14217 if Some(next_selected_text) != selected_text {
14218 same_text_selected = false;
14219 selected_text = None;
14220 }
14221 } else {
14222 same_text_selected = false;
14223 selected_text = None;
14224 }
14225 }
14226 }
14227 }
14228
14229 if only_carets {
14230 for selection in &mut selections {
14231 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14232 selection.start = word_range.start;
14233 selection.end = word_range.end;
14234 selection.goal = SelectionGoal::None;
14235 selection.reversed = false;
14236 self.select_match_ranges(
14237 selection.start..selection.end,
14238 selection.reversed,
14239 replace_newest,
14240 autoscroll,
14241 window,
14242 cx,
14243 );
14244 }
14245
14246 if selections.len() == 1 {
14247 let selection = selections
14248 .last()
14249 .expect("ensured that there's only one selection");
14250 let query = buffer
14251 .text_for_range(selection.start..selection.end)
14252 .collect::<String>();
14253 let is_empty = query.is_empty();
14254 let select_state = SelectNextState {
14255 query: AhoCorasick::new(&[query])?,
14256 wordwise: true,
14257 done: is_empty,
14258 };
14259 self.select_next_state = Some(select_state);
14260 } else {
14261 self.select_next_state = None;
14262 }
14263 } else if let Some(selected_text) = selected_text {
14264 self.select_next_state = Some(SelectNextState {
14265 query: AhoCorasick::new(&[selected_text])?,
14266 wordwise: false,
14267 done: false,
14268 });
14269 self.select_next_match_internal(
14270 display_map,
14271 replace_newest,
14272 autoscroll,
14273 window,
14274 cx,
14275 )?;
14276 }
14277 }
14278 Ok(())
14279 }
14280
14281 pub fn select_all_matches(
14282 &mut self,
14283 _action: &SelectAllMatches,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) -> Result<()> {
14287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14288
14289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14290
14291 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14292 let Some(select_next_state) = self.select_next_state.as_mut() else {
14293 return Ok(());
14294 };
14295 if select_next_state.done {
14296 return Ok(());
14297 }
14298
14299 let mut new_selections = Vec::new();
14300
14301 let reversed = self.selections.oldest::<usize>(cx).reversed;
14302 let buffer = &display_map.buffer_snapshot;
14303 let query_matches = select_next_state
14304 .query
14305 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14306
14307 for query_match in query_matches.into_iter() {
14308 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14309 let offset_range = if reversed {
14310 query_match.end()..query_match.start()
14311 } else {
14312 query_match.start()..query_match.end()
14313 };
14314
14315 if !select_next_state.wordwise
14316 || (!buffer.is_inside_word(offset_range.start, false)
14317 && !buffer.is_inside_word(offset_range.end, false))
14318 {
14319 new_selections.push(offset_range.start..offset_range.end);
14320 }
14321 }
14322
14323 select_next_state.done = true;
14324
14325 if new_selections.is_empty() {
14326 log::error!("bug: new_selections is empty in select_all_matches");
14327 return Ok(());
14328 }
14329
14330 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14331 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14332 selections.select_ranges(new_selections)
14333 });
14334
14335 Ok(())
14336 }
14337
14338 pub fn select_next(
14339 &mut self,
14340 action: &SelectNext,
14341 window: &mut Window,
14342 cx: &mut Context<Self>,
14343 ) -> Result<()> {
14344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14346 self.select_next_match_internal(
14347 &display_map,
14348 action.replace_newest,
14349 Some(Autoscroll::newest()),
14350 window,
14351 cx,
14352 )?;
14353 Ok(())
14354 }
14355
14356 pub fn select_previous(
14357 &mut self,
14358 action: &SelectPrevious,
14359 window: &mut Window,
14360 cx: &mut Context<Self>,
14361 ) -> Result<()> {
14362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14364 let buffer = &display_map.buffer_snapshot;
14365 let mut selections = self.selections.all::<usize>(cx);
14366 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14367 let query = &select_prev_state.query;
14368 if !select_prev_state.done {
14369 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14370 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14371 let mut next_selected_range = None;
14372 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14373 let bytes_before_last_selection =
14374 buffer.reversed_bytes_in_range(0..last_selection.start);
14375 let bytes_after_first_selection =
14376 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14377 let query_matches = query
14378 .stream_find_iter(bytes_before_last_selection)
14379 .map(|result| (last_selection.start, result))
14380 .chain(
14381 query
14382 .stream_find_iter(bytes_after_first_selection)
14383 .map(|result| (buffer.len(), result)),
14384 );
14385 for (end_offset, query_match) in query_matches {
14386 let query_match = query_match.unwrap(); // can only fail due to I/O
14387 let offset_range =
14388 end_offset - query_match.end()..end_offset - query_match.start();
14389
14390 if !select_prev_state.wordwise
14391 || (!buffer.is_inside_word(offset_range.start, false)
14392 && !buffer.is_inside_word(offset_range.end, false))
14393 {
14394 next_selected_range = Some(offset_range);
14395 break;
14396 }
14397 }
14398
14399 if let Some(next_selected_range) = next_selected_range {
14400 self.select_match_ranges(
14401 next_selected_range,
14402 last_selection.reversed,
14403 action.replace_newest,
14404 Some(Autoscroll::newest()),
14405 window,
14406 cx,
14407 );
14408 } else {
14409 select_prev_state.done = true;
14410 }
14411 }
14412
14413 self.select_prev_state = Some(select_prev_state);
14414 } else {
14415 let mut only_carets = true;
14416 let mut same_text_selected = true;
14417 let mut selected_text = None;
14418
14419 let mut selections_iter = selections.iter().peekable();
14420 while let Some(selection) = selections_iter.next() {
14421 if selection.start != selection.end {
14422 only_carets = false;
14423 }
14424
14425 if same_text_selected {
14426 if selected_text.is_none() {
14427 selected_text =
14428 Some(buffer.text_for_range(selection.range()).collect::<String>());
14429 }
14430
14431 if let Some(next_selection) = selections_iter.peek() {
14432 if next_selection.range().len() == selection.range().len() {
14433 let next_selected_text = buffer
14434 .text_for_range(next_selection.range())
14435 .collect::<String>();
14436 if Some(next_selected_text) != selected_text {
14437 same_text_selected = false;
14438 selected_text = None;
14439 }
14440 } else {
14441 same_text_selected = false;
14442 selected_text = None;
14443 }
14444 }
14445 }
14446 }
14447
14448 if only_carets {
14449 for selection in &mut selections {
14450 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14451 selection.start = word_range.start;
14452 selection.end = word_range.end;
14453 selection.goal = SelectionGoal::None;
14454 selection.reversed = false;
14455 self.select_match_ranges(
14456 selection.start..selection.end,
14457 selection.reversed,
14458 action.replace_newest,
14459 Some(Autoscroll::newest()),
14460 window,
14461 cx,
14462 );
14463 }
14464 if selections.len() == 1 {
14465 let selection = selections
14466 .last()
14467 .expect("ensured that there's only one selection");
14468 let query = buffer
14469 .text_for_range(selection.start..selection.end)
14470 .collect::<String>();
14471 let is_empty = query.is_empty();
14472 let select_state = SelectNextState {
14473 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14474 wordwise: true,
14475 done: is_empty,
14476 };
14477 self.select_prev_state = Some(select_state);
14478 } else {
14479 self.select_prev_state = None;
14480 }
14481 } else if let Some(selected_text) = selected_text {
14482 self.select_prev_state = Some(SelectNextState {
14483 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14484 wordwise: false,
14485 done: false,
14486 });
14487 self.select_previous(action, window, cx)?;
14488 }
14489 }
14490 Ok(())
14491 }
14492
14493 pub fn find_next_match(
14494 &mut self,
14495 _: &FindNextMatch,
14496 window: &mut Window,
14497 cx: &mut Context<Self>,
14498 ) -> Result<()> {
14499 let selections = self.selections.disjoint_anchors();
14500 match selections.first() {
14501 Some(first) if selections.len() >= 2 => {
14502 self.change_selections(Default::default(), window, cx, |s| {
14503 s.select_ranges([first.range()]);
14504 });
14505 }
14506 _ => self.select_next(
14507 &SelectNext {
14508 replace_newest: true,
14509 },
14510 window,
14511 cx,
14512 )?,
14513 }
14514 Ok(())
14515 }
14516
14517 pub fn find_previous_match(
14518 &mut self,
14519 _: &FindPreviousMatch,
14520 window: &mut Window,
14521 cx: &mut Context<Self>,
14522 ) -> Result<()> {
14523 let selections = self.selections.disjoint_anchors();
14524 match selections.last() {
14525 Some(last) if selections.len() >= 2 => {
14526 self.change_selections(Default::default(), window, cx, |s| {
14527 s.select_ranges([last.range()]);
14528 });
14529 }
14530 _ => self.select_previous(
14531 &SelectPrevious {
14532 replace_newest: true,
14533 },
14534 window,
14535 cx,
14536 )?,
14537 }
14538 Ok(())
14539 }
14540
14541 pub fn toggle_comments(
14542 &mut self,
14543 action: &ToggleComments,
14544 window: &mut Window,
14545 cx: &mut Context<Self>,
14546 ) {
14547 if self.read_only(cx) {
14548 return;
14549 }
14550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14551 let text_layout_details = &self.text_layout_details(window);
14552 self.transact(window, cx, |this, window, cx| {
14553 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14554 let mut edits = Vec::new();
14555 let mut selection_edit_ranges = Vec::new();
14556 let mut last_toggled_row = None;
14557 let snapshot = this.buffer.read(cx).read(cx);
14558 let empty_str: Arc<str> = Arc::default();
14559 let mut suffixes_inserted = Vec::new();
14560 let ignore_indent = action.ignore_indent;
14561
14562 fn comment_prefix_range(
14563 snapshot: &MultiBufferSnapshot,
14564 row: MultiBufferRow,
14565 comment_prefix: &str,
14566 comment_prefix_whitespace: &str,
14567 ignore_indent: bool,
14568 ) -> Range<Point> {
14569 let indent_size = if ignore_indent {
14570 0
14571 } else {
14572 snapshot.indent_size_for_line(row).len
14573 };
14574
14575 let start = Point::new(row.0, indent_size);
14576
14577 let mut line_bytes = snapshot
14578 .bytes_in_range(start..snapshot.max_point())
14579 .flatten()
14580 .copied();
14581
14582 // If this line currently begins with the line comment prefix, then record
14583 // the range containing the prefix.
14584 if line_bytes
14585 .by_ref()
14586 .take(comment_prefix.len())
14587 .eq(comment_prefix.bytes())
14588 {
14589 // Include any whitespace that matches the comment prefix.
14590 let matching_whitespace_len = line_bytes
14591 .zip(comment_prefix_whitespace.bytes())
14592 .take_while(|(a, b)| a == b)
14593 .count() as u32;
14594 let end = Point::new(
14595 start.row,
14596 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14597 );
14598 start..end
14599 } else {
14600 start..start
14601 }
14602 }
14603
14604 fn comment_suffix_range(
14605 snapshot: &MultiBufferSnapshot,
14606 row: MultiBufferRow,
14607 comment_suffix: &str,
14608 comment_suffix_has_leading_space: bool,
14609 ) -> Range<Point> {
14610 let end = Point::new(row.0, snapshot.line_len(row));
14611 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14612
14613 let mut line_end_bytes = snapshot
14614 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14615 .flatten()
14616 .copied();
14617
14618 let leading_space_len = if suffix_start_column > 0
14619 && line_end_bytes.next() == Some(b' ')
14620 && comment_suffix_has_leading_space
14621 {
14622 1
14623 } else {
14624 0
14625 };
14626
14627 // If this line currently begins with the line comment prefix, then record
14628 // the range containing the prefix.
14629 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14630 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14631 start..end
14632 } else {
14633 end..end
14634 }
14635 }
14636
14637 // TODO: Handle selections that cross excerpts
14638 for selection in &mut selections {
14639 let start_column = snapshot
14640 .indent_size_for_line(MultiBufferRow(selection.start.row))
14641 .len;
14642 let language = if let Some(language) =
14643 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14644 {
14645 language
14646 } else {
14647 continue;
14648 };
14649
14650 selection_edit_ranges.clear();
14651
14652 // If multiple selections contain a given row, avoid processing that
14653 // row more than once.
14654 let mut start_row = MultiBufferRow(selection.start.row);
14655 if last_toggled_row == Some(start_row) {
14656 start_row = start_row.next_row();
14657 }
14658 let end_row =
14659 if selection.end.row > selection.start.row && selection.end.column == 0 {
14660 MultiBufferRow(selection.end.row - 1)
14661 } else {
14662 MultiBufferRow(selection.end.row)
14663 };
14664 last_toggled_row = Some(end_row);
14665
14666 if start_row > end_row {
14667 continue;
14668 }
14669
14670 // If the language has line comments, toggle those.
14671 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14672
14673 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14674 if ignore_indent {
14675 full_comment_prefixes = full_comment_prefixes
14676 .into_iter()
14677 .map(|s| Arc::from(s.trim_end()))
14678 .collect();
14679 }
14680
14681 if !full_comment_prefixes.is_empty() {
14682 let first_prefix = full_comment_prefixes
14683 .first()
14684 .expect("prefixes is non-empty");
14685 let prefix_trimmed_lengths = full_comment_prefixes
14686 .iter()
14687 .map(|p| p.trim_end_matches(' ').len())
14688 .collect::<SmallVec<[usize; 4]>>();
14689
14690 let mut all_selection_lines_are_comments = true;
14691
14692 for row in start_row.0..=end_row.0 {
14693 let row = MultiBufferRow(row);
14694 if start_row < end_row && snapshot.is_line_blank(row) {
14695 continue;
14696 }
14697
14698 let prefix_range = full_comment_prefixes
14699 .iter()
14700 .zip(prefix_trimmed_lengths.iter().copied())
14701 .map(|(prefix, trimmed_prefix_len)| {
14702 comment_prefix_range(
14703 snapshot.deref(),
14704 row,
14705 &prefix[..trimmed_prefix_len],
14706 &prefix[trimmed_prefix_len..],
14707 ignore_indent,
14708 )
14709 })
14710 .max_by_key(|range| range.end.column - range.start.column)
14711 .expect("prefixes is non-empty");
14712
14713 if prefix_range.is_empty() {
14714 all_selection_lines_are_comments = false;
14715 }
14716
14717 selection_edit_ranges.push(prefix_range);
14718 }
14719
14720 if all_selection_lines_are_comments {
14721 edits.extend(
14722 selection_edit_ranges
14723 .iter()
14724 .cloned()
14725 .map(|range| (range, empty_str.clone())),
14726 );
14727 } else {
14728 let min_column = selection_edit_ranges
14729 .iter()
14730 .map(|range| range.start.column)
14731 .min()
14732 .unwrap_or(0);
14733 edits.extend(selection_edit_ranges.iter().map(|range| {
14734 let position = Point::new(range.start.row, min_column);
14735 (position..position, first_prefix.clone())
14736 }));
14737 }
14738 } else if let Some(BlockCommentConfig {
14739 start: full_comment_prefix,
14740 end: comment_suffix,
14741 ..
14742 }) = language.block_comment()
14743 {
14744 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14745 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14746 let prefix_range = comment_prefix_range(
14747 snapshot.deref(),
14748 start_row,
14749 comment_prefix,
14750 comment_prefix_whitespace,
14751 ignore_indent,
14752 );
14753 let suffix_range = comment_suffix_range(
14754 snapshot.deref(),
14755 end_row,
14756 comment_suffix.trim_start_matches(' '),
14757 comment_suffix.starts_with(' '),
14758 );
14759
14760 if prefix_range.is_empty() || suffix_range.is_empty() {
14761 edits.push((
14762 prefix_range.start..prefix_range.start,
14763 full_comment_prefix.clone(),
14764 ));
14765 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14766 suffixes_inserted.push((end_row, comment_suffix.len()));
14767 } else {
14768 edits.push((prefix_range, empty_str.clone()));
14769 edits.push((suffix_range, empty_str.clone()));
14770 }
14771 } else {
14772 continue;
14773 }
14774 }
14775
14776 drop(snapshot);
14777 this.buffer.update(cx, |buffer, cx| {
14778 buffer.edit(edits, None, cx);
14779 });
14780
14781 // Adjust selections so that they end before any comment suffixes that
14782 // were inserted.
14783 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14784 let mut selections = this.selections.all::<Point>(cx);
14785 let snapshot = this.buffer.read(cx).read(cx);
14786 for selection in &mut selections {
14787 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14788 match row.cmp(&MultiBufferRow(selection.end.row)) {
14789 Ordering::Less => {
14790 suffixes_inserted.next();
14791 continue;
14792 }
14793 Ordering::Greater => break,
14794 Ordering::Equal => {
14795 if selection.end.column == snapshot.line_len(row) {
14796 if selection.is_empty() {
14797 selection.start.column -= suffix_len as u32;
14798 }
14799 selection.end.column -= suffix_len as u32;
14800 }
14801 break;
14802 }
14803 }
14804 }
14805 }
14806
14807 drop(snapshot);
14808 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14809
14810 let selections = this.selections.all::<Point>(cx);
14811 let selections_on_single_row = selections.windows(2).all(|selections| {
14812 selections[0].start.row == selections[1].start.row
14813 && selections[0].end.row == selections[1].end.row
14814 && selections[0].start.row == selections[0].end.row
14815 });
14816 let selections_selecting = selections
14817 .iter()
14818 .any(|selection| selection.start != selection.end);
14819 let advance_downwards = action.advance_downwards
14820 && selections_on_single_row
14821 && !selections_selecting
14822 && !matches!(this.mode, EditorMode::SingleLine);
14823
14824 if advance_downwards {
14825 let snapshot = this.buffer.read(cx).snapshot(cx);
14826
14827 this.change_selections(Default::default(), window, cx, |s| {
14828 s.move_cursors_with(|display_snapshot, display_point, _| {
14829 let mut point = display_point.to_point(display_snapshot);
14830 point.row += 1;
14831 point = snapshot.clip_point(point, Bias::Left);
14832 let display_point = point.to_display_point(display_snapshot);
14833 let goal = SelectionGoal::HorizontalPosition(
14834 display_snapshot
14835 .x_for_display_point(display_point, text_layout_details)
14836 .into(),
14837 );
14838 (display_point, goal)
14839 })
14840 });
14841 }
14842 });
14843 }
14844
14845 pub fn select_enclosing_symbol(
14846 &mut self,
14847 _: &SelectEnclosingSymbol,
14848 window: &mut Window,
14849 cx: &mut Context<Self>,
14850 ) {
14851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14852
14853 let buffer = self.buffer.read(cx).snapshot(cx);
14854 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14855
14856 fn update_selection(
14857 selection: &Selection<usize>,
14858 buffer_snap: &MultiBufferSnapshot,
14859 ) -> Option<Selection<usize>> {
14860 let cursor = selection.head();
14861 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14862 for symbol in symbols.iter().rev() {
14863 let start = symbol.range.start.to_offset(buffer_snap);
14864 let end = symbol.range.end.to_offset(buffer_snap);
14865 let new_range = start..end;
14866 if start < selection.start || end > selection.end {
14867 return Some(Selection {
14868 id: selection.id,
14869 start: new_range.start,
14870 end: new_range.end,
14871 goal: SelectionGoal::None,
14872 reversed: selection.reversed,
14873 });
14874 }
14875 }
14876 None
14877 }
14878
14879 let mut selected_larger_symbol = false;
14880 let new_selections = old_selections
14881 .iter()
14882 .map(|selection| match update_selection(selection, &buffer) {
14883 Some(new_selection) => {
14884 if new_selection.range() != selection.range() {
14885 selected_larger_symbol = true;
14886 }
14887 new_selection
14888 }
14889 None => selection.clone(),
14890 })
14891 .collect::<Vec<_>>();
14892
14893 if selected_larger_symbol {
14894 self.change_selections(Default::default(), window, cx, |s| {
14895 s.select(new_selections);
14896 });
14897 }
14898 }
14899
14900 pub fn select_larger_syntax_node(
14901 &mut self,
14902 _: &SelectLargerSyntaxNode,
14903 window: &mut Window,
14904 cx: &mut Context<Self>,
14905 ) {
14906 let Some(visible_row_count) = self.visible_row_count() else {
14907 return;
14908 };
14909 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14910 if old_selections.is_empty() {
14911 return;
14912 }
14913
14914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14915
14916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14917 let buffer = self.buffer.read(cx).snapshot(cx);
14918
14919 let mut selected_larger_node = false;
14920 let mut new_selections = old_selections
14921 .iter()
14922 .map(|selection| {
14923 let old_range = selection.start..selection.end;
14924
14925 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14926 // manually select word at selection
14927 if ["string_content", "inline"].contains(&node.kind()) {
14928 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14929 // ignore if word is already selected
14930 if !word_range.is_empty() && old_range != word_range {
14931 let (last_word_range, _) =
14932 buffer.surrounding_word(old_range.end, false);
14933 // only select word if start and end point belongs to same word
14934 if word_range == last_word_range {
14935 selected_larger_node = true;
14936 return Selection {
14937 id: selection.id,
14938 start: word_range.start,
14939 end: word_range.end,
14940 goal: SelectionGoal::None,
14941 reversed: selection.reversed,
14942 };
14943 }
14944 }
14945 }
14946 }
14947
14948 let mut new_range = old_range.clone();
14949 while let Some((_node, containing_range)) =
14950 buffer.syntax_ancestor(new_range.clone())
14951 {
14952 new_range = match containing_range {
14953 MultiOrSingleBufferOffsetRange::Single(_) => break,
14954 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14955 };
14956 if !display_map.intersects_fold(new_range.start)
14957 && !display_map.intersects_fold(new_range.end)
14958 {
14959 break;
14960 }
14961 }
14962
14963 selected_larger_node |= new_range != old_range;
14964 Selection {
14965 id: selection.id,
14966 start: new_range.start,
14967 end: new_range.end,
14968 goal: SelectionGoal::None,
14969 reversed: selection.reversed,
14970 }
14971 })
14972 .collect::<Vec<_>>();
14973
14974 if !selected_larger_node {
14975 return; // don't put this call in the history
14976 }
14977
14978 // scroll based on transformation done to the last selection created by the user
14979 let (last_old, last_new) = old_selections
14980 .last()
14981 .zip(new_selections.last().cloned())
14982 .expect("old_selections isn't empty");
14983
14984 // revert selection
14985 let is_selection_reversed = {
14986 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14987 new_selections.last_mut().expect("checked above").reversed =
14988 should_newest_selection_be_reversed;
14989 should_newest_selection_be_reversed
14990 };
14991
14992 if selected_larger_node {
14993 self.select_syntax_node_history.disable_clearing = true;
14994 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14995 s.select(new_selections.clone());
14996 });
14997 self.select_syntax_node_history.disable_clearing = false;
14998 }
14999
15000 let start_row = last_new.start.to_display_point(&display_map).row().0;
15001 let end_row = last_new.end.to_display_point(&display_map).row().0;
15002 let selection_height = end_row - start_row + 1;
15003 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15004
15005 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15006 let scroll_behavior = if fits_on_the_screen {
15007 self.request_autoscroll(Autoscroll::fit(), cx);
15008 SelectSyntaxNodeScrollBehavior::FitSelection
15009 } else if is_selection_reversed {
15010 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15011 SelectSyntaxNodeScrollBehavior::CursorTop
15012 } else {
15013 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15014 SelectSyntaxNodeScrollBehavior::CursorBottom
15015 };
15016
15017 self.select_syntax_node_history.push((
15018 old_selections,
15019 scroll_behavior,
15020 is_selection_reversed,
15021 ));
15022 }
15023
15024 pub fn select_smaller_syntax_node(
15025 &mut self,
15026 _: &SelectSmallerSyntaxNode,
15027 window: &mut Window,
15028 cx: &mut Context<Self>,
15029 ) {
15030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15031
15032 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15033 self.select_syntax_node_history.pop()
15034 {
15035 if let Some(selection) = selections.last_mut() {
15036 selection.reversed = is_selection_reversed;
15037 }
15038
15039 self.select_syntax_node_history.disable_clearing = true;
15040 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15041 s.select(selections.to_vec());
15042 });
15043 self.select_syntax_node_history.disable_clearing = false;
15044
15045 match scroll_behavior {
15046 SelectSyntaxNodeScrollBehavior::CursorTop => {
15047 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15048 }
15049 SelectSyntaxNodeScrollBehavior::FitSelection => {
15050 self.request_autoscroll(Autoscroll::fit(), cx);
15051 }
15052 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15053 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15054 }
15055 }
15056 }
15057 }
15058
15059 pub fn unwrap_syntax_node(
15060 &mut self,
15061 _: &UnwrapSyntaxNode,
15062 window: &mut Window,
15063 cx: &mut Context<Self>,
15064 ) {
15065 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15066
15067 let buffer = self.buffer.read(cx).snapshot(cx);
15068 let selections = self
15069 .selections
15070 .all::<usize>(cx)
15071 .into_iter()
15072 // subtracting the offset requires sorting
15073 .sorted_by_key(|i| i.start);
15074
15075 let full_edits = selections
15076 .into_iter()
15077 .filter_map(|selection| {
15078 // Only requires two branches once if-let-chains stabilize (#53667)
15079 let child = if !selection.is_empty() {
15080 selection.range()
15081 } else if let Some((_, ancestor_range)) =
15082 buffer.syntax_ancestor(selection.start..selection.end)
15083 {
15084 match ancestor_range {
15085 MultiOrSingleBufferOffsetRange::Single(range) => range,
15086 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15087 }
15088 } else {
15089 selection.range()
15090 };
15091
15092 let mut parent = child.clone();
15093 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15094 parent = match ancestor_range {
15095 MultiOrSingleBufferOffsetRange::Single(range) => range,
15096 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15097 };
15098 if parent.start < child.start || parent.end > child.end {
15099 break;
15100 }
15101 }
15102
15103 if parent == child {
15104 return None;
15105 }
15106 let text = buffer.text_for_range(child).collect::<String>();
15107 Some((selection.id, parent, text))
15108 })
15109 .collect::<Vec<_>>();
15110
15111 self.transact(window, cx, |this, window, cx| {
15112 this.buffer.update(cx, |buffer, cx| {
15113 buffer.edit(
15114 full_edits
15115 .iter()
15116 .map(|(_, p, t)| (p.clone(), t.clone()))
15117 .collect::<Vec<_>>(),
15118 None,
15119 cx,
15120 );
15121 });
15122 this.change_selections(Default::default(), window, cx, |s| {
15123 let mut offset = 0;
15124 let mut selections = vec![];
15125 for (id, parent, text) in full_edits {
15126 let start = parent.start - offset;
15127 offset += parent.len() - text.len();
15128 selections.push(Selection {
15129 id,
15130 start,
15131 end: start + text.len(),
15132 reversed: false,
15133 goal: Default::default(),
15134 });
15135 }
15136 s.select(selections);
15137 });
15138 });
15139 }
15140
15141 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15142 if !EditorSettings::get_global(cx).gutter.runnables {
15143 self.clear_tasks();
15144 return Task::ready(());
15145 }
15146 let project = self.project().map(Entity::downgrade);
15147 let task_sources = self.lsp_task_sources(cx);
15148 let multi_buffer = self.buffer.downgrade();
15149 cx.spawn_in(window, async move |editor, cx| {
15150 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15151 let Some(project) = project.and_then(|p| p.upgrade()) else {
15152 return;
15153 };
15154 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15155 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15156 }) else {
15157 return;
15158 };
15159
15160 let hide_runnables = project
15161 .update(cx, |project, _| project.is_via_collab())
15162 .unwrap_or(true);
15163 if hide_runnables {
15164 return;
15165 }
15166 let new_rows =
15167 cx.background_spawn({
15168 let snapshot = display_snapshot.clone();
15169 async move {
15170 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15171 }
15172 })
15173 .await;
15174 let Ok(lsp_tasks) =
15175 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15176 else {
15177 return;
15178 };
15179 let lsp_tasks = lsp_tasks.await;
15180
15181 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15182 lsp_tasks
15183 .into_iter()
15184 .flat_map(|(kind, tasks)| {
15185 tasks.into_iter().filter_map(move |(location, task)| {
15186 Some((kind.clone(), location?, task))
15187 })
15188 })
15189 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15190 let buffer = location.target.buffer;
15191 let buffer_snapshot = buffer.read(cx).snapshot();
15192 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15193 |(excerpt_id, snapshot, _)| {
15194 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15195 display_snapshot
15196 .buffer_snapshot
15197 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15198 } else {
15199 None
15200 }
15201 },
15202 );
15203 if let Some(offset) = offset {
15204 let task_buffer_range =
15205 location.target.range.to_point(&buffer_snapshot);
15206 let context_buffer_range =
15207 task_buffer_range.to_offset(&buffer_snapshot);
15208 let context_range = BufferOffset(context_buffer_range.start)
15209 ..BufferOffset(context_buffer_range.end);
15210
15211 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15212 .or_insert_with(|| RunnableTasks {
15213 templates: Vec::new(),
15214 offset,
15215 column: task_buffer_range.start.column,
15216 extra_variables: HashMap::default(),
15217 context_range,
15218 })
15219 .templates
15220 .push((kind, task.original_task().clone()));
15221 }
15222
15223 acc
15224 })
15225 }) else {
15226 return;
15227 };
15228
15229 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15230 buffer.language_settings(cx).tasks.prefer_lsp
15231 }) else {
15232 return;
15233 };
15234
15235 let rows = Self::runnable_rows(
15236 project,
15237 display_snapshot,
15238 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15239 new_rows,
15240 cx.clone(),
15241 )
15242 .await;
15243 editor
15244 .update(cx, |editor, _| {
15245 editor.clear_tasks();
15246 for (key, mut value) in rows {
15247 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15248 value.templates.extend(lsp_tasks.templates);
15249 }
15250
15251 editor.insert_tasks(key, value);
15252 }
15253 for (key, value) in lsp_tasks_by_rows {
15254 editor.insert_tasks(key, value);
15255 }
15256 })
15257 .ok();
15258 })
15259 }
15260 fn fetch_runnable_ranges(
15261 snapshot: &DisplaySnapshot,
15262 range: Range<Anchor>,
15263 ) -> Vec<language::RunnableRange> {
15264 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15265 }
15266
15267 fn runnable_rows(
15268 project: Entity<Project>,
15269 snapshot: DisplaySnapshot,
15270 prefer_lsp: bool,
15271 runnable_ranges: Vec<RunnableRange>,
15272 cx: AsyncWindowContext,
15273 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15274 cx.spawn(async move |cx| {
15275 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15276 for mut runnable in runnable_ranges {
15277 let Some(tasks) = cx
15278 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15279 .ok()
15280 else {
15281 continue;
15282 };
15283 let mut tasks = tasks.await;
15284
15285 if prefer_lsp {
15286 tasks.retain(|(task_kind, _)| {
15287 !matches!(task_kind, TaskSourceKind::Language { .. })
15288 });
15289 }
15290 if tasks.is_empty() {
15291 continue;
15292 }
15293
15294 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15295 let Some(row) = snapshot
15296 .buffer_snapshot
15297 .buffer_line_for_row(MultiBufferRow(point.row))
15298 .map(|(_, range)| range.start.row)
15299 else {
15300 continue;
15301 };
15302
15303 let context_range =
15304 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15305 runnable_rows.push((
15306 (runnable.buffer_id, row),
15307 RunnableTasks {
15308 templates: tasks,
15309 offset: snapshot
15310 .buffer_snapshot
15311 .anchor_before(runnable.run_range.start),
15312 context_range,
15313 column: point.column,
15314 extra_variables: runnable.extra_captures,
15315 },
15316 ));
15317 }
15318 runnable_rows
15319 })
15320 }
15321
15322 fn templates_with_tags(
15323 project: &Entity<Project>,
15324 runnable: &mut Runnable,
15325 cx: &mut App,
15326 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15327 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15328 let (worktree_id, file) = project
15329 .buffer_for_id(runnable.buffer, cx)
15330 .and_then(|buffer| buffer.read(cx).file())
15331 .map(|file| (file.worktree_id(cx), file.clone()))
15332 .unzip();
15333
15334 (
15335 project.task_store().read(cx).task_inventory().cloned(),
15336 worktree_id,
15337 file,
15338 )
15339 });
15340
15341 let tags = mem::take(&mut runnable.tags);
15342 let language = runnable.language.clone();
15343 cx.spawn(async move |cx| {
15344 let mut templates_with_tags = Vec::new();
15345 if let Some(inventory) = inventory {
15346 for RunnableTag(tag) in tags {
15347 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15348 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15349 }) else {
15350 return templates_with_tags;
15351 };
15352 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15353 move |(_, template)| {
15354 template.tags.iter().any(|source_tag| source_tag == &tag)
15355 },
15356 ));
15357 }
15358 }
15359 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15360
15361 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15362 // Strongest source wins; if we have worktree tag binding, prefer that to
15363 // global and language bindings;
15364 // if we have a global binding, prefer that to language binding.
15365 let first_mismatch = templates_with_tags
15366 .iter()
15367 .position(|(tag_source, _)| tag_source != leading_tag_source);
15368 if let Some(index) = first_mismatch {
15369 templates_with_tags.truncate(index);
15370 }
15371 }
15372
15373 templates_with_tags
15374 })
15375 }
15376
15377 pub fn move_to_enclosing_bracket(
15378 &mut self,
15379 _: &MoveToEnclosingBracket,
15380 window: &mut Window,
15381 cx: &mut Context<Self>,
15382 ) {
15383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15384 self.change_selections(Default::default(), window, cx, |s| {
15385 s.move_offsets_with(|snapshot, selection| {
15386 let Some(enclosing_bracket_ranges) =
15387 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15388 else {
15389 return;
15390 };
15391
15392 let mut best_length = usize::MAX;
15393 let mut best_inside = false;
15394 let mut best_in_bracket_range = false;
15395 let mut best_destination = None;
15396 for (open, close) in enclosing_bracket_ranges {
15397 let close = close.to_inclusive();
15398 let length = close.end() - open.start;
15399 let inside = selection.start >= open.end && selection.end <= *close.start();
15400 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15401 || close.contains(&selection.head());
15402
15403 // If best is next to a bracket and current isn't, skip
15404 if !in_bracket_range && best_in_bracket_range {
15405 continue;
15406 }
15407
15408 // Prefer smaller lengths unless best is inside and current isn't
15409 if length > best_length && (best_inside || !inside) {
15410 continue;
15411 }
15412
15413 best_length = length;
15414 best_inside = inside;
15415 best_in_bracket_range = in_bracket_range;
15416 best_destination = Some(
15417 if close.contains(&selection.start) && close.contains(&selection.end) {
15418 if inside { open.end } else { open.start }
15419 } else if inside {
15420 *close.start()
15421 } else {
15422 *close.end()
15423 },
15424 );
15425 }
15426
15427 if let Some(destination) = best_destination {
15428 selection.collapse_to(destination, SelectionGoal::None);
15429 }
15430 })
15431 });
15432 }
15433
15434 pub fn undo_selection(
15435 &mut self,
15436 _: &UndoSelection,
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.undo_stack.pop_back() {
15442 self.selection_history.mode = SelectionHistoryMode::Undoing;
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 redo_selection(
15461 &mut self,
15462 _: &RedoSelection,
15463 window: &mut Window,
15464 cx: &mut Context<Self>,
15465 ) {
15466 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15467 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15468 self.selection_history.mode = SelectionHistoryMode::Redoing;
15469 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15470 this.end_selection(window, cx);
15471 this.change_selections(
15472 SelectionEffects::scroll(Autoscroll::newest()),
15473 window,
15474 cx,
15475 |s| s.select_anchors(entry.selections.to_vec()),
15476 );
15477 });
15478 self.selection_history.mode = SelectionHistoryMode::Normal;
15479
15480 self.select_next_state = entry.select_next_state;
15481 self.select_prev_state = entry.select_prev_state;
15482 self.add_selections_state = entry.add_selections_state;
15483 }
15484 }
15485
15486 pub fn expand_excerpts(
15487 &mut self,
15488 action: &ExpandExcerpts,
15489 _: &mut Window,
15490 cx: &mut Context<Self>,
15491 ) {
15492 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15493 }
15494
15495 pub fn expand_excerpts_down(
15496 &mut self,
15497 action: &ExpandExcerptsDown,
15498 _: &mut Window,
15499 cx: &mut Context<Self>,
15500 ) {
15501 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15502 }
15503
15504 pub fn expand_excerpts_up(
15505 &mut self,
15506 action: &ExpandExcerptsUp,
15507 _: &mut Window,
15508 cx: &mut Context<Self>,
15509 ) {
15510 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15511 }
15512
15513 pub fn expand_excerpts_for_direction(
15514 &mut self,
15515 lines: u32,
15516 direction: ExpandExcerptDirection,
15517
15518 cx: &mut Context<Self>,
15519 ) {
15520 let selections = self.selections.disjoint_anchors();
15521
15522 let lines = if lines == 0 {
15523 EditorSettings::get_global(cx).expand_excerpt_lines
15524 } else {
15525 lines
15526 };
15527
15528 self.buffer.update(cx, |buffer, cx| {
15529 let snapshot = buffer.snapshot(cx);
15530 let mut excerpt_ids = selections
15531 .iter()
15532 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15533 .collect::<Vec<_>>();
15534 excerpt_ids.sort();
15535 excerpt_ids.dedup();
15536 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15537 })
15538 }
15539
15540 pub fn expand_excerpt(
15541 &mut self,
15542 excerpt: ExcerptId,
15543 direction: ExpandExcerptDirection,
15544 window: &mut Window,
15545 cx: &mut Context<Self>,
15546 ) {
15547 let current_scroll_position = self.scroll_position(cx);
15548 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15549 let mut should_scroll_up = false;
15550
15551 if direction == ExpandExcerptDirection::Down {
15552 let multi_buffer = self.buffer.read(cx);
15553 let snapshot = multi_buffer.snapshot(cx);
15554 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15555 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15556 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15557 {
15558 let buffer_snapshot = buffer.read(cx).snapshot();
15559 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15560 let last_row = buffer_snapshot.max_point().row;
15561 let lines_below = last_row.saturating_sub(excerpt_end_row);
15562 should_scroll_up = lines_below >= lines_to_expand;
15563 }
15564 }
15565
15566 self.buffer.update(cx, |buffer, cx| {
15567 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15568 });
15569
15570 if should_scroll_up {
15571 let new_scroll_position =
15572 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15573 self.set_scroll_position(new_scroll_position, window, cx);
15574 }
15575 }
15576
15577 pub fn go_to_singleton_buffer_point(
15578 &mut self,
15579 point: Point,
15580 window: &mut Window,
15581 cx: &mut Context<Self>,
15582 ) {
15583 self.go_to_singleton_buffer_range(point..point, window, cx);
15584 }
15585
15586 pub fn go_to_singleton_buffer_range(
15587 &mut self,
15588 range: Range<Point>,
15589 window: &mut Window,
15590 cx: &mut Context<Self>,
15591 ) {
15592 let multibuffer = self.buffer().read(cx);
15593 let Some(buffer) = multibuffer.as_singleton() else {
15594 return;
15595 };
15596 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15597 return;
15598 };
15599 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15600 return;
15601 };
15602 self.change_selections(
15603 SelectionEffects::default().nav_history(true),
15604 window,
15605 cx,
15606 |s| s.select_anchor_ranges([start..end]),
15607 );
15608 }
15609
15610 pub fn go_to_diagnostic(
15611 &mut self,
15612 action: &GoToDiagnostic,
15613 window: &mut Window,
15614 cx: &mut Context<Self>,
15615 ) {
15616 if !self.diagnostics_enabled() {
15617 return;
15618 }
15619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15620 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15621 }
15622
15623 pub fn go_to_prev_diagnostic(
15624 &mut self,
15625 action: &GoToPreviousDiagnostic,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) {
15629 if !self.diagnostics_enabled() {
15630 return;
15631 }
15632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15633 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15634 }
15635
15636 pub fn go_to_diagnostic_impl(
15637 &mut self,
15638 direction: Direction,
15639 severity: GoToDiagnosticSeverityFilter,
15640 window: &mut Window,
15641 cx: &mut Context<Self>,
15642 ) {
15643 let buffer = self.buffer.read(cx).snapshot(cx);
15644 let selection = self.selections.newest::<usize>(cx);
15645
15646 let mut active_group_id = None;
15647 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15648 && active_group.active_range.start.to_offset(&buffer) == selection.start
15649 {
15650 active_group_id = Some(active_group.group_id);
15651 }
15652
15653 fn filtered(
15654 snapshot: EditorSnapshot,
15655 severity: GoToDiagnosticSeverityFilter,
15656 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15657 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15658 diagnostics
15659 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15660 .filter(|entry| entry.range.start != entry.range.end)
15661 .filter(|entry| !entry.diagnostic.is_unnecessary)
15662 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15663 }
15664
15665 let snapshot = self.snapshot(window, cx);
15666 let before = filtered(
15667 snapshot.clone(),
15668 severity,
15669 buffer
15670 .diagnostics_in_range(0..selection.start)
15671 .filter(|entry| entry.range.start <= selection.start),
15672 );
15673 let after = filtered(
15674 snapshot,
15675 severity,
15676 buffer
15677 .diagnostics_in_range(selection.start..buffer.len())
15678 .filter(|entry| entry.range.start >= selection.start),
15679 );
15680
15681 let mut found: Option<DiagnosticEntry<usize>> = None;
15682 if direction == Direction::Prev {
15683 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15684 {
15685 for diagnostic in prev_diagnostics.into_iter().rev() {
15686 if diagnostic.range.start != selection.start
15687 || active_group_id
15688 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15689 {
15690 found = Some(diagnostic);
15691 break 'outer;
15692 }
15693 }
15694 }
15695 } else {
15696 for diagnostic in after.chain(before) {
15697 if diagnostic.range.start != selection.start
15698 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15699 {
15700 found = Some(diagnostic);
15701 break;
15702 }
15703 }
15704 }
15705 let Some(next_diagnostic) = found else {
15706 return;
15707 };
15708
15709 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15710 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15711 return;
15712 };
15713 self.change_selections(Default::default(), window, cx, |s| {
15714 s.select_ranges(vec![
15715 next_diagnostic.range.start..next_diagnostic.range.start,
15716 ])
15717 });
15718 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15719 self.refresh_edit_prediction(false, true, window, cx);
15720 }
15721
15722 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15723 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15724 let snapshot = self.snapshot(window, cx);
15725 let selection = self.selections.newest::<Point>(cx);
15726 self.go_to_hunk_before_or_after_position(
15727 &snapshot,
15728 selection.head(),
15729 Direction::Next,
15730 window,
15731 cx,
15732 );
15733 }
15734
15735 pub fn go_to_hunk_before_or_after_position(
15736 &mut self,
15737 snapshot: &EditorSnapshot,
15738 position: Point,
15739 direction: Direction,
15740 window: &mut Window,
15741 cx: &mut Context<Editor>,
15742 ) {
15743 let row = if direction == Direction::Next {
15744 self.hunk_after_position(snapshot, position)
15745 .map(|hunk| hunk.row_range.start)
15746 } else {
15747 self.hunk_before_position(snapshot, position)
15748 };
15749
15750 if let Some(row) = row {
15751 let destination = Point::new(row.0, 0);
15752 let autoscroll = Autoscroll::center();
15753
15754 self.unfold_ranges(&[destination..destination], false, false, cx);
15755 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15756 s.select_ranges([destination..destination]);
15757 });
15758 }
15759 }
15760
15761 fn hunk_after_position(
15762 &mut self,
15763 snapshot: &EditorSnapshot,
15764 position: Point,
15765 ) -> Option<MultiBufferDiffHunk> {
15766 snapshot
15767 .buffer_snapshot
15768 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15769 .find(|hunk| hunk.row_range.start.0 > position.row)
15770 .or_else(|| {
15771 snapshot
15772 .buffer_snapshot
15773 .diff_hunks_in_range(Point::zero()..position)
15774 .find(|hunk| hunk.row_range.end.0 < position.row)
15775 })
15776 }
15777
15778 fn go_to_prev_hunk(
15779 &mut self,
15780 _: &GoToPreviousHunk,
15781 window: &mut Window,
15782 cx: &mut Context<Self>,
15783 ) {
15784 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15785 let snapshot = self.snapshot(window, cx);
15786 let selection = self.selections.newest::<Point>(cx);
15787 self.go_to_hunk_before_or_after_position(
15788 &snapshot,
15789 selection.head(),
15790 Direction::Prev,
15791 window,
15792 cx,
15793 );
15794 }
15795
15796 fn hunk_before_position(
15797 &mut self,
15798 snapshot: &EditorSnapshot,
15799 position: Point,
15800 ) -> Option<MultiBufferRow> {
15801 snapshot
15802 .buffer_snapshot
15803 .diff_hunk_before(position)
15804 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15805 }
15806
15807 fn go_to_next_change(
15808 &mut self,
15809 _: &GoToNextChange,
15810 window: &mut Window,
15811 cx: &mut Context<Self>,
15812 ) {
15813 if let Some(selections) = self
15814 .change_list
15815 .next_change(1, Direction::Next)
15816 .map(|s| s.to_vec())
15817 {
15818 self.change_selections(Default::default(), window, cx, |s| {
15819 let map = s.display_map();
15820 s.select_display_ranges(selections.iter().map(|a| {
15821 let point = a.to_display_point(&map);
15822 point..point
15823 }))
15824 })
15825 }
15826 }
15827
15828 fn go_to_previous_change(
15829 &mut self,
15830 _: &GoToPreviousChange,
15831 window: &mut Window,
15832 cx: &mut Context<Self>,
15833 ) {
15834 if let Some(selections) = self
15835 .change_list
15836 .next_change(1, Direction::Prev)
15837 .map(|s| s.to_vec())
15838 {
15839 self.change_selections(Default::default(), window, cx, |s| {
15840 let map = s.display_map();
15841 s.select_display_ranges(selections.iter().map(|a| {
15842 let point = a.to_display_point(&map);
15843 point..point
15844 }))
15845 })
15846 }
15847 }
15848
15849 fn go_to_line<T: 'static>(
15850 &mut self,
15851 position: Anchor,
15852 highlight_color: Option<Hsla>,
15853 window: &mut Window,
15854 cx: &mut Context<Self>,
15855 ) {
15856 let snapshot = self.snapshot(window, cx).display_snapshot;
15857 let position = position.to_point(&snapshot.buffer_snapshot);
15858 let start = snapshot
15859 .buffer_snapshot
15860 .clip_point(Point::new(position.row, 0), Bias::Left);
15861 let end = start + Point::new(1, 0);
15862 let start = snapshot.buffer_snapshot.anchor_before(start);
15863 let end = snapshot.buffer_snapshot.anchor_before(end);
15864
15865 self.highlight_rows::<T>(
15866 start..end,
15867 highlight_color
15868 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15869 Default::default(),
15870 cx,
15871 );
15872
15873 if self.buffer.read(cx).is_singleton() {
15874 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15875 }
15876 }
15877
15878 pub fn go_to_definition(
15879 &mut self,
15880 _: &GoToDefinition,
15881 window: &mut Window,
15882 cx: &mut Context<Self>,
15883 ) -> Task<Result<Navigated>> {
15884 let definition =
15885 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15886 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15887 cx.spawn_in(window, async move |editor, cx| {
15888 if definition.await? == Navigated::Yes {
15889 return Ok(Navigated::Yes);
15890 }
15891 match fallback_strategy {
15892 GoToDefinitionFallback::None => Ok(Navigated::No),
15893 GoToDefinitionFallback::FindAllReferences => {
15894 match editor.update_in(cx, |editor, window, cx| {
15895 editor.find_all_references(&FindAllReferences, window, cx)
15896 })? {
15897 Some(references) => references.await,
15898 None => Ok(Navigated::No),
15899 }
15900 }
15901 }
15902 })
15903 }
15904
15905 pub fn go_to_declaration(
15906 &mut self,
15907 _: &GoToDeclaration,
15908 window: &mut Window,
15909 cx: &mut Context<Self>,
15910 ) -> Task<Result<Navigated>> {
15911 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15912 }
15913
15914 pub fn go_to_declaration_split(
15915 &mut self,
15916 _: &GoToDeclaration,
15917 window: &mut Window,
15918 cx: &mut Context<Self>,
15919 ) -> Task<Result<Navigated>> {
15920 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15921 }
15922
15923 pub fn go_to_implementation(
15924 &mut self,
15925 _: &GoToImplementation,
15926 window: &mut Window,
15927 cx: &mut Context<Self>,
15928 ) -> Task<Result<Navigated>> {
15929 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15930 }
15931
15932 pub fn go_to_implementation_split(
15933 &mut self,
15934 _: &GoToImplementationSplit,
15935 window: &mut Window,
15936 cx: &mut Context<Self>,
15937 ) -> Task<Result<Navigated>> {
15938 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15939 }
15940
15941 pub fn go_to_type_definition(
15942 &mut self,
15943 _: &GoToTypeDefinition,
15944 window: &mut Window,
15945 cx: &mut Context<Self>,
15946 ) -> Task<Result<Navigated>> {
15947 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15948 }
15949
15950 pub fn go_to_definition_split(
15951 &mut self,
15952 _: &GoToDefinitionSplit,
15953 window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) -> Task<Result<Navigated>> {
15956 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15957 }
15958
15959 pub fn go_to_type_definition_split(
15960 &mut self,
15961 _: &GoToTypeDefinitionSplit,
15962 window: &mut Window,
15963 cx: &mut Context<Self>,
15964 ) -> Task<Result<Navigated>> {
15965 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15966 }
15967
15968 fn go_to_definition_of_kind(
15969 &mut self,
15970 kind: GotoDefinitionKind,
15971 split: bool,
15972 window: &mut Window,
15973 cx: &mut Context<Self>,
15974 ) -> Task<Result<Navigated>> {
15975 let Some(provider) = self.semantics_provider.clone() else {
15976 return Task::ready(Ok(Navigated::No));
15977 };
15978 let head = self.selections.newest::<usize>(cx).head();
15979 let buffer = self.buffer.read(cx);
15980 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15981 return Task::ready(Ok(Navigated::No));
15982 };
15983 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15984 return Task::ready(Ok(Navigated::No));
15985 };
15986
15987 cx.spawn_in(window, async move |editor, cx| {
15988 let Some(definitions) = definitions.await? else {
15989 return Ok(Navigated::No);
15990 };
15991 let navigated = editor
15992 .update_in(cx, |editor, window, cx| {
15993 editor.navigate_to_hover_links(
15994 Some(kind),
15995 definitions
15996 .into_iter()
15997 .filter(|location| {
15998 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15999 })
16000 .map(HoverLink::Text)
16001 .collect::<Vec<_>>(),
16002 split,
16003 window,
16004 cx,
16005 )
16006 })?
16007 .await?;
16008 anyhow::Ok(navigated)
16009 })
16010 }
16011
16012 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16013 let selection = self.selections.newest_anchor();
16014 let head = selection.head();
16015 let tail = selection.tail();
16016
16017 let Some((buffer, start_position)) =
16018 self.buffer.read(cx).text_anchor_for_position(head, cx)
16019 else {
16020 return;
16021 };
16022
16023 let end_position = if head != tail {
16024 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16025 return;
16026 };
16027 Some(pos)
16028 } else {
16029 None
16030 };
16031
16032 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16033 let url = if let Some(end_pos) = end_position {
16034 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16035 } else {
16036 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16037 };
16038
16039 if let Some(url) = url {
16040 editor.update(cx, |_, cx| {
16041 cx.open_url(&url);
16042 })
16043 } else {
16044 Ok(())
16045 }
16046 });
16047
16048 url_finder.detach();
16049 }
16050
16051 pub fn open_selected_filename(
16052 &mut self,
16053 _: &OpenSelectedFilename,
16054 window: &mut Window,
16055 cx: &mut Context<Self>,
16056 ) {
16057 let Some(workspace) = self.workspace() else {
16058 return;
16059 };
16060
16061 let position = self.selections.newest_anchor().head();
16062
16063 let Some((buffer, buffer_position)) =
16064 self.buffer.read(cx).text_anchor_for_position(position, cx)
16065 else {
16066 return;
16067 };
16068
16069 let project = self.project.clone();
16070
16071 cx.spawn_in(window, async move |_, cx| {
16072 let result = find_file(&buffer, project, buffer_position, cx).await;
16073
16074 if let Some((_, path)) = result {
16075 workspace
16076 .update_in(cx, |workspace, window, cx| {
16077 workspace.open_resolved_path(path, window, cx)
16078 })?
16079 .await?;
16080 }
16081 anyhow::Ok(())
16082 })
16083 .detach();
16084 }
16085
16086 pub(crate) fn navigate_to_hover_links(
16087 &mut self,
16088 kind: Option<GotoDefinitionKind>,
16089 definitions: Vec<HoverLink>,
16090 split: bool,
16091 window: &mut Window,
16092 cx: &mut Context<Editor>,
16093 ) -> Task<Result<Navigated>> {
16094 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16095 let mut first_url_or_file = None;
16096 let definitions: Vec<_> = definitions
16097 .into_iter()
16098 .filter_map(|def| match def {
16099 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16100 HoverLink::InlayHint(lsp_location, server_id) => {
16101 let computation =
16102 self.compute_target_location(lsp_location, server_id, window, cx);
16103 Some(cx.background_spawn(computation))
16104 }
16105 HoverLink::Url(url) => {
16106 first_url_or_file = Some(Either::Left(url));
16107 None
16108 }
16109 HoverLink::File(path) => {
16110 first_url_or_file = Some(Either::Right(path));
16111 None
16112 }
16113 })
16114 .collect();
16115
16116 let workspace = self.workspace();
16117
16118 cx.spawn_in(window, async move |editor, acx| {
16119 let mut locations: Vec<Location> = future::join_all(definitions)
16120 .await
16121 .into_iter()
16122 .filter_map(|location| location.transpose())
16123 .collect::<Result<_>>()
16124 .context("location tasks")?;
16125
16126 if locations.len() > 1 {
16127 let Some(workspace) = workspace else {
16128 return Ok(Navigated::No);
16129 };
16130
16131 let tab_kind = match kind {
16132 Some(GotoDefinitionKind::Implementation) => "Implementations",
16133 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16134 Some(GotoDefinitionKind::Declaration) => "Declarations",
16135 Some(GotoDefinitionKind::Type) => "Types",
16136 };
16137 let title = editor
16138 .update_in(acx, |_, _, cx| {
16139 let target = locations
16140 .iter()
16141 .map(|location| {
16142 location
16143 .buffer
16144 .read(cx)
16145 .text_for_range(location.range.clone())
16146 .collect::<String>()
16147 })
16148 .filter(|text| !text.contains('\n'))
16149 .unique()
16150 .take(3)
16151 .join(", ");
16152 if target.is_empty() {
16153 tab_kind.to_owned()
16154 } else {
16155 format!("{tab_kind} for {target}")
16156 }
16157 })
16158 .context("buffer title")?;
16159
16160 let opened = workspace
16161 .update_in(acx, |workspace, window, cx| {
16162 Self::open_locations_in_multibuffer(
16163 workspace,
16164 locations,
16165 title,
16166 split,
16167 MultibufferSelectionMode::First,
16168 window,
16169 cx,
16170 )
16171 })
16172 .is_ok();
16173
16174 anyhow::Ok(Navigated::from_bool(opened))
16175 } else if locations.is_empty() {
16176 // If there is one definition, just open it directly
16177 match first_url_or_file {
16178 Some(Either::Left(url)) => {
16179 acx.update(|_, cx| cx.open_url(&url))?;
16180 Ok(Navigated::Yes)
16181 }
16182 Some(Either::Right(path)) => {
16183 let Some(workspace) = workspace else {
16184 return Ok(Navigated::No);
16185 };
16186
16187 workspace
16188 .update_in(acx, |workspace, window, cx| {
16189 workspace.open_resolved_path(path, window, cx)
16190 })?
16191 .await?;
16192 Ok(Navigated::Yes)
16193 }
16194 None => Ok(Navigated::No),
16195 }
16196 } else {
16197 let Some(workspace) = workspace else {
16198 return Ok(Navigated::No);
16199 };
16200
16201 let target = locations.pop().unwrap();
16202 editor.update_in(acx, |editor, window, cx| {
16203 let pane = workspace.read(cx).active_pane().clone();
16204
16205 let range = target.range.to_point(target.buffer.read(cx));
16206 let range = editor.range_for_match(&range);
16207 let range = collapse_multiline_range(range);
16208
16209 if !split
16210 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16211 {
16212 editor.go_to_singleton_buffer_range(range, window, cx);
16213 } else {
16214 window.defer(cx, move |window, cx| {
16215 let target_editor: Entity<Self> =
16216 workspace.update(cx, |workspace, cx| {
16217 let pane = if split {
16218 workspace.adjacent_pane(window, cx)
16219 } else {
16220 workspace.active_pane().clone()
16221 };
16222
16223 workspace.open_project_item(
16224 pane,
16225 target.buffer.clone(),
16226 true,
16227 true,
16228 window,
16229 cx,
16230 )
16231 });
16232 target_editor.update(cx, |target_editor, cx| {
16233 // When selecting a definition in a different buffer, disable the nav history
16234 // to avoid creating a history entry at the previous cursor location.
16235 pane.update(cx, |pane, _| pane.disable_history());
16236 target_editor.go_to_singleton_buffer_range(range, window, cx);
16237 pane.update(cx, |pane, _| pane.enable_history());
16238 });
16239 });
16240 }
16241 Navigated::Yes
16242 })
16243 }
16244 })
16245 }
16246
16247 fn compute_target_location(
16248 &self,
16249 lsp_location: lsp::Location,
16250 server_id: LanguageServerId,
16251 window: &mut Window,
16252 cx: &mut Context<Self>,
16253 ) -> Task<anyhow::Result<Option<Location>>> {
16254 let Some(project) = self.project.clone() else {
16255 return Task::ready(Ok(None));
16256 };
16257
16258 cx.spawn_in(window, async move |editor, cx| {
16259 let location_task = editor.update(cx, |_, cx| {
16260 project.update(cx, |project, cx| {
16261 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16262 })
16263 })?;
16264 let location = Some({
16265 let target_buffer_handle = location_task.await.context("open local buffer")?;
16266 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16267 let target_start = target_buffer
16268 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16269 let target_end = target_buffer
16270 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16271 target_buffer.anchor_after(target_start)
16272 ..target_buffer.anchor_before(target_end)
16273 })?;
16274 Location {
16275 buffer: target_buffer_handle,
16276 range,
16277 }
16278 });
16279 Ok(location)
16280 })
16281 }
16282
16283 pub fn find_all_references(
16284 &mut self,
16285 _: &FindAllReferences,
16286 window: &mut Window,
16287 cx: &mut Context<Self>,
16288 ) -> Option<Task<Result<Navigated>>> {
16289 let selection = self.selections.newest::<usize>(cx);
16290 let multi_buffer = self.buffer.read(cx);
16291 let head = selection.head();
16292
16293 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16294 let head_anchor = multi_buffer_snapshot.anchor_at(
16295 head,
16296 if head < selection.tail() {
16297 Bias::Right
16298 } else {
16299 Bias::Left
16300 },
16301 );
16302
16303 match self
16304 .find_all_references_task_sources
16305 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16306 {
16307 Ok(_) => {
16308 log::info!(
16309 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16310 );
16311 return None;
16312 }
16313 Err(i) => {
16314 self.find_all_references_task_sources.insert(i, head_anchor);
16315 }
16316 }
16317
16318 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16319 let workspace = self.workspace()?;
16320 let project = workspace.read(cx).project().clone();
16321 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16322 Some(cx.spawn_in(window, async move |editor, cx| {
16323 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16324 if let Ok(i) = editor
16325 .find_all_references_task_sources
16326 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16327 {
16328 editor.find_all_references_task_sources.remove(i);
16329 }
16330 });
16331
16332 let Some(locations) = references.await? else {
16333 return anyhow::Ok(Navigated::No);
16334 };
16335 if locations.is_empty() {
16336 return anyhow::Ok(Navigated::No);
16337 }
16338
16339 workspace.update_in(cx, |workspace, window, cx| {
16340 let target = locations
16341 .iter()
16342 .map(|location| {
16343 location
16344 .buffer
16345 .read(cx)
16346 .text_for_range(location.range.clone())
16347 .collect::<String>()
16348 })
16349 .filter(|text| !text.contains('\n'))
16350 .unique()
16351 .take(3)
16352 .join(", ");
16353 let title = if target.is_empty() {
16354 "References".to_owned()
16355 } else {
16356 format!("References to {target}")
16357 };
16358 Self::open_locations_in_multibuffer(
16359 workspace,
16360 locations,
16361 title,
16362 false,
16363 MultibufferSelectionMode::First,
16364 window,
16365 cx,
16366 );
16367 Navigated::Yes
16368 })
16369 }))
16370 }
16371
16372 /// Opens a multibuffer with the given project locations in it
16373 pub fn open_locations_in_multibuffer(
16374 workspace: &mut Workspace,
16375 mut locations: Vec<Location>,
16376 title: String,
16377 split: bool,
16378 multibuffer_selection_mode: MultibufferSelectionMode,
16379 window: &mut Window,
16380 cx: &mut Context<Workspace>,
16381 ) {
16382 if locations.is_empty() {
16383 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16384 return;
16385 }
16386
16387 // If there are multiple definitions, open them in a multibuffer
16388 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16389 let mut locations = locations.into_iter().peekable();
16390 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16391 let capability = workspace.project().read(cx).capability();
16392
16393 let excerpt_buffer = cx.new(|cx| {
16394 let mut multibuffer = MultiBuffer::new(capability);
16395 while let Some(location) = locations.next() {
16396 let buffer = location.buffer.read(cx);
16397 let mut ranges_for_buffer = Vec::new();
16398 let range = location.range.to_point(buffer);
16399 ranges_for_buffer.push(range.clone());
16400
16401 while let Some(next_location) = locations.peek() {
16402 if next_location.buffer == location.buffer {
16403 ranges_for_buffer.push(next_location.range.to_point(buffer));
16404 locations.next();
16405 } else {
16406 break;
16407 }
16408 }
16409
16410 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16411 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16412 PathKey::for_buffer(&location.buffer, cx),
16413 location.buffer.clone(),
16414 ranges_for_buffer,
16415 multibuffer_context_lines(cx),
16416 cx,
16417 );
16418 ranges.extend(new_ranges)
16419 }
16420
16421 multibuffer.with_title(title)
16422 });
16423
16424 let editor = cx.new(|cx| {
16425 Editor::for_multibuffer(
16426 excerpt_buffer,
16427 Some(workspace.project().clone()),
16428 window,
16429 cx,
16430 )
16431 });
16432 editor.update(cx, |editor, cx| {
16433 match multibuffer_selection_mode {
16434 MultibufferSelectionMode::First => {
16435 if let Some(first_range) = ranges.first() {
16436 editor.change_selections(
16437 SelectionEffects::no_scroll(),
16438 window,
16439 cx,
16440 |selections| {
16441 selections.clear_disjoint();
16442 selections
16443 .select_anchor_ranges(std::iter::once(first_range.clone()));
16444 },
16445 );
16446 }
16447 editor.highlight_background::<Self>(
16448 &ranges,
16449 |theme| theme.colors().editor_highlighted_line_background,
16450 cx,
16451 );
16452 }
16453 MultibufferSelectionMode::All => {
16454 editor.change_selections(
16455 SelectionEffects::no_scroll(),
16456 window,
16457 cx,
16458 |selections| {
16459 selections.clear_disjoint();
16460 selections.select_anchor_ranges(ranges);
16461 },
16462 );
16463 }
16464 }
16465 editor.register_buffers_with_language_servers(cx);
16466 });
16467
16468 let item = Box::new(editor);
16469 let item_id = item.item_id();
16470
16471 if split {
16472 workspace.split_item(SplitDirection::Right, item, window, cx);
16473 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16474 let (preview_item_id, preview_item_idx) =
16475 workspace.active_pane().read_with(cx, |pane, _| {
16476 (pane.preview_item_id(), pane.preview_item_idx())
16477 });
16478
16479 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16480
16481 if let Some(preview_item_id) = preview_item_id {
16482 workspace.active_pane().update(cx, |pane, cx| {
16483 pane.remove_item(preview_item_id, false, false, window, cx);
16484 });
16485 }
16486 } else {
16487 workspace.add_item_to_active_pane(item, None, true, window, cx);
16488 }
16489 workspace.active_pane().update(cx, |pane, cx| {
16490 pane.set_preview_item_id(Some(item_id), cx);
16491 });
16492 }
16493
16494 pub fn rename(
16495 &mut self,
16496 _: &Rename,
16497 window: &mut Window,
16498 cx: &mut Context<Self>,
16499 ) -> Option<Task<Result<()>>> {
16500 use language::ToOffset as _;
16501
16502 let provider = self.semantics_provider.clone()?;
16503 let selection = self.selections.newest_anchor().clone();
16504 let (cursor_buffer, cursor_buffer_position) = self
16505 .buffer
16506 .read(cx)
16507 .text_anchor_for_position(selection.head(), cx)?;
16508 let (tail_buffer, cursor_buffer_position_end) = self
16509 .buffer
16510 .read(cx)
16511 .text_anchor_for_position(selection.tail(), cx)?;
16512 if tail_buffer != cursor_buffer {
16513 return None;
16514 }
16515
16516 let snapshot = cursor_buffer.read(cx).snapshot();
16517 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16518 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16519 let prepare_rename = provider
16520 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16521 .unwrap_or_else(|| Task::ready(Ok(None)));
16522 drop(snapshot);
16523
16524 Some(cx.spawn_in(window, async move |this, cx| {
16525 let rename_range = if let Some(range) = prepare_rename.await? {
16526 Some(range)
16527 } else {
16528 this.update(cx, |this, cx| {
16529 let buffer = this.buffer.read(cx).snapshot(cx);
16530 let mut buffer_highlights = this
16531 .document_highlights_for_position(selection.head(), &buffer)
16532 .filter(|highlight| {
16533 highlight.start.excerpt_id == selection.head().excerpt_id
16534 && highlight.end.excerpt_id == selection.head().excerpt_id
16535 });
16536 buffer_highlights
16537 .next()
16538 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16539 })?
16540 };
16541 if let Some(rename_range) = rename_range {
16542 this.update_in(cx, |this, window, cx| {
16543 let snapshot = cursor_buffer.read(cx).snapshot();
16544 let rename_buffer_range = rename_range.to_offset(&snapshot);
16545 let cursor_offset_in_rename_range =
16546 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16547 let cursor_offset_in_rename_range_end =
16548 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16549
16550 this.take_rename(false, window, cx);
16551 let buffer = this.buffer.read(cx).read(cx);
16552 let cursor_offset = selection.head().to_offset(&buffer);
16553 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16554 let rename_end = rename_start + rename_buffer_range.len();
16555 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16556 let mut old_highlight_id = None;
16557 let old_name: Arc<str> = buffer
16558 .chunks(rename_start..rename_end, true)
16559 .map(|chunk| {
16560 if old_highlight_id.is_none() {
16561 old_highlight_id = chunk.syntax_highlight_id;
16562 }
16563 chunk.text
16564 })
16565 .collect::<String>()
16566 .into();
16567
16568 drop(buffer);
16569
16570 // Position the selection in the rename editor so that it matches the current selection.
16571 this.show_local_selections = false;
16572 let rename_editor = cx.new(|cx| {
16573 let mut editor = Editor::single_line(window, cx);
16574 editor.buffer.update(cx, |buffer, cx| {
16575 buffer.edit([(0..0, old_name.clone())], None, cx)
16576 });
16577 let rename_selection_range = match cursor_offset_in_rename_range
16578 .cmp(&cursor_offset_in_rename_range_end)
16579 {
16580 Ordering::Equal => {
16581 editor.select_all(&SelectAll, window, cx);
16582 return editor;
16583 }
16584 Ordering::Less => {
16585 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16586 }
16587 Ordering::Greater => {
16588 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16589 }
16590 };
16591 if rename_selection_range.end > old_name.len() {
16592 editor.select_all(&SelectAll, window, cx);
16593 } else {
16594 editor.change_selections(Default::default(), window, cx, |s| {
16595 s.select_ranges([rename_selection_range]);
16596 });
16597 }
16598 editor
16599 });
16600 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16601 if e == &EditorEvent::Focused {
16602 cx.emit(EditorEvent::FocusedIn)
16603 }
16604 })
16605 .detach();
16606
16607 let write_highlights =
16608 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16609 let read_highlights =
16610 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16611 let ranges = write_highlights
16612 .iter()
16613 .flat_map(|(_, ranges)| ranges.iter())
16614 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16615 .cloned()
16616 .collect();
16617
16618 this.highlight_text::<Rename>(
16619 ranges,
16620 HighlightStyle {
16621 fade_out: Some(0.6),
16622 ..Default::default()
16623 },
16624 cx,
16625 );
16626 let rename_focus_handle = rename_editor.focus_handle(cx);
16627 window.focus(&rename_focus_handle);
16628 let block_id = this.insert_blocks(
16629 [BlockProperties {
16630 style: BlockStyle::Flex,
16631 placement: BlockPlacement::Below(range.start),
16632 height: Some(1),
16633 render: Arc::new({
16634 let rename_editor = rename_editor.clone();
16635 move |cx: &mut BlockContext| {
16636 let mut text_style = cx.editor_style.text.clone();
16637 if let Some(highlight_style) = old_highlight_id
16638 .and_then(|h| h.style(&cx.editor_style.syntax))
16639 {
16640 text_style = text_style.highlight(highlight_style);
16641 }
16642 div()
16643 .block_mouse_except_scroll()
16644 .pl(cx.anchor_x)
16645 .child(EditorElement::new(
16646 &rename_editor,
16647 EditorStyle {
16648 background: cx.theme().system().transparent,
16649 local_player: cx.editor_style.local_player,
16650 text: text_style,
16651 scrollbar_width: cx.editor_style.scrollbar_width,
16652 syntax: cx.editor_style.syntax.clone(),
16653 status: cx.editor_style.status.clone(),
16654 inlay_hints_style: HighlightStyle {
16655 font_weight: Some(FontWeight::BOLD),
16656 ..make_inlay_hints_style(cx.app)
16657 },
16658 edit_prediction_styles: make_suggestion_styles(
16659 cx.app,
16660 ),
16661 ..EditorStyle::default()
16662 },
16663 ))
16664 .into_any_element()
16665 }
16666 }),
16667 priority: 0,
16668 }],
16669 Some(Autoscroll::fit()),
16670 cx,
16671 )[0];
16672 this.pending_rename = Some(RenameState {
16673 range,
16674 old_name,
16675 editor: rename_editor,
16676 block_id,
16677 });
16678 })?;
16679 }
16680
16681 Ok(())
16682 }))
16683 }
16684
16685 pub fn confirm_rename(
16686 &mut self,
16687 _: &ConfirmRename,
16688 window: &mut Window,
16689 cx: &mut Context<Self>,
16690 ) -> Option<Task<Result<()>>> {
16691 let rename = self.take_rename(false, window, cx)?;
16692 let workspace = self.workspace()?.downgrade();
16693 let (buffer, start) = self
16694 .buffer
16695 .read(cx)
16696 .text_anchor_for_position(rename.range.start, cx)?;
16697 let (end_buffer, _) = self
16698 .buffer
16699 .read(cx)
16700 .text_anchor_for_position(rename.range.end, cx)?;
16701 if buffer != end_buffer {
16702 return None;
16703 }
16704
16705 let old_name = rename.old_name;
16706 let new_name = rename.editor.read(cx).text(cx);
16707
16708 let rename = self.semantics_provider.as_ref()?.perform_rename(
16709 &buffer,
16710 start,
16711 new_name.clone(),
16712 cx,
16713 )?;
16714
16715 Some(cx.spawn_in(window, async move |editor, cx| {
16716 let project_transaction = rename.await?;
16717 Self::open_project_transaction(
16718 &editor,
16719 workspace,
16720 project_transaction,
16721 format!("Rename: {} → {}", old_name, new_name),
16722 cx,
16723 )
16724 .await?;
16725
16726 editor.update(cx, |editor, cx| {
16727 editor.refresh_document_highlights(cx);
16728 })?;
16729 Ok(())
16730 }))
16731 }
16732
16733 fn take_rename(
16734 &mut self,
16735 moving_cursor: bool,
16736 window: &mut Window,
16737 cx: &mut Context<Self>,
16738 ) -> Option<RenameState> {
16739 let rename = self.pending_rename.take()?;
16740 if rename.editor.focus_handle(cx).is_focused(window) {
16741 window.focus(&self.focus_handle);
16742 }
16743
16744 self.remove_blocks(
16745 [rename.block_id].into_iter().collect(),
16746 Some(Autoscroll::fit()),
16747 cx,
16748 );
16749 self.clear_highlights::<Rename>(cx);
16750 self.show_local_selections = true;
16751
16752 if moving_cursor {
16753 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16754 editor.selections.newest::<usize>(cx).head()
16755 });
16756
16757 // Update the selection to match the position of the selection inside
16758 // the rename editor.
16759 let snapshot = self.buffer.read(cx).read(cx);
16760 let rename_range = rename.range.to_offset(&snapshot);
16761 let cursor_in_editor = snapshot
16762 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16763 .min(rename_range.end);
16764 drop(snapshot);
16765
16766 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16767 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16768 });
16769 } else {
16770 self.refresh_document_highlights(cx);
16771 }
16772
16773 Some(rename)
16774 }
16775
16776 pub fn pending_rename(&self) -> Option<&RenameState> {
16777 self.pending_rename.as_ref()
16778 }
16779
16780 fn format(
16781 &mut self,
16782 _: &Format,
16783 window: &mut Window,
16784 cx: &mut Context<Self>,
16785 ) -> Option<Task<Result<()>>> {
16786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16787
16788 let project = match &self.project {
16789 Some(project) => project.clone(),
16790 None => return None,
16791 };
16792
16793 Some(self.perform_format(
16794 project,
16795 FormatTrigger::Manual,
16796 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16797 window,
16798 cx,
16799 ))
16800 }
16801
16802 fn format_selections(
16803 &mut self,
16804 _: &FormatSelections,
16805 window: &mut Window,
16806 cx: &mut Context<Self>,
16807 ) -> Option<Task<Result<()>>> {
16808 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16809
16810 let project = match &self.project {
16811 Some(project) => project.clone(),
16812 None => return None,
16813 };
16814
16815 let ranges = self
16816 .selections
16817 .all_adjusted(cx)
16818 .into_iter()
16819 .map(|selection| selection.range())
16820 .collect_vec();
16821
16822 Some(self.perform_format(
16823 project,
16824 FormatTrigger::Manual,
16825 FormatTarget::Ranges(ranges),
16826 window,
16827 cx,
16828 ))
16829 }
16830
16831 fn perform_format(
16832 &mut self,
16833 project: Entity<Project>,
16834 trigger: FormatTrigger,
16835 target: FormatTarget,
16836 window: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) -> Task<Result<()>> {
16839 let buffer = self.buffer.clone();
16840 let (buffers, target) = match target {
16841 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16842 FormatTarget::Ranges(selection_ranges) => {
16843 let multi_buffer = buffer.read(cx);
16844 let snapshot = multi_buffer.read(cx);
16845 let mut buffers = HashSet::default();
16846 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16847 BTreeMap::new();
16848 for selection_range in selection_ranges {
16849 for (buffer, buffer_range, _) in
16850 snapshot.range_to_buffer_ranges(selection_range)
16851 {
16852 let buffer_id = buffer.remote_id();
16853 let start = buffer.anchor_before(buffer_range.start);
16854 let end = buffer.anchor_after(buffer_range.end);
16855 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16856 buffer_id_to_ranges
16857 .entry(buffer_id)
16858 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16859 .or_insert_with(|| vec![start..end]);
16860 }
16861 }
16862 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16863 }
16864 };
16865
16866 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16867 let selections_prev = transaction_id_prev
16868 .and_then(|transaction_id_prev| {
16869 // default to selections as they were after the last edit, if we have them,
16870 // instead of how they are now.
16871 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16872 // will take you back to where you made the last edit, instead of staying where you scrolled
16873 self.selection_history
16874 .transaction(transaction_id_prev)
16875 .map(|t| t.0.clone())
16876 })
16877 .unwrap_or_else(|| self.selections.disjoint_anchors());
16878
16879 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16880 let format = project.update(cx, |project, cx| {
16881 project.format(buffers, target, true, trigger, cx)
16882 });
16883
16884 cx.spawn_in(window, async move |editor, cx| {
16885 let transaction = futures::select_biased! {
16886 transaction = format.log_err().fuse() => transaction,
16887 () = timeout => {
16888 log::warn!("timed out waiting for formatting");
16889 None
16890 }
16891 };
16892
16893 buffer
16894 .update(cx, |buffer, cx| {
16895 if let Some(transaction) = transaction
16896 && !buffer.is_singleton()
16897 {
16898 buffer.push_transaction(&transaction.0, cx);
16899 }
16900 cx.notify();
16901 })
16902 .ok();
16903
16904 if let Some(transaction_id_now) =
16905 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16906 {
16907 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16908 if has_new_transaction {
16909 _ = editor.update(cx, |editor, _| {
16910 editor
16911 .selection_history
16912 .insert_transaction(transaction_id_now, selections_prev);
16913 });
16914 }
16915 }
16916
16917 Ok(())
16918 })
16919 }
16920
16921 fn organize_imports(
16922 &mut self,
16923 _: &OrganizeImports,
16924 window: &mut Window,
16925 cx: &mut Context<Self>,
16926 ) -> Option<Task<Result<()>>> {
16927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16928 let project = match &self.project {
16929 Some(project) => project.clone(),
16930 None => return None,
16931 };
16932 Some(self.perform_code_action_kind(
16933 project,
16934 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16935 window,
16936 cx,
16937 ))
16938 }
16939
16940 fn perform_code_action_kind(
16941 &mut self,
16942 project: Entity<Project>,
16943 kind: CodeActionKind,
16944 window: &mut Window,
16945 cx: &mut Context<Self>,
16946 ) -> Task<Result<()>> {
16947 let buffer = self.buffer.clone();
16948 let buffers = buffer.read(cx).all_buffers();
16949 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16950 let apply_action = project.update(cx, |project, cx| {
16951 project.apply_code_action_kind(buffers, kind, true, cx)
16952 });
16953 cx.spawn_in(window, async move |_, cx| {
16954 let transaction = futures::select_biased! {
16955 () = timeout => {
16956 log::warn!("timed out waiting for executing code action");
16957 None
16958 }
16959 transaction = apply_action.log_err().fuse() => transaction,
16960 };
16961 buffer
16962 .update(cx, |buffer, cx| {
16963 // check if we need this
16964 if let Some(transaction) = transaction
16965 && !buffer.is_singleton()
16966 {
16967 buffer.push_transaction(&transaction.0, cx);
16968 }
16969 cx.notify();
16970 })
16971 .ok();
16972 Ok(())
16973 })
16974 }
16975
16976 pub fn restart_language_server(
16977 &mut self,
16978 _: &RestartLanguageServer,
16979 _: &mut Window,
16980 cx: &mut Context<Self>,
16981 ) {
16982 if let Some(project) = self.project.clone() {
16983 self.buffer.update(cx, |multi_buffer, cx| {
16984 project.update(cx, |project, cx| {
16985 project.restart_language_servers_for_buffers(
16986 multi_buffer.all_buffers().into_iter().collect(),
16987 HashSet::default(),
16988 cx,
16989 );
16990 });
16991 })
16992 }
16993 }
16994
16995 pub fn stop_language_server(
16996 &mut self,
16997 _: &StopLanguageServer,
16998 _: &mut Window,
16999 cx: &mut Context<Self>,
17000 ) {
17001 if let Some(project) = self.project.clone() {
17002 self.buffer.update(cx, |multi_buffer, cx| {
17003 project.update(cx, |project, cx| {
17004 project.stop_language_servers_for_buffers(
17005 multi_buffer.all_buffers().into_iter().collect(),
17006 HashSet::default(),
17007 cx,
17008 );
17009 cx.emit(project::Event::RefreshInlayHints);
17010 });
17011 });
17012 }
17013 }
17014
17015 fn cancel_language_server_work(
17016 workspace: &mut Workspace,
17017 _: &actions::CancelLanguageServerWork,
17018 _: &mut Window,
17019 cx: &mut Context<Workspace>,
17020 ) {
17021 let project = workspace.project();
17022 let buffers = workspace
17023 .active_item(cx)
17024 .and_then(|item| item.act_as::<Editor>(cx))
17025 .map_or(HashSet::default(), |editor| {
17026 editor.read(cx).buffer.read(cx).all_buffers()
17027 });
17028 project.update(cx, |project, cx| {
17029 project.cancel_language_server_work_for_buffers(buffers, cx);
17030 });
17031 }
17032
17033 fn show_character_palette(
17034 &mut self,
17035 _: &ShowCharacterPalette,
17036 window: &mut Window,
17037 _: &mut Context<Self>,
17038 ) {
17039 window.show_character_palette();
17040 }
17041
17042 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17043 if !self.diagnostics_enabled() {
17044 return;
17045 }
17046
17047 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17048 let buffer = self.buffer.read(cx).snapshot(cx);
17049 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17050 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17051 let is_valid = buffer
17052 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17053 .any(|entry| {
17054 entry.diagnostic.is_primary
17055 && !entry.range.is_empty()
17056 && entry.range.start == primary_range_start
17057 && entry.diagnostic.message == active_diagnostics.active_message
17058 });
17059
17060 if !is_valid {
17061 self.dismiss_diagnostics(cx);
17062 }
17063 }
17064 }
17065
17066 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17067 match &self.active_diagnostics {
17068 ActiveDiagnostic::Group(group) => Some(group),
17069 _ => None,
17070 }
17071 }
17072
17073 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17074 if !self.diagnostics_enabled() {
17075 return;
17076 }
17077 self.dismiss_diagnostics(cx);
17078 self.active_diagnostics = ActiveDiagnostic::All;
17079 }
17080
17081 fn activate_diagnostics(
17082 &mut self,
17083 buffer_id: BufferId,
17084 diagnostic: DiagnosticEntry<usize>,
17085 window: &mut Window,
17086 cx: &mut Context<Self>,
17087 ) {
17088 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17089 return;
17090 }
17091 self.dismiss_diagnostics(cx);
17092 let snapshot = self.snapshot(window, cx);
17093 let buffer = self.buffer.read(cx).snapshot(cx);
17094 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17095 return;
17096 };
17097
17098 let diagnostic_group = buffer
17099 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17100 .collect::<Vec<_>>();
17101
17102 let blocks =
17103 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17104
17105 let blocks = self.display_map.update(cx, |display_map, cx| {
17106 display_map.insert_blocks(blocks, cx).into_iter().collect()
17107 });
17108 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17109 active_range: buffer.anchor_before(diagnostic.range.start)
17110 ..buffer.anchor_after(diagnostic.range.end),
17111 active_message: diagnostic.diagnostic.message.clone(),
17112 group_id: diagnostic.diagnostic.group_id,
17113 blocks,
17114 });
17115 cx.notify();
17116 }
17117
17118 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17119 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17120 return;
17121 };
17122
17123 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17124 if let ActiveDiagnostic::Group(group) = prev {
17125 self.display_map.update(cx, |display_map, cx| {
17126 display_map.remove_blocks(group.blocks, cx);
17127 });
17128 cx.notify();
17129 }
17130 }
17131
17132 /// Disable inline diagnostics rendering for this editor.
17133 pub fn disable_inline_diagnostics(&mut self) {
17134 self.inline_diagnostics_enabled = false;
17135 self.inline_diagnostics_update = Task::ready(());
17136 self.inline_diagnostics.clear();
17137 }
17138
17139 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17140 self.diagnostics_enabled = false;
17141 self.dismiss_diagnostics(cx);
17142 self.inline_diagnostics_update = Task::ready(());
17143 self.inline_diagnostics.clear();
17144 }
17145
17146 pub fn disable_word_completions(&mut self) {
17147 self.word_completions_enabled = false;
17148 }
17149
17150 pub fn diagnostics_enabled(&self) -> bool {
17151 self.diagnostics_enabled && self.mode.is_full()
17152 }
17153
17154 pub fn inline_diagnostics_enabled(&self) -> bool {
17155 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17156 }
17157
17158 pub fn show_inline_diagnostics(&self) -> bool {
17159 self.show_inline_diagnostics
17160 }
17161
17162 pub fn toggle_inline_diagnostics(
17163 &mut self,
17164 _: &ToggleInlineDiagnostics,
17165 window: &mut Window,
17166 cx: &mut Context<Editor>,
17167 ) {
17168 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17169 self.refresh_inline_diagnostics(false, window, cx);
17170 }
17171
17172 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17173 self.diagnostics_max_severity = severity;
17174 self.display_map.update(cx, |display_map, _| {
17175 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17176 });
17177 }
17178
17179 pub fn toggle_diagnostics(
17180 &mut self,
17181 _: &ToggleDiagnostics,
17182 window: &mut Window,
17183 cx: &mut Context<Editor>,
17184 ) {
17185 if !self.diagnostics_enabled() {
17186 return;
17187 }
17188
17189 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17190 EditorSettings::get_global(cx)
17191 .diagnostics_max_severity
17192 .filter(|severity| severity != &DiagnosticSeverity::Off)
17193 .unwrap_or(DiagnosticSeverity::Hint)
17194 } else {
17195 DiagnosticSeverity::Off
17196 };
17197 self.set_max_diagnostics_severity(new_severity, cx);
17198 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17199 self.active_diagnostics = ActiveDiagnostic::None;
17200 self.inline_diagnostics_update = Task::ready(());
17201 self.inline_diagnostics.clear();
17202 } else {
17203 self.refresh_inline_diagnostics(false, window, cx);
17204 }
17205
17206 cx.notify();
17207 }
17208
17209 pub fn toggle_minimap(
17210 &mut self,
17211 _: &ToggleMinimap,
17212 window: &mut Window,
17213 cx: &mut Context<Editor>,
17214 ) {
17215 if self.supports_minimap(cx) {
17216 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17217 }
17218 }
17219
17220 fn refresh_inline_diagnostics(
17221 &mut self,
17222 debounce: bool,
17223 window: &mut Window,
17224 cx: &mut Context<Self>,
17225 ) {
17226 let max_severity = ProjectSettings::get_global(cx)
17227 .diagnostics
17228 .inline
17229 .max_severity
17230 .unwrap_or(self.diagnostics_max_severity);
17231
17232 if !self.inline_diagnostics_enabled()
17233 || !self.show_inline_diagnostics
17234 || max_severity == DiagnosticSeverity::Off
17235 {
17236 self.inline_diagnostics_update = Task::ready(());
17237 self.inline_diagnostics.clear();
17238 return;
17239 }
17240
17241 let debounce_ms = ProjectSettings::get_global(cx)
17242 .diagnostics
17243 .inline
17244 .update_debounce_ms;
17245 let debounce = if debounce && debounce_ms > 0 {
17246 Some(Duration::from_millis(debounce_ms))
17247 } else {
17248 None
17249 };
17250 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17251 if let Some(debounce) = debounce {
17252 cx.background_executor().timer(debounce).await;
17253 }
17254 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17255 editor
17256 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17257 .ok()
17258 }) else {
17259 return;
17260 };
17261
17262 let new_inline_diagnostics = cx
17263 .background_spawn(async move {
17264 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17265 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17266 let message = diagnostic_entry
17267 .diagnostic
17268 .message
17269 .split_once('\n')
17270 .map(|(line, _)| line)
17271 .map(SharedString::new)
17272 .unwrap_or_else(|| {
17273 SharedString::from(diagnostic_entry.diagnostic.message)
17274 });
17275 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17276 let (Ok(i) | Err(i)) = inline_diagnostics
17277 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17278 inline_diagnostics.insert(
17279 i,
17280 (
17281 start_anchor,
17282 InlineDiagnostic {
17283 message,
17284 group_id: diagnostic_entry.diagnostic.group_id,
17285 start: diagnostic_entry.range.start.to_point(&snapshot),
17286 is_primary: diagnostic_entry.diagnostic.is_primary,
17287 severity: diagnostic_entry.diagnostic.severity,
17288 },
17289 ),
17290 );
17291 }
17292 inline_diagnostics
17293 })
17294 .await;
17295
17296 editor
17297 .update(cx, |editor, cx| {
17298 editor.inline_diagnostics = new_inline_diagnostics;
17299 cx.notify();
17300 })
17301 .ok();
17302 });
17303 }
17304
17305 fn pull_diagnostics(
17306 &mut self,
17307 buffer_id: Option<BufferId>,
17308 window: &Window,
17309 cx: &mut Context<Self>,
17310 ) -> Option<()> {
17311 if !self.mode().is_full() {
17312 return None;
17313 }
17314 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17315 .diagnostics
17316 .lsp_pull_diagnostics;
17317 if !pull_diagnostics_settings.enabled {
17318 return None;
17319 }
17320 let project = self.project()?.downgrade();
17321 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17322 let mut buffers = self.buffer.read(cx).all_buffers();
17323 if let Some(buffer_id) = buffer_id {
17324 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17325 }
17326
17327 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17328 cx.background_executor().timer(debounce).await;
17329
17330 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17331 buffers
17332 .into_iter()
17333 .filter_map(|buffer| {
17334 project
17335 .update(cx, |project, cx| {
17336 project.lsp_store().update(cx, |lsp_store, cx| {
17337 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17338 })
17339 })
17340 .ok()
17341 })
17342 .collect::<FuturesUnordered<_>>()
17343 }) else {
17344 return;
17345 };
17346
17347 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17348 match pull_task {
17349 Ok(()) => {
17350 if editor
17351 .update_in(cx, |editor, window, cx| {
17352 editor.update_diagnostics_state(window, cx);
17353 })
17354 .is_err()
17355 {
17356 return;
17357 }
17358 }
17359 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17360 }
17361 }
17362 });
17363
17364 Some(())
17365 }
17366
17367 pub fn set_selections_from_remote(
17368 &mut self,
17369 selections: Vec<Selection<Anchor>>,
17370 pending_selection: Option<Selection<Anchor>>,
17371 window: &mut Window,
17372 cx: &mut Context<Self>,
17373 ) {
17374 let old_cursor_position = self.selections.newest_anchor().head();
17375 self.selections.change_with(cx, |s| {
17376 s.select_anchors(selections);
17377 if let Some(pending_selection) = pending_selection {
17378 s.set_pending(pending_selection, SelectMode::Character);
17379 } else {
17380 s.clear_pending();
17381 }
17382 });
17383 self.selections_did_change(
17384 false,
17385 &old_cursor_position,
17386 SelectionEffects::default(),
17387 window,
17388 cx,
17389 );
17390 }
17391
17392 pub fn transact(
17393 &mut self,
17394 window: &mut Window,
17395 cx: &mut Context<Self>,
17396 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17397 ) -> Option<TransactionId> {
17398 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17399 this.start_transaction_at(Instant::now(), window, cx);
17400 update(this, window, cx);
17401 this.end_transaction_at(Instant::now(), cx)
17402 })
17403 }
17404
17405 pub fn start_transaction_at(
17406 &mut self,
17407 now: Instant,
17408 window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) -> Option<TransactionId> {
17411 self.end_selection(window, cx);
17412 if let Some(tx_id) = self
17413 .buffer
17414 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17415 {
17416 self.selection_history
17417 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17418 cx.emit(EditorEvent::TransactionBegun {
17419 transaction_id: tx_id,
17420 });
17421 Some(tx_id)
17422 } else {
17423 None
17424 }
17425 }
17426
17427 pub fn end_transaction_at(
17428 &mut self,
17429 now: Instant,
17430 cx: &mut Context<Self>,
17431 ) -> Option<TransactionId> {
17432 if let Some(transaction_id) = self
17433 .buffer
17434 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17435 {
17436 if let Some((_, end_selections)) =
17437 self.selection_history.transaction_mut(transaction_id)
17438 {
17439 *end_selections = Some(self.selections.disjoint_anchors());
17440 } else {
17441 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17442 }
17443
17444 cx.emit(EditorEvent::Edited { transaction_id });
17445 Some(transaction_id)
17446 } else {
17447 None
17448 }
17449 }
17450
17451 pub fn modify_transaction_selection_history(
17452 &mut self,
17453 transaction_id: TransactionId,
17454 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17455 ) -> bool {
17456 self.selection_history
17457 .transaction_mut(transaction_id)
17458 .map(modify)
17459 .is_some()
17460 }
17461
17462 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17463 if self.selection_mark_mode {
17464 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17465 s.move_with(|_, sel| {
17466 sel.collapse_to(sel.head(), SelectionGoal::None);
17467 });
17468 })
17469 }
17470 self.selection_mark_mode = true;
17471 cx.notify();
17472 }
17473
17474 pub fn swap_selection_ends(
17475 &mut self,
17476 _: &actions::SwapSelectionEnds,
17477 window: &mut Window,
17478 cx: &mut Context<Self>,
17479 ) {
17480 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17481 s.move_with(|_, sel| {
17482 if sel.start != sel.end {
17483 sel.reversed = !sel.reversed
17484 }
17485 });
17486 });
17487 self.request_autoscroll(Autoscroll::newest(), cx);
17488 cx.notify();
17489 }
17490
17491 pub fn toggle_focus(
17492 workspace: &mut Workspace,
17493 _: &actions::ToggleFocus,
17494 window: &mut Window,
17495 cx: &mut Context<Workspace>,
17496 ) {
17497 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17498 return;
17499 };
17500 workspace.activate_item(&item, true, true, window, cx);
17501 }
17502
17503 pub fn toggle_fold(
17504 &mut self,
17505 _: &actions::ToggleFold,
17506 window: &mut Window,
17507 cx: &mut Context<Self>,
17508 ) {
17509 if self.is_singleton(cx) {
17510 let selection = self.selections.newest::<Point>(cx);
17511
17512 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17513 let range = if selection.is_empty() {
17514 let point = selection.head().to_display_point(&display_map);
17515 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17516 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17517 .to_point(&display_map);
17518 start..end
17519 } else {
17520 selection.range()
17521 };
17522 if display_map.folds_in_range(range).next().is_some() {
17523 self.unfold_lines(&Default::default(), window, cx)
17524 } else {
17525 self.fold(&Default::default(), window, cx)
17526 }
17527 } else {
17528 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17529 let buffer_ids: HashSet<_> = self
17530 .selections
17531 .disjoint_anchor_ranges()
17532 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17533 .collect();
17534
17535 let should_unfold = buffer_ids
17536 .iter()
17537 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17538
17539 for buffer_id in buffer_ids {
17540 if should_unfold {
17541 self.unfold_buffer(buffer_id, cx);
17542 } else {
17543 self.fold_buffer(buffer_id, cx);
17544 }
17545 }
17546 }
17547 }
17548
17549 pub fn toggle_fold_recursive(
17550 &mut self,
17551 _: &actions::ToggleFoldRecursive,
17552 window: &mut Window,
17553 cx: &mut Context<Self>,
17554 ) {
17555 let selection = self.selections.newest::<Point>(cx);
17556
17557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17558 let range = if selection.is_empty() {
17559 let point = selection.head().to_display_point(&display_map);
17560 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17561 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17562 .to_point(&display_map);
17563 start..end
17564 } else {
17565 selection.range()
17566 };
17567 if display_map.folds_in_range(range).next().is_some() {
17568 self.unfold_recursive(&Default::default(), window, cx)
17569 } else {
17570 self.fold_recursive(&Default::default(), window, cx)
17571 }
17572 }
17573
17574 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17575 if self.is_singleton(cx) {
17576 let mut to_fold = Vec::new();
17577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17578 let selections = self.selections.all_adjusted(cx);
17579
17580 for selection in selections {
17581 let range = selection.range().sorted();
17582 let buffer_start_row = range.start.row;
17583
17584 if range.start.row != range.end.row {
17585 let mut found = false;
17586 let mut row = range.start.row;
17587 while row <= range.end.row {
17588 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17589 {
17590 found = true;
17591 row = crease.range().end.row + 1;
17592 to_fold.push(crease);
17593 } else {
17594 row += 1
17595 }
17596 }
17597 if found {
17598 continue;
17599 }
17600 }
17601
17602 for row in (0..=range.start.row).rev() {
17603 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17604 && crease.range().end.row >= buffer_start_row
17605 {
17606 to_fold.push(crease);
17607 if row <= range.start.row {
17608 break;
17609 }
17610 }
17611 }
17612 }
17613
17614 self.fold_creases(to_fold, true, window, cx);
17615 } else {
17616 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17617 let buffer_ids = self
17618 .selections
17619 .disjoint_anchor_ranges()
17620 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17621 .collect::<HashSet<_>>();
17622 for buffer_id in buffer_ids {
17623 self.fold_buffer(buffer_id, cx);
17624 }
17625 }
17626 }
17627
17628 pub fn toggle_fold_all(
17629 &mut self,
17630 _: &actions::ToggleFoldAll,
17631 window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) {
17634 if self.buffer.read(cx).is_singleton() {
17635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17636 let has_folds = display_map
17637 .folds_in_range(0..display_map.buffer_snapshot.len())
17638 .next()
17639 .is_some();
17640
17641 if has_folds {
17642 self.unfold_all(&actions::UnfoldAll, window, cx);
17643 } else {
17644 self.fold_all(&actions::FoldAll, window, cx);
17645 }
17646 } else {
17647 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17648 let should_unfold = buffer_ids
17649 .iter()
17650 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17651
17652 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17653 editor
17654 .update_in(cx, |editor, _, cx| {
17655 for buffer_id in buffer_ids {
17656 if should_unfold {
17657 editor.unfold_buffer(buffer_id, cx);
17658 } else {
17659 editor.fold_buffer(buffer_id, cx);
17660 }
17661 }
17662 })
17663 .ok();
17664 });
17665 }
17666 }
17667
17668 fn fold_at_level(
17669 &mut self,
17670 fold_at: &FoldAtLevel,
17671 window: &mut Window,
17672 cx: &mut Context<Self>,
17673 ) {
17674 if !self.buffer.read(cx).is_singleton() {
17675 return;
17676 }
17677
17678 let fold_at_level = fold_at.0;
17679 let snapshot = self.buffer.read(cx).snapshot(cx);
17680 let mut to_fold = Vec::new();
17681 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17682
17683 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17684 while start_row < end_row {
17685 match self
17686 .snapshot(window, cx)
17687 .crease_for_buffer_row(MultiBufferRow(start_row))
17688 {
17689 Some(crease) => {
17690 let nested_start_row = crease.range().start.row + 1;
17691 let nested_end_row = crease.range().end.row;
17692
17693 if current_level < fold_at_level {
17694 stack.push((nested_start_row, nested_end_row, current_level + 1));
17695 } else if current_level == fold_at_level {
17696 to_fold.push(crease);
17697 }
17698
17699 start_row = nested_end_row + 1;
17700 }
17701 None => start_row += 1,
17702 }
17703 }
17704 }
17705
17706 self.fold_creases(to_fold, true, window, cx);
17707 }
17708
17709 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17710 if self.buffer.read(cx).is_singleton() {
17711 let mut fold_ranges = Vec::new();
17712 let snapshot = self.buffer.read(cx).snapshot(cx);
17713
17714 for row in 0..snapshot.max_row().0 {
17715 if let Some(foldable_range) = self
17716 .snapshot(window, cx)
17717 .crease_for_buffer_row(MultiBufferRow(row))
17718 {
17719 fold_ranges.push(foldable_range);
17720 }
17721 }
17722
17723 self.fold_creases(fold_ranges, true, window, cx);
17724 } else {
17725 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17726 editor
17727 .update_in(cx, |editor, _, cx| {
17728 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17729 editor.fold_buffer(buffer_id, cx);
17730 }
17731 })
17732 .ok();
17733 });
17734 }
17735 }
17736
17737 pub fn fold_function_bodies(
17738 &mut self,
17739 _: &actions::FoldFunctionBodies,
17740 window: &mut Window,
17741 cx: &mut Context<Self>,
17742 ) {
17743 let snapshot = self.buffer.read(cx).snapshot(cx);
17744
17745 let ranges = snapshot
17746 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17747 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17748 .collect::<Vec<_>>();
17749
17750 let creases = ranges
17751 .into_iter()
17752 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17753 .collect();
17754
17755 self.fold_creases(creases, true, window, cx);
17756 }
17757
17758 pub fn fold_recursive(
17759 &mut self,
17760 _: &actions::FoldRecursive,
17761 window: &mut Window,
17762 cx: &mut Context<Self>,
17763 ) {
17764 let mut to_fold = Vec::new();
17765 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17766 let selections = self.selections.all_adjusted(cx);
17767
17768 for selection in selections {
17769 let range = selection.range().sorted();
17770 let buffer_start_row = range.start.row;
17771
17772 if range.start.row != range.end.row {
17773 let mut found = false;
17774 for row in range.start.row..=range.end.row {
17775 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17776 found = true;
17777 to_fold.push(crease);
17778 }
17779 }
17780 if found {
17781 continue;
17782 }
17783 }
17784
17785 for row in (0..=range.start.row).rev() {
17786 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17787 if crease.range().end.row >= buffer_start_row {
17788 to_fold.push(crease);
17789 } else {
17790 break;
17791 }
17792 }
17793 }
17794 }
17795
17796 self.fold_creases(to_fold, true, window, cx);
17797 }
17798
17799 pub fn fold_at(
17800 &mut self,
17801 buffer_row: MultiBufferRow,
17802 window: &mut Window,
17803 cx: &mut Context<Self>,
17804 ) {
17805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17806
17807 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17808 let autoscroll = self
17809 .selections
17810 .all::<Point>(cx)
17811 .iter()
17812 .any(|selection| crease.range().overlaps(&selection.range()));
17813
17814 self.fold_creases(vec![crease], autoscroll, window, cx);
17815 }
17816 }
17817
17818 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17819 if self.is_singleton(cx) {
17820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17821 let buffer = &display_map.buffer_snapshot;
17822 let selections = self.selections.all::<Point>(cx);
17823 let ranges = selections
17824 .iter()
17825 .map(|s| {
17826 let range = s.display_range(&display_map).sorted();
17827 let mut start = range.start.to_point(&display_map);
17828 let mut end = range.end.to_point(&display_map);
17829 start.column = 0;
17830 end.column = buffer.line_len(MultiBufferRow(end.row));
17831 start..end
17832 })
17833 .collect::<Vec<_>>();
17834
17835 self.unfold_ranges(&ranges, true, true, cx);
17836 } else {
17837 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17838 let buffer_ids = self
17839 .selections
17840 .disjoint_anchor_ranges()
17841 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17842 .collect::<HashSet<_>>();
17843 for buffer_id in buffer_ids {
17844 self.unfold_buffer(buffer_id, cx);
17845 }
17846 }
17847 }
17848
17849 pub fn unfold_recursive(
17850 &mut self,
17851 _: &UnfoldRecursive,
17852 _window: &mut Window,
17853 cx: &mut Context<Self>,
17854 ) {
17855 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17856 let selections = self.selections.all::<Point>(cx);
17857 let ranges = selections
17858 .iter()
17859 .map(|s| {
17860 let mut range = s.display_range(&display_map).sorted();
17861 *range.start.column_mut() = 0;
17862 *range.end.column_mut() = display_map.line_len(range.end.row());
17863 let start = range.start.to_point(&display_map);
17864 let end = range.end.to_point(&display_map);
17865 start..end
17866 })
17867 .collect::<Vec<_>>();
17868
17869 self.unfold_ranges(&ranges, true, true, cx);
17870 }
17871
17872 pub fn unfold_at(
17873 &mut self,
17874 buffer_row: MultiBufferRow,
17875 _window: &mut Window,
17876 cx: &mut Context<Self>,
17877 ) {
17878 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17879
17880 let intersection_range = Point::new(buffer_row.0, 0)
17881 ..Point::new(
17882 buffer_row.0,
17883 display_map.buffer_snapshot.line_len(buffer_row),
17884 );
17885
17886 let autoscroll = self
17887 .selections
17888 .all::<Point>(cx)
17889 .iter()
17890 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17891
17892 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17893 }
17894
17895 pub fn unfold_all(
17896 &mut self,
17897 _: &actions::UnfoldAll,
17898 _window: &mut Window,
17899 cx: &mut Context<Self>,
17900 ) {
17901 if self.buffer.read(cx).is_singleton() {
17902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17903 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17904 } else {
17905 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17906 editor
17907 .update(cx, |editor, cx| {
17908 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17909 editor.unfold_buffer(buffer_id, cx);
17910 }
17911 })
17912 .ok();
17913 });
17914 }
17915 }
17916
17917 pub fn fold_selected_ranges(
17918 &mut self,
17919 _: &FoldSelectedRanges,
17920 window: &mut Window,
17921 cx: &mut Context<Self>,
17922 ) {
17923 let selections = self.selections.all_adjusted(cx);
17924 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17925 let ranges = selections
17926 .into_iter()
17927 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17928 .collect::<Vec<_>>();
17929 self.fold_creases(ranges, true, window, cx);
17930 }
17931
17932 pub fn fold_ranges<T: ToOffset + Clone>(
17933 &mut self,
17934 ranges: Vec<Range<T>>,
17935 auto_scroll: bool,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) {
17939 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17940 let ranges = ranges
17941 .into_iter()
17942 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17943 .collect::<Vec<_>>();
17944 self.fold_creases(ranges, auto_scroll, window, cx);
17945 }
17946
17947 pub fn fold_creases<T: ToOffset + Clone>(
17948 &mut self,
17949 creases: Vec<Crease<T>>,
17950 auto_scroll: bool,
17951 _window: &mut Window,
17952 cx: &mut Context<Self>,
17953 ) {
17954 if creases.is_empty() {
17955 return;
17956 }
17957
17958 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17959
17960 if auto_scroll {
17961 self.request_autoscroll(Autoscroll::fit(), cx);
17962 }
17963
17964 cx.notify();
17965
17966 self.scrollbar_marker_state.dirty = true;
17967 self.folds_did_change(cx);
17968 }
17969
17970 /// Removes any folds whose ranges intersect any of the given ranges.
17971 pub fn unfold_ranges<T: ToOffset + Clone>(
17972 &mut self,
17973 ranges: &[Range<T>],
17974 inclusive: bool,
17975 auto_scroll: bool,
17976 cx: &mut Context<Self>,
17977 ) {
17978 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17979 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17980 });
17981 self.folds_did_change(cx);
17982 }
17983
17984 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17985 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17986 return;
17987 }
17988 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17989 self.display_map.update(cx, |display_map, cx| {
17990 display_map.fold_buffers([buffer_id], cx)
17991 });
17992 cx.emit(EditorEvent::BufferFoldToggled {
17993 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17994 folded: true,
17995 });
17996 cx.notify();
17997 }
17998
17999 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18000 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18001 return;
18002 }
18003 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18004 self.display_map.update(cx, |display_map, cx| {
18005 display_map.unfold_buffers([buffer_id], cx);
18006 });
18007 cx.emit(EditorEvent::BufferFoldToggled {
18008 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18009 folded: false,
18010 });
18011 cx.notify();
18012 }
18013
18014 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18015 self.display_map.read(cx).is_buffer_folded(buffer)
18016 }
18017
18018 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18019 self.display_map.read(cx).folded_buffers()
18020 }
18021
18022 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18023 self.display_map.update(cx, |display_map, cx| {
18024 display_map.disable_header_for_buffer(buffer_id, cx);
18025 });
18026 cx.notify();
18027 }
18028
18029 /// Removes any folds with the given ranges.
18030 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18031 &mut self,
18032 ranges: &[Range<T>],
18033 type_id: TypeId,
18034 auto_scroll: bool,
18035 cx: &mut Context<Self>,
18036 ) {
18037 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18038 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18039 });
18040 self.folds_did_change(cx);
18041 }
18042
18043 fn remove_folds_with<T: ToOffset + Clone>(
18044 &mut self,
18045 ranges: &[Range<T>],
18046 auto_scroll: bool,
18047 cx: &mut Context<Self>,
18048 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18049 ) {
18050 if ranges.is_empty() {
18051 return;
18052 }
18053
18054 let mut buffers_affected = HashSet::default();
18055 let multi_buffer = self.buffer().read(cx);
18056 for range in ranges {
18057 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18058 buffers_affected.insert(buffer.read(cx).remote_id());
18059 };
18060 }
18061
18062 self.display_map.update(cx, update);
18063
18064 if auto_scroll {
18065 self.request_autoscroll(Autoscroll::fit(), cx);
18066 }
18067
18068 cx.notify();
18069 self.scrollbar_marker_state.dirty = true;
18070 self.active_indent_guides_state.dirty = true;
18071 }
18072
18073 pub fn update_renderer_widths(
18074 &mut self,
18075 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18076 cx: &mut Context<Self>,
18077 ) -> bool {
18078 self.display_map
18079 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18080 }
18081
18082 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18083 self.display_map.read(cx).fold_placeholder.clone()
18084 }
18085
18086 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18087 self.buffer.update(cx, |buffer, cx| {
18088 buffer.set_all_diff_hunks_expanded(cx);
18089 });
18090 }
18091
18092 pub fn expand_all_diff_hunks(
18093 &mut self,
18094 _: &ExpandAllDiffHunks,
18095 _window: &mut Window,
18096 cx: &mut Context<Self>,
18097 ) {
18098 self.buffer.update(cx, |buffer, cx| {
18099 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18100 });
18101 }
18102
18103 pub fn toggle_selected_diff_hunks(
18104 &mut self,
18105 _: &ToggleSelectedDiffHunks,
18106 _window: &mut Window,
18107 cx: &mut Context<Self>,
18108 ) {
18109 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18110 self.toggle_diff_hunks_in_ranges(ranges, cx);
18111 }
18112
18113 pub fn diff_hunks_in_ranges<'a>(
18114 &'a self,
18115 ranges: &'a [Range<Anchor>],
18116 buffer: &'a MultiBufferSnapshot,
18117 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18118 ranges.iter().flat_map(move |range| {
18119 let end_excerpt_id = range.end.excerpt_id;
18120 let range = range.to_point(buffer);
18121 let mut peek_end = range.end;
18122 if range.end.row < buffer.max_row().0 {
18123 peek_end = Point::new(range.end.row + 1, 0);
18124 }
18125 buffer
18126 .diff_hunks_in_range(range.start..peek_end)
18127 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18128 })
18129 }
18130
18131 pub fn has_stageable_diff_hunks_in_ranges(
18132 &self,
18133 ranges: &[Range<Anchor>],
18134 snapshot: &MultiBufferSnapshot,
18135 ) -> bool {
18136 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18137 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18138 }
18139
18140 pub fn toggle_staged_selected_diff_hunks(
18141 &mut self,
18142 _: &::git::ToggleStaged,
18143 _: &mut Window,
18144 cx: &mut Context<Self>,
18145 ) {
18146 let snapshot = self.buffer.read(cx).snapshot(cx);
18147 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18148 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18149 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18150 }
18151
18152 pub fn set_render_diff_hunk_controls(
18153 &mut self,
18154 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18155 cx: &mut Context<Self>,
18156 ) {
18157 self.render_diff_hunk_controls = render_diff_hunk_controls;
18158 cx.notify();
18159 }
18160
18161 pub fn stage_and_next(
18162 &mut self,
18163 _: &::git::StageAndNext,
18164 window: &mut Window,
18165 cx: &mut Context<Self>,
18166 ) {
18167 self.do_stage_or_unstage_and_next(true, window, cx);
18168 }
18169
18170 pub fn unstage_and_next(
18171 &mut self,
18172 _: &::git::UnstageAndNext,
18173 window: &mut Window,
18174 cx: &mut Context<Self>,
18175 ) {
18176 self.do_stage_or_unstage_and_next(false, window, cx);
18177 }
18178
18179 pub fn stage_or_unstage_diff_hunks(
18180 &mut self,
18181 stage: bool,
18182 ranges: Vec<Range<Anchor>>,
18183 cx: &mut Context<Self>,
18184 ) {
18185 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18186 cx.spawn(async move |this, cx| {
18187 task.await?;
18188 this.update(cx, |this, cx| {
18189 let snapshot = this.buffer.read(cx).snapshot(cx);
18190 let chunk_by = this
18191 .diff_hunks_in_ranges(&ranges, &snapshot)
18192 .chunk_by(|hunk| hunk.buffer_id);
18193 for (buffer_id, hunks) in &chunk_by {
18194 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18195 }
18196 })
18197 })
18198 .detach_and_log_err(cx);
18199 }
18200
18201 fn save_buffers_for_ranges_if_needed(
18202 &mut self,
18203 ranges: &[Range<Anchor>],
18204 cx: &mut Context<Editor>,
18205 ) -> Task<Result<()>> {
18206 let multibuffer = self.buffer.read(cx);
18207 let snapshot = multibuffer.read(cx);
18208 let buffer_ids: HashSet<_> = ranges
18209 .iter()
18210 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18211 .collect();
18212 drop(snapshot);
18213
18214 let mut buffers = HashSet::default();
18215 for buffer_id in buffer_ids {
18216 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18217 let buffer = buffer_entity.read(cx);
18218 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18219 {
18220 buffers.insert(buffer_entity);
18221 }
18222 }
18223 }
18224
18225 if let Some(project) = &self.project {
18226 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18227 } else {
18228 Task::ready(Ok(()))
18229 }
18230 }
18231
18232 fn do_stage_or_unstage_and_next(
18233 &mut self,
18234 stage: bool,
18235 window: &mut Window,
18236 cx: &mut Context<Self>,
18237 ) {
18238 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18239
18240 if ranges.iter().any(|range| range.start != range.end) {
18241 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18242 return;
18243 }
18244
18245 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18246 let snapshot = self.snapshot(window, cx);
18247 let position = self.selections.newest::<Point>(cx).head();
18248 let mut row = snapshot
18249 .buffer_snapshot
18250 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18251 .find(|hunk| hunk.row_range.start.0 > position.row)
18252 .map(|hunk| hunk.row_range.start);
18253
18254 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18255 // Outside of the project diff editor, wrap around to the beginning.
18256 if !all_diff_hunks_expanded {
18257 row = row.or_else(|| {
18258 snapshot
18259 .buffer_snapshot
18260 .diff_hunks_in_range(Point::zero()..position)
18261 .find(|hunk| hunk.row_range.end.0 < position.row)
18262 .map(|hunk| hunk.row_range.start)
18263 });
18264 }
18265
18266 if let Some(row) = row {
18267 let destination = Point::new(row.0, 0);
18268 let autoscroll = Autoscroll::center();
18269
18270 self.unfold_ranges(&[destination..destination], false, false, cx);
18271 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18272 s.select_ranges([destination..destination]);
18273 });
18274 }
18275 }
18276
18277 fn do_stage_or_unstage(
18278 &self,
18279 stage: bool,
18280 buffer_id: BufferId,
18281 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18282 cx: &mut App,
18283 ) -> Option<()> {
18284 let project = self.project()?;
18285 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18286 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18287 let buffer_snapshot = buffer.read(cx).snapshot();
18288 let file_exists = buffer_snapshot
18289 .file()
18290 .is_some_and(|file| file.disk_state().exists());
18291 diff.update(cx, |diff, cx| {
18292 diff.stage_or_unstage_hunks(
18293 stage,
18294 &hunks
18295 .map(|hunk| buffer_diff::DiffHunk {
18296 buffer_range: hunk.buffer_range,
18297 diff_base_byte_range: hunk.diff_base_byte_range,
18298 secondary_status: hunk.secondary_status,
18299 range: Point::zero()..Point::zero(), // unused
18300 })
18301 .collect::<Vec<_>>(),
18302 &buffer_snapshot,
18303 file_exists,
18304 cx,
18305 )
18306 });
18307 None
18308 }
18309
18310 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18311 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18312 self.buffer
18313 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18314 }
18315
18316 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18317 self.buffer.update(cx, |buffer, cx| {
18318 let ranges = vec![Anchor::min()..Anchor::max()];
18319 if !buffer.all_diff_hunks_expanded()
18320 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18321 {
18322 buffer.collapse_diff_hunks(ranges, cx);
18323 true
18324 } else {
18325 false
18326 }
18327 })
18328 }
18329
18330 fn toggle_diff_hunks_in_ranges(
18331 &mut self,
18332 ranges: Vec<Range<Anchor>>,
18333 cx: &mut Context<Editor>,
18334 ) {
18335 self.buffer.update(cx, |buffer, cx| {
18336 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18337 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18338 })
18339 }
18340
18341 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18342 self.buffer.update(cx, |buffer, cx| {
18343 let snapshot = buffer.snapshot(cx);
18344 let excerpt_id = range.end.excerpt_id;
18345 let point_range = range.to_point(&snapshot);
18346 let expand = !buffer.single_hunk_is_expanded(range, cx);
18347 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18348 })
18349 }
18350
18351 pub(crate) fn apply_all_diff_hunks(
18352 &mut self,
18353 _: &ApplyAllDiffHunks,
18354 window: &mut Window,
18355 cx: &mut Context<Self>,
18356 ) {
18357 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18358
18359 let buffers = self.buffer.read(cx).all_buffers();
18360 for branch_buffer in buffers {
18361 branch_buffer.update(cx, |branch_buffer, cx| {
18362 branch_buffer.merge_into_base(Vec::new(), cx);
18363 });
18364 }
18365
18366 if let Some(project) = self.project.clone() {
18367 self.save(
18368 SaveOptions {
18369 format: true,
18370 autosave: false,
18371 },
18372 project,
18373 window,
18374 cx,
18375 )
18376 .detach_and_log_err(cx);
18377 }
18378 }
18379
18380 pub(crate) fn apply_selected_diff_hunks(
18381 &mut self,
18382 _: &ApplyDiffHunk,
18383 window: &mut Window,
18384 cx: &mut Context<Self>,
18385 ) {
18386 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18387 let snapshot = self.snapshot(window, cx);
18388 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18389 let mut ranges_by_buffer = HashMap::default();
18390 self.transact(window, cx, |editor, _window, cx| {
18391 for hunk in hunks {
18392 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18393 ranges_by_buffer
18394 .entry(buffer.clone())
18395 .or_insert_with(Vec::new)
18396 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18397 }
18398 }
18399
18400 for (buffer, ranges) in ranges_by_buffer {
18401 buffer.update(cx, |buffer, cx| {
18402 buffer.merge_into_base(ranges, cx);
18403 });
18404 }
18405 });
18406
18407 if let Some(project) = self.project.clone() {
18408 self.save(
18409 SaveOptions {
18410 format: true,
18411 autosave: false,
18412 },
18413 project,
18414 window,
18415 cx,
18416 )
18417 .detach_and_log_err(cx);
18418 }
18419 }
18420
18421 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18422 if hovered != self.gutter_hovered {
18423 self.gutter_hovered = hovered;
18424 cx.notify();
18425 }
18426 }
18427
18428 pub fn insert_blocks(
18429 &mut self,
18430 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18431 autoscroll: Option<Autoscroll>,
18432 cx: &mut Context<Self>,
18433 ) -> Vec<CustomBlockId> {
18434 let blocks = self
18435 .display_map
18436 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18437 if let Some(autoscroll) = autoscroll {
18438 self.request_autoscroll(autoscroll, cx);
18439 }
18440 cx.notify();
18441 blocks
18442 }
18443
18444 pub fn resize_blocks(
18445 &mut self,
18446 heights: HashMap<CustomBlockId, u32>,
18447 autoscroll: Option<Autoscroll>,
18448 cx: &mut Context<Self>,
18449 ) {
18450 self.display_map
18451 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18452 if let Some(autoscroll) = autoscroll {
18453 self.request_autoscroll(autoscroll, cx);
18454 }
18455 cx.notify();
18456 }
18457
18458 pub fn replace_blocks(
18459 &mut self,
18460 renderers: HashMap<CustomBlockId, RenderBlock>,
18461 autoscroll: Option<Autoscroll>,
18462 cx: &mut Context<Self>,
18463 ) {
18464 self.display_map
18465 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18466 if let Some(autoscroll) = autoscroll {
18467 self.request_autoscroll(autoscroll, cx);
18468 }
18469 cx.notify();
18470 }
18471
18472 pub fn remove_blocks(
18473 &mut self,
18474 block_ids: HashSet<CustomBlockId>,
18475 autoscroll: Option<Autoscroll>,
18476 cx: &mut Context<Self>,
18477 ) {
18478 self.display_map.update(cx, |display_map, cx| {
18479 display_map.remove_blocks(block_ids, cx)
18480 });
18481 if let Some(autoscroll) = autoscroll {
18482 self.request_autoscroll(autoscroll, cx);
18483 }
18484 cx.notify();
18485 }
18486
18487 pub fn row_for_block(
18488 &self,
18489 block_id: CustomBlockId,
18490 cx: &mut Context<Self>,
18491 ) -> Option<DisplayRow> {
18492 self.display_map
18493 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18494 }
18495
18496 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18497 self.focused_block = Some(focused_block);
18498 }
18499
18500 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18501 self.focused_block.take()
18502 }
18503
18504 pub fn insert_creases(
18505 &mut self,
18506 creases: impl IntoIterator<Item = Crease<Anchor>>,
18507 cx: &mut Context<Self>,
18508 ) -> Vec<CreaseId> {
18509 self.display_map
18510 .update(cx, |map, cx| map.insert_creases(creases, cx))
18511 }
18512
18513 pub fn remove_creases(
18514 &mut self,
18515 ids: impl IntoIterator<Item = CreaseId>,
18516 cx: &mut Context<Self>,
18517 ) -> Vec<(CreaseId, Range<Anchor>)> {
18518 self.display_map
18519 .update(cx, |map, cx| map.remove_creases(ids, cx))
18520 }
18521
18522 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18523 self.display_map
18524 .update(cx, |map, cx| map.snapshot(cx))
18525 .longest_row()
18526 }
18527
18528 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18529 self.display_map
18530 .update(cx, |map, cx| map.snapshot(cx))
18531 .max_point()
18532 }
18533
18534 pub fn text(&self, cx: &App) -> String {
18535 self.buffer.read(cx).read(cx).text()
18536 }
18537
18538 pub fn is_empty(&self, cx: &App) -> bool {
18539 self.buffer.read(cx).read(cx).is_empty()
18540 }
18541
18542 pub fn text_option(&self, cx: &App) -> Option<String> {
18543 let text = self.text(cx);
18544 let text = text.trim();
18545
18546 if text.is_empty() {
18547 return None;
18548 }
18549
18550 Some(text.to_string())
18551 }
18552
18553 pub fn set_text(
18554 &mut self,
18555 text: impl Into<Arc<str>>,
18556 window: &mut Window,
18557 cx: &mut Context<Self>,
18558 ) {
18559 self.transact(window, cx, |this, _, cx| {
18560 this.buffer
18561 .read(cx)
18562 .as_singleton()
18563 .expect("you can only call set_text on editors for singleton buffers")
18564 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18565 });
18566 }
18567
18568 pub fn display_text(&self, cx: &mut App) -> String {
18569 self.display_map
18570 .update(cx, |map, cx| map.snapshot(cx))
18571 .text()
18572 }
18573
18574 fn create_minimap(
18575 &self,
18576 minimap_settings: MinimapSettings,
18577 window: &mut Window,
18578 cx: &mut Context<Self>,
18579 ) -> Option<Entity<Self>> {
18580 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18581 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18582 }
18583
18584 fn initialize_new_minimap(
18585 &self,
18586 minimap_settings: MinimapSettings,
18587 window: &mut Window,
18588 cx: &mut Context<Self>,
18589 ) -> Entity<Self> {
18590 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18591
18592 let mut minimap = Editor::new_internal(
18593 EditorMode::Minimap {
18594 parent: cx.weak_entity(),
18595 },
18596 self.buffer.clone(),
18597 None,
18598 Some(self.display_map.clone()),
18599 window,
18600 cx,
18601 );
18602 minimap.scroll_manager.clone_state(&self.scroll_manager);
18603 minimap.set_text_style_refinement(TextStyleRefinement {
18604 font_size: Some(MINIMAP_FONT_SIZE),
18605 font_weight: Some(MINIMAP_FONT_WEIGHT),
18606 ..Default::default()
18607 });
18608 minimap.update_minimap_configuration(minimap_settings, cx);
18609 cx.new(|_| minimap)
18610 }
18611
18612 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18613 let current_line_highlight = minimap_settings
18614 .current_line_highlight
18615 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18616 self.set_current_line_highlight(Some(current_line_highlight));
18617 }
18618
18619 pub fn minimap(&self) -> Option<&Entity<Self>> {
18620 self.minimap
18621 .as_ref()
18622 .filter(|_| self.minimap_visibility.visible())
18623 }
18624
18625 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18626 let mut wrap_guides = smallvec![];
18627
18628 if self.show_wrap_guides == Some(false) {
18629 return wrap_guides;
18630 }
18631
18632 let settings = self.buffer.read(cx).language_settings(cx);
18633 if settings.show_wrap_guides {
18634 match self.soft_wrap_mode(cx) {
18635 SoftWrap::Column(soft_wrap) => {
18636 wrap_guides.push((soft_wrap as usize, true));
18637 }
18638 SoftWrap::Bounded(soft_wrap) => {
18639 wrap_guides.push((soft_wrap as usize, true));
18640 }
18641 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18642 }
18643 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18644 }
18645
18646 wrap_guides
18647 }
18648
18649 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18650 let settings = self.buffer.read(cx).language_settings(cx);
18651 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18652 match mode {
18653 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18654 SoftWrap::None
18655 }
18656 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18657 language_settings::SoftWrap::PreferredLineLength => {
18658 SoftWrap::Column(settings.preferred_line_length)
18659 }
18660 language_settings::SoftWrap::Bounded => {
18661 SoftWrap::Bounded(settings.preferred_line_length)
18662 }
18663 }
18664 }
18665
18666 pub fn set_soft_wrap_mode(
18667 &mut self,
18668 mode: language_settings::SoftWrap,
18669
18670 cx: &mut Context<Self>,
18671 ) {
18672 self.soft_wrap_mode_override = Some(mode);
18673 cx.notify();
18674 }
18675
18676 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18677 self.hard_wrap = hard_wrap;
18678 cx.notify();
18679 }
18680
18681 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18682 self.text_style_refinement = Some(style);
18683 }
18684
18685 /// called by the Element so we know what style we were most recently rendered with.
18686 pub(crate) fn set_style(
18687 &mut self,
18688 style: EditorStyle,
18689 window: &mut Window,
18690 cx: &mut Context<Self>,
18691 ) {
18692 // We intentionally do not inform the display map about the minimap style
18693 // so that wrapping is not recalculated and stays consistent for the editor
18694 // and its linked minimap.
18695 if !self.mode.is_minimap() {
18696 let rem_size = window.rem_size();
18697 self.display_map.update(cx, |map, cx| {
18698 map.set_font(
18699 style.text.font(),
18700 style.text.font_size.to_pixels(rem_size),
18701 cx,
18702 )
18703 });
18704 }
18705 self.style = Some(style);
18706 }
18707
18708 pub fn style(&self) -> Option<&EditorStyle> {
18709 self.style.as_ref()
18710 }
18711
18712 // Called by the element. This method is not designed to be called outside of the editor
18713 // element's layout code because it does not notify when rewrapping is computed synchronously.
18714 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18715 self.display_map
18716 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18717 }
18718
18719 pub fn set_soft_wrap(&mut self) {
18720 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18721 }
18722
18723 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18724 if self.soft_wrap_mode_override.is_some() {
18725 self.soft_wrap_mode_override.take();
18726 } else {
18727 let soft_wrap = match self.soft_wrap_mode(cx) {
18728 SoftWrap::GitDiff => return,
18729 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18730 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18731 language_settings::SoftWrap::None
18732 }
18733 };
18734 self.soft_wrap_mode_override = Some(soft_wrap);
18735 }
18736 cx.notify();
18737 }
18738
18739 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18740 let Some(workspace) = self.workspace() else {
18741 return;
18742 };
18743 let fs = workspace.read(cx).app_state().fs.clone();
18744 let current_show = TabBarSettings::get_global(cx).show;
18745 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18746 setting.show = Some(!current_show);
18747 });
18748 }
18749
18750 pub fn toggle_indent_guides(
18751 &mut self,
18752 _: &ToggleIndentGuides,
18753 _: &mut Window,
18754 cx: &mut Context<Self>,
18755 ) {
18756 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18757 self.buffer
18758 .read(cx)
18759 .language_settings(cx)
18760 .indent_guides
18761 .enabled
18762 });
18763 self.show_indent_guides = Some(!currently_enabled);
18764 cx.notify();
18765 }
18766
18767 fn should_show_indent_guides(&self) -> Option<bool> {
18768 self.show_indent_guides
18769 }
18770
18771 pub fn toggle_line_numbers(
18772 &mut self,
18773 _: &ToggleLineNumbers,
18774 _: &mut Window,
18775 cx: &mut Context<Self>,
18776 ) {
18777 let mut editor_settings = EditorSettings::get_global(cx).clone();
18778 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18779 EditorSettings::override_global(editor_settings, cx);
18780 }
18781
18782 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18783 if let Some(show_line_numbers) = self.show_line_numbers {
18784 return show_line_numbers;
18785 }
18786 EditorSettings::get_global(cx).gutter.line_numbers
18787 }
18788
18789 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18790 self.use_relative_line_numbers
18791 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18792 }
18793
18794 pub fn toggle_relative_line_numbers(
18795 &mut self,
18796 _: &ToggleRelativeLineNumbers,
18797 _: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 let is_relative = self.should_use_relative_line_numbers(cx);
18801 self.set_relative_line_number(Some(!is_relative), cx)
18802 }
18803
18804 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18805 self.use_relative_line_numbers = is_relative;
18806 cx.notify();
18807 }
18808
18809 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18810 self.show_gutter = show_gutter;
18811 cx.notify();
18812 }
18813
18814 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18815 self.show_scrollbars = ScrollbarAxes {
18816 horizontal: show,
18817 vertical: show,
18818 };
18819 cx.notify();
18820 }
18821
18822 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18823 self.show_scrollbars.vertical = show;
18824 cx.notify();
18825 }
18826
18827 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18828 self.show_scrollbars.horizontal = show;
18829 cx.notify();
18830 }
18831
18832 pub fn set_minimap_visibility(
18833 &mut self,
18834 minimap_visibility: MinimapVisibility,
18835 window: &mut Window,
18836 cx: &mut Context<Self>,
18837 ) {
18838 if self.minimap_visibility != minimap_visibility {
18839 if minimap_visibility.visible() && self.minimap.is_none() {
18840 let minimap_settings = EditorSettings::get_global(cx).minimap;
18841 self.minimap =
18842 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18843 }
18844 self.minimap_visibility = minimap_visibility;
18845 cx.notify();
18846 }
18847 }
18848
18849 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18850 self.set_show_scrollbars(false, cx);
18851 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18852 }
18853
18854 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18855 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18856 }
18857
18858 /// Normally the text in full mode and auto height editors is padded on the
18859 /// left side by roughly half a character width for improved hit testing.
18860 ///
18861 /// Use this method to disable this for cases where this is not wanted (e.g.
18862 /// if you want to align the editor text with some other text above or below)
18863 /// or if you want to add this padding to single-line editors.
18864 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18865 self.offset_content = offset_content;
18866 cx.notify();
18867 }
18868
18869 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18870 self.show_line_numbers = Some(show_line_numbers);
18871 cx.notify();
18872 }
18873
18874 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18875 self.disable_expand_excerpt_buttons = true;
18876 cx.notify();
18877 }
18878
18879 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18880 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18881 cx.notify();
18882 }
18883
18884 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18885 self.show_code_actions = Some(show_code_actions);
18886 cx.notify();
18887 }
18888
18889 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18890 self.show_runnables = Some(show_runnables);
18891 cx.notify();
18892 }
18893
18894 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18895 self.show_breakpoints = Some(show_breakpoints);
18896 cx.notify();
18897 }
18898
18899 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18900 if self.display_map.read(cx).masked != masked {
18901 self.display_map.update(cx, |map, _| map.masked = masked);
18902 }
18903 cx.notify()
18904 }
18905
18906 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18907 self.show_wrap_guides = Some(show_wrap_guides);
18908 cx.notify();
18909 }
18910
18911 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18912 self.show_indent_guides = Some(show_indent_guides);
18913 cx.notify();
18914 }
18915
18916 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18917 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18918 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18919 && let Some(dir) = file.abs_path(cx).parent()
18920 {
18921 return Some(dir.to_owned());
18922 }
18923
18924 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18925 return Some(project_path.path.to_path_buf());
18926 }
18927 }
18928
18929 None
18930 }
18931
18932 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18933 self.active_excerpt(cx)?
18934 .1
18935 .read(cx)
18936 .file()
18937 .and_then(|f| f.as_local())
18938 }
18939
18940 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18941 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18942 let buffer = buffer.read(cx);
18943 if let Some(project_path) = buffer.project_path(cx) {
18944 let project = self.project()?.read(cx);
18945 project.absolute_path(&project_path, cx)
18946 } else {
18947 buffer
18948 .file()
18949 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18950 }
18951 })
18952 }
18953
18954 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18955 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18956 let project_path = buffer.read(cx).project_path(cx)?;
18957 let project = self.project()?.read(cx);
18958 let entry = project.entry_for_path(&project_path, cx)?;
18959 let path = entry.path.to_path_buf();
18960 Some(path)
18961 })
18962 }
18963
18964 pub fn reveal_in_finder(
18965 &mut self,
18966 _: &RevealInFileManager,
18967 _window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 if let Some(target) = self.target_file(cx) {
18971 cx.reveal_path(&target.abs_path(cx));
18972 }
18973 }
18974
18975 pub fn copy_path(
18976 &mut self,
18977 _: &zed_actions::workspace::CopyPath,
18978 _window: &mut Window,
18979 cx: &mut Context<Self>,
18980 ) {
18981 if let Some(path) = self.target_file_abs_path(cx)
18982 && let Some(path) = path.to_str()
18983 {
18984 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18985 }
18986 }
18987
18988 pub fn copy_relative_path(
18989 &mut self,
18990 _: &zed_actions::workspace::CopyRelativePath,
18991 _window: &mut Window,
18992 cx: &mut Context<Self>,
18993 ) {
18994 if let Some(path) = self.target_file_path(cx)
18995 && let Some(path) = path.to_str()
18996 {
18997 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18998 }
18999 }
19000
19001 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19002 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19003 buffer.read(cx).project_path(cx)
19004 } else {
19005 None
19006 }
19007 }
19008
19009 // Returns true if the editor handled a go-to-line request
19010 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19011 maybe!({
19012 let breakpoint_store = self.breakpoint_store.as_ref()?;
19013
19014 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19015 else {
19016 self.clear_row_highlights::<ActiveDebugLine>();
19017 return None;
19018 };
19019
19020 let position = active_stack_frame.position;
19021 let buffer_id = position.buffer_id?;
19022 let snapshot = self
19023 .project
19024 .as_ref()?
19025 .read(cx)
19026 .buffer_for_id(buffer_id, cx)?
19027 .read(cx)
19028 .snapshot();
19029
19030 let mut handled = false;
19031 for (id, ExcerptRange { context, .. }) in
19032 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19033 {
19034 if context.start.cmp(&position, &snapshot).is_ge()
19035 || context.end.cmp(&position, &snapshot).is_lt()
19036 {
19037 continue;
19038 }
19039 let snapshot = self.buffer.read(cx).snapshot(cx);
19040 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19041
19042 handled = true;
19043 self.clear_row_highlights::<ActiveDebugLine>();
19044
19045 self.go_to_line::<ActiveDebugLine>(
19046 multibuffer_anchor,
19047 Some(cx.theme().colors().editor_debugger_active_line_background),
19048 window,
19049 cx,
19050 );
19051
19052 cx.notify();
19053 }
19054
19055 handled.then_some(())
19056 })
19057 .is_some()
19058 }
19059
19060 pub fn copy_file_name_without_extension(
19061 &mut self,
19062 _: &CopyFileNameWithoutExtension,
19063 _: &mut Window,
19064 cx: &mut Context<Self>,
19065 ) {
19066 if let Some(file) = self.target_file(cx)
19067 && let Some(file_stem) = file.path().file_stem()
19068 && let Some(name) = file_stem.to_str()
19069 {
19070 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19071 }
19072 }
19073
19074 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19075 if let Some(file) = self.target_file(cx)
19076 && let Some(file_name) = file.path().file_name()
19077 && let Some(name) = file_name.to_str()
19078 {
19079 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19080 }
19081 }
19082
19083 pub fn toggle_git_blame(
19084 &mut self,
19085 _: &::git::Blame,
19086 window: &mut Window,
19087 cx: &mut Context<Self>,
19088 ) {
19089 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19090
19091 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19092 self.start_git_blame(true, window, cx);
19093 }
19094
19095 cx.notify();
19096 }
19097
19098 pub fn toggle_git_blame_inline(
19099 &mut self,
19100 _: &ToggleGitBlameInline,
19101 window: &mut Window,
19102 cx: &mut Context<Self>,
19103 ) {
19104 self.toggle_git_blame_inline_internal(true, window, cx);
19105 cx.notify();
19106 }
19107
19108 pub fn open_git_blame_commit(
19109 &mut self,
19110 _: &OpenGitBlameCommit,
19111 window: &mut Window,
19112 cx: &mut Context<Self>,
19113 ) {
19114 self.open_git_blame_commit_internal(window, cx);
19115 }
19116
19117 fn open_git_blame_commit_internal(
19118 &mut self,
19119 window: &mut Window,
19120 cx: &mut Context<Self>,
19121 ) -> Option<()> {
19122 let blame = self.blame.as_ref()?;
19123 let snapshot = self.snapshot(window, cx);
19124 let cursor = self.selections.newest::<Point>(cx).head();
19125 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19126 let (_, blame_entry) = blame
19127 .update(cx, |blame, cx| {
19128 blame
19129 .blame_for_rows(
19130 &[RowInfo {
19131 buffer_id: Some(buffer.remote_id()),
19132 buffer_row: Some(point.row),
19133 ..Default::default()
19134 }],
19135 cx,
19136 )
19137 .next()
19138 })
19139 .flatten()?;
19140 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19141 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19142 let workspace = self.workspace()?.downgrade();
19143 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19144 None
19145 }
19146
19147 pub fn git_blame_inline_enabled(&self) -> bool {
19148 self.git_blame_inline_enabled
19149 }
19150
19151 pub fn toggle_selection_menu(
19152 &mut self,
19153 _: &ToggleSelectionMenu,
19154 _: &mut Window,
19155 cx: &mut Context<Self>,
19156 ) {
19157 self.show_selection_menu = self
19158 .show_selection_menu
19159 .map(|show_selections_menu| !show_selections_menu)
19160 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19161
19162 cx.notify();
19163 }
19164
19165 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19166 self.show_selection_menu
19167 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19168 }
19169
19170 fn start_git_blame(
19171 &mut self,
19172 user_triggered: bool,
19173 window: &mut Window,
19174 cx: &mut Context<Self>,
19175 ) {
19176 if let Some(project) = self.project() {
19177 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19178 && buffer.read(cx).file().is_none()
19179 {
19180 return;
19181 }
19182
19183 let focused = self.focus_handle(cx).contains_focused(window, cx);
19184
19185 let project = project.clone();
19186 let blame = cx
19187 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19188 self.blame_subscription =
19189 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19190 self.blame = Some(blame);
19191 }
19192 }
19193
19194 fn toggle_git_blame_inline_internal(
19195 &mut self,
19196 user_triggered: bool,
19197 window: &mut Window,
19198 cx: &mut Context<Self>,
19199 ) {
19200 if self.git_blame_inline_enabled {
19201 self.git_blame_inline_enabled = false;
19202 self.show_git_blame_inline = false;
19203 self.show_git_blame_inline_delay_task.take();
19204 } else {
19205 self.git_blame_inline_enabled = true;
19206 self.start_git_blame_inline(user_triggered, window, cx);
19207 }
19208
19209 cx.notify();
19210 }
19211
19212 fn start_git_blame_inline(
19213 &mut self,
19214 user_triggered: bool,
19215 window: &mut Window,
19216 cx: &mut Context<Self>,
19217 ) {
19218 self.start_git_blame(user_triggered, window, cx);
19219
19220 if ProjectSettings::get_global(cx)
19221 .git
19222 .inline_blame_delay()
19223 .is_some()
19224 {
19225 self.start_inline_blame_timer(window, cx);
19226 } else {
19227 self.show_git_blame_inline = true
19228 }
19229 }
19230
19231 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19232 self.blame.as_ref()
19233 }
19234
19235 pub fn show_git_blame_gutter(&self) -> bool {
19236 self.show_git_blame_gutter
19237 }
19238
19239 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19240 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19241 }
19242
19243 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19244 self.show_git_blame_inline
19245 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19246 && !self.newest_selection_head_on_empty_line(cx)
19247 && self.has_blame_entries(cx)
19248 }
19249
19250 fn has_blame_entries(&self, cx: &App) -> bool {
19251 self.blame()
19252 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19253 }
19254
19255 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19256 let cursor_anchor = self.selections.newest_anchor().head();
19257
19258 let snapshot = self.buffer.read(cx).snapshot(cx);
19259 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19260
19261 snapshot.line_len(buffer_row) == 0
19262 }
19263
19264 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19265 let buffer_and_selection = maybe!({
19266 let selection = self.selections.newest::<Point>(cx);
19267 let selection_range = selection.range();
19268
19269 let multi_buffer = self.buffer().read(cx);
19270 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19271 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19272
19273 let (buffer, range, _) = if selection.reversed {
19274 buffer_ranges.first()
19275 } else {
19276 buffer_ranges.last()
19277 }?;
19278
19279 let selection = text::ToPoint::to_point(&range.start, buffer).row
19280 ..text::ToPoint::to_point(&range.end, buffer).row;
19281 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19282 });
19283
19284 let Some((buffer, selection)) = buffer_and_selection else {
19285 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19286 };
19287
19288 let Some(project) = self.project() else {
19289 return Task::ready(Err(anyhow!("editor does not have project")));
19290 };
19291
19292 project.update(cx, |project, cx| {
19293 project.get_permalink_to_line(&buffer, selection, cx)
19294 })
19295 }
19296
19297 pub fn copy_permalink_to_line(
19298 &mut self,
19299 _: &CopyPermalinkToLine,
19300 window: &mut Window,
19301 cx: &mut Context<Self>,
19302 ) {
19303 let permalink_task = self.get_permalink_to_line(cx);
19304 let workspace = self.workspace();
19305
19306 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19307 Ok(permalink) => {
19308 cx.update(|_, cx| {
19309 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19310 })
19311 .ok();
19312 }
19313 Err(err) => {
19314 let message = format!("Failed to copy permalink: {err}");
19315
19316 anyhow::Result::<()>::Err(err).log_err();
19317
19318 if let Some(workspace) = workspace {
19319 workspace
19320 .update_in(cx, |workspace, _, cx| {
19321 struct CopyPermalinkToLine;
19322
19323 workspace.show_toast(
19324 Toast::new(
19325 NotificationId::unique::<CopyPermalinkToLine>(),
19326 message,
19327 ),
19328 cx,
19329 )
19330 })
19331 .ok();
19332 }
19333 }
19334 })
19335 .detach();
19336 }
19337
19338 pub fn copy_file_location(
19339 &mut self,
19340 _: &CopyFileLocation,
19341 _: &mut Window,
19342 cx: &mut Context<Self>,
19343 ) {
19344 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19345 if let Some(file) = self.target_file(cx)
19346 && let Some(path) = file.path().to_str()
19347 {
19348 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19349 }
19350 }
19351
19352 pub fn open_permalink_to_line(
19353 &mut self,
19354 _: &OpenPermalinkToLine,
19355 window: &mut Window,
19356 cx: &mut Context<Self>,
19357 ) {
19358 let permalink_task = self.get_permalink_to_line(cx);
19359 let workspace = self.workspace();
19360
19361 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19362 Ok(permalink) => {
19363 cx.update(|_, cx| {
19364 cx.open_url(permalink.as_ref());
19365 })
19366 .ok();
19367 }
19368 Err(err) => {
19369 let message = format!("Failed to open permalink: {err}");
19370
19371 anyhow::Result::<()>::Err(err).log_err();
19372
19373 if let Some(workspace) = workspace {
19374 workspace
19375 .update(cx, |workspace, cx| {
19376 struct OpenPermalinkToLine;
19377
19378 workspace.show_toast(
19379 Toast::new(
19380 NotificationId::unique::<OpenPermalinkToLine>(),
19381 message,
19382 ),
19383 cx,
19384 )
19385 })
19386 .ok();
19387 }
19388 }
19389 })
19390 .detach();
19391 }
19392
19393 pub fn insert_uuid_v4(
19394 &mut self,
19395 _: &InsertUuidV4,
19396 window: &mut Window,
19397 cx: &mut Context<Self>,
19398 ) {
19399 self.insert_uuid(UuidVersion::V4, window, cx);
19400 }
19401
19402 pub fn insert_uuid_v7(
19403 &mut self,
19404 _: &InsertUuidV7,
19405 window: &mut Window,
19406 cx: &mut Context<Self>,
19407 ) {
19408 self.insert_uuid(UuidVersion::V7, window, cx);
19409 }
19410
19411 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19413 self.transact(window, cx, |this, window, cx| {
19414 let edits = this
19415 .selections
19416 .all::<Point>(cx)
19417 .into_iter()
19418 .map(|selection| {
19419 let uuid = match version {
19420 UuidVersion::V4 => uuid::Uuid::new_v4(),
19421 UuidVersion::V7 => uuid::Uuid::now_v7(),
19422 };
19423
19424 (selection.range(), uuid.to_string())
19425 });
19426 this.edit(edits, cx);
19427 this.refresh_edit_prediction(true, false, window, cx);
19428 });
19429 }
19430
19431 pub fn open_selections_in_multibuffer(
19432 &mut self,
19433 _: &OpenSelectionsInMultibuffer,
19434 window: &mut Window,
19435 cx: &mut Context<Self>,
19436 ) {
19437 let multibuffer = self.buffer.read(cx);
19438
19439 let Some(buffer) = multibuffer.as_singleton() else {
19440 return;
19441 };
19442
19443 let Some(workspace) = self.workspace() else {
19444 return;
19445 };
19446
19447 let title = multibuffer.title(cx).to_string();
19448
19449 let locations = self
19450 .selections
19451 .all_anchors(cx)
19452 .iter()
19453 .map(|selection| Location {
19454 buffer: buffer.clone(),
19455 range: selection.start.text_anchor..selection.end.text_anchor,
19456 })
19457 .collect::<Vec<_>>();
19458
19459 cx.spawn_in(window, async move |_, cx| {
19460 workspace.update_in(cx, |workspace, window, cx| {
19461 Self::open_locations_in_multibuffer(
19462 workspace,
19463 locations,
19464 format!("Selections for '{title}'"),
19465 false,
19466 MultibufferSelectionMode::All,
19467 window,
19468 cx,
19469 );
19470 })
19471 })
19472 .detach();
19473 }
19474
19475 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19476 /// last highlight added will be used.
19477 ///
19478 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19479 pub fn highlight_rows<T: 'static>(
19480 &mut self,
19481 range: Range<Anchor>,
19482 color: Hsla,
19483 options: RowHighlightOptions,
19484 cx: &mut Context<Self>,
19485 ) {
19486 let snapshot = self.buffer().read(cx).snapshot(cx);
19487 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19488 let ix = row_highlights.binary_search_by(|highlight| {
19489 Ordering::Equal
19490 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19491 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19492 });
19493
19494 if let Err(mut ix) = ix {
19495 let index = post_inc(&mut self.highlight_order);
19496
19497 // If this range intersects with the preceding highlight, then merge it with
19498 // the preceding highlight. Otherwise insert a new highlight.
19499 let mut merged = false;
19500 if ix > 0 {
19501 let prev_highlight = &mut row_highlights[ix - 1];
19502 if prev_highlight
19503 .range
19504 .end
19505 .cmp(&range.start, &snapshot)
19506 .is_ge()
19507 {
19508 ix -= 1;
19509 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19510 prev_highlight.range.end = range.end;
19511 }
19512 merged = true;
19513 prev_highlight.index = index;
19514 prev_highlight.color = color;
19515 prev_highlight.options = options;
19516 }
19517 }
19518
19519 if !merged {
19520 row_highlights.insert(
19521 ix,
19522 RowHighlight {
19523 range,
19524 index,
19525 color,
19526 options,
19527 type_id: TypeId::of::<T>(),
19528 },
19529 );
19530 }
19531
19532 // If any of the following highlights intersect with this one, merge them.
19533 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19534 let highlight = &row_highlights[ix];
19535 if next_highlight
19536 .range
19537 .start
19538 .cmp(&highlight.range.end, &snapshot)
19539 .is_le()
19540 {
19541 if next_highlight
19542 .range
19543 .end
19544 .cmp(&highlight.range.end, &snapshot)
19545 .is_gt()
19546 {
19547 row_highlights[ix].range.end = next_highlight.range.end;
19548 }
19549 row_highlights.remove(ix + 1);
19550 } else {
19551 break;
19552 }
19553 }
19554 }
19555 }
19556
19557 /// Remove any highlighted row ranges of the given type that intersect the
19558 /// given ranges.
19559 pub fn remove_highlighted_rows<T: 'static>(
19560 &mut self,
19561 ranges_to_remove: Vec<Range<Anchor>>,
19562 cx: &mut Context<Self>,
19563 ) {
19564 let snapshot = self.buffer().read(cx).snapshot(cx);
19565 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19566 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19567 row_highlights.retain(|highlight| {
19568 while let Some(range_to_remove) = ranges_to_remove.peek() {
19569 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19570 Ordering::Less | Ordering::Equal => {
19571 ranges_to_remove.next();
19572 }
19573 Ordering::Greater => {
19574 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19575 Ordering::Less | Ordering::Equal => {
19576 return false;
19577 }
19578 Ordering::Greater => break,
19579 }
19580 }
19581 }
19582 }
19583
19584 true
19585 })
19586 }
19587
19588 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19589 pub fn clear_row_highlights<T: 'static>(&mut self) {
19590 self.highlighted_rows.remove(&TypeId::of::<T>());
19591 }
19592
19593 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19594 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19595 self.highlighted_rows
19596 .get(&TypeId::of::<T>())
19597 .map_or(&[] as &[_], |vec| vec.as_slice())
19598 .iter()
19599 .map(|highlight| (highlight.range.clone(), highlight.color))
19600 }
19601
19602 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19603 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19604 /// Allows to ignore certain kinds of highlights.
19605 pub fn highlighted_display_rows(
19606 &self,
19607 window: &mut Window,
19608 cx: &mut App,
19609 ) -> BTreeMap<DisplayRow, LineHighlight> {
19610 let snapshot = self.snapshot(window, cx);
19611 let mut used_highlight_orders = HashMap::default();
19612 self.highlighted_rows
19613 .iter()
19614 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19615 .fold(
19616 BTreeMap::<DisplayRow, LineHighlight>::new(),
19617 |mut unique_rows, highlight| {
19618 let start = highlight.range.start.to_display_point(&snapshot);
19619 let end = highlight.range.end.to_display_point(&snapshot);
19620 let start_row = start.row().0;
19621 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19622 && end.column() == 0
19623 {
19624 end.row().0.saturating_sub(1)
19625 } else {
19626 end.row().0
19627 };
19628 for row in start_row..=end_row {
19629 let used_index =
19630 used_highlight_orders.entry(row).or_insert(highlight.index);
19631 if highlight.index >= *used_index {
19632 *used_index = highlight.index;
19633 unique_rows.insert(
19634 DisplayRow(row),
19635 LineHighlight {
19636 include_gutter: highlight.options.include_gutter,
19637 border: None,
19638 background: highlight.color.into(),
19639 type_id: Some(highlight.type_id),
19640 },
19641 );
19642 }
19643 }
19644 unique_rows
19645 },
19646 )
19647 }
19648
19649 pub fn highlighted_display_row_for_autoscroll(
19650 &self,
19651 snapshot: &DisplaySnapshot,
19652 ) -> Option<DisplayRow> {
19653 self.highlighted_rows
19654 .values()
19655 .flat_map(|highlighted_rows| highlighted_rows.iter())
19656 .filter_map(|highlight| {
19657 if highlight.options.autoscroll {
19658 Some(highlight.range.start.to_display_point(snapshot).row())
19659 } else {
19660 None
19661 }
19662 })
19663 .min()
19664 }
19665
19666 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19667 self.highlight_background::<SearchWithinRange>(
19668 ranges,
19669 |colors| colors.colors().editor_document_highlight_read_background,
19670 cx,
19671 )
19672 }
19673
19674 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19675 self.breadcrumb_header = Some(new_header);
19676 }
19677
19678 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19679 self.clear_background_highlights::<SearchWithinRange>(cx);
19680 }
19681
19682 pub fn highlight_background<T: 'static>(
19683 &mut self,
19684 ranges: &[Range<Anchor>],
19685 color_fetcher: fn(&Theme) -> Hsla,
19686 cx: &mut Context<Self>,
19687 ) {
19688 self.background_highlights.insert(
19689 HighlightKey::Type(TypeId::of::<T>()),
19690 (color_fetcher, Arc::from(ranges)),
19691 );
19692 self.scrollbar_marker_state.dirty = true;
19693 cx.notify();
19694 }
19695
19696 pub fn highlight_background_key<T: 'static>(
19697 &mut self,
19698 key: usize,
19699 ranges: &[Range<Anchor>],
19700 color_fetcher: fn(&Theme) -> Hsla,
19701 cx: &mut Context<Self>,
19702 ) {
19703 self.background_highlights.insert(
19704 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19705 (color_fetcher, Arc::from(ranges)),
19706 );
19707 self.scrollbar_marker_state.dirty = true;
19708 cx.notify();
19709 }
19710
19711 pub fn clear_background_highlights<T: 'static>(
19712 &mut self,
19713 cx: &mut Context<Self>,
19714 ) -> Option<BackgroundHighlight> {
19715 let text_highlights = self
19716 .background_highlights
19717 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19718 if !text_highlights.1.is_empty() {
19719 self.scrollbar_marker_state.dirty = true;
19720 cx.notify();
19721 }
19722 Some(text_highlights)
19723 }
19724
19725 pub fn highlight_gutter<T: 'static>(
19726 &mut self,
19727 ranges: impl Into<Vec<Range<Anchor>>>,
19728 color_fetcher: fn(&App) -> Hsla,
19729 cx: &mut Context<Self>,
19730 ) {
19731 self.gutter_highlights
19732 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19733 cx.notify();
19734 }
19735
19736 pub fn clear_gutter_highlights<T: 'static>(
19737 &mut self,
19738 cx: &mut Context<Self>,
19739 ) -> Option<GutterHighlight> {
19740 cx.notify();
19741 self.gutter_highlights.remove(&TypeId::of::<T>())
19742 }
19743
19744 pub fn insert_gutter_highlight<T: 'static>(
19745 &mut self,
19746 range: Range<Anchor>,
19747 color_fetcher: fn(&App) -> Hsla,
19748 cx: &mut Context<Self>,
19749 ) {
19750 let snapshot = self.buffer().read(cx).snapshot(cx);
19751 let mut highlights = self
19752 .gutter_highlights
19753 .remove(&TypeId::of::<T>())
19754 .map(|(_, highlights)| highlights)
19755 .unwrap_or_default();
19756 let ix = highlights.binary_search_by(|highlight| {
19757 Ordering::Equal
19758 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19759 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19760 });
19761 if let Err(ix) = ix {
19762 highlights.insert(ix, range);
19763 }
19764 self.gutter_highlights
19765 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19766 }
19767
19768 pub fn remove_gutter_highlights<T: 'static>(
19769 &mut self,
19770 ranges_to_remove: Vec<Range<Anchor>>,
19771 cx: &mut Context<Self>,
19772 ) {
19773 let snapshot = self.buffer().read(cx).snapshot(cx);
19774 let Some((color_fetcher, mut gutter_highlights)) =
19775 self.gutter_highlights.remove(&TypeId::of::<T>())
19776 else {
19777 return;
19778 };
19779 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19780 gutter_highlights.retain(|highlight| {
19781 while let Some(range_to_remove) = ranges_to_remove.peek() {
19782 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19783 Ordering::Less | Ordering::Equal => {
19784 ranges_to_remove.next();
19785 }
19786 Ordering::Greater => {
19787 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19788 Ordering::Less | Ordering::Equal => {
19789 return false;
19790 }
19791 Ordering::Greater => break,
19792 }
19793 }
19794 }
19795 }
19796
19797 true
19798 });
19799 self.gutter_highlights
19800 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19801 }
19802
19803 #[cfg(feature = "test-support")]
19804 pub fn all_text_highlights(
19805 &self,
19806 window: &mut Window,
19807 cx: &mut Context<Self>,
19808 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19809 let snapshot = self.snapshot(window, cx);
19810 self.display_map.update(cx, |display_map, _| {
19811 display_map
19812 .all_text_highlights()
19813 .map(|highlight| {
19814 let (style, ranges) = highlight.as_ref();
19815 (
19816 *style,
19817 ranges
19818 .iter()
19819 .map(|range| range.clone().to_display_points(&snapshot))
19820 .collect(),
19821 )
19822 })
19823 .collect()
19824 })
19825 }
19826
19827 #[cfg(feature = "test-support")]
19828 pub fn all_text_background_highlights(
19829 &self,
19830 window: &mut Window,
19831 cx: &mut Context<Self>,
19832 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19833 let snapshot = self.snapshot(window, cx);
19834 let buffer = &snapshot.buffer_snapshot;
19835 let start = buffer.anchor_before(0);
19836 let end = buffer.anchor_after(buffer.len());
19837 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
19838 }
19839
19840 #[cfg(any(test, feature = "test-support"))]
19841 pub fn sorted_background_highlights_in_range(
19842 &self,
19843 search_range: Range<Anchor>,
19844 display_snapshot: &DisplaySnapshot,
19845 theme: &Theme,
19846 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19847 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
19848 res.sort_by(|a, b| {
19849 a.0.start
19850 .cmp(&b.0.start)
19851 .then_with(|| a.0.end.cmp(&b.0.end))
19852 .then_with(|| a.1.cmp(&b.1))
19853 });
19854 res
19855 }
19856
19857 #[cfg(feature = "test-support")]
19858 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19859 let snapshot = self.buffer().read(cx).snapshot(cx);
19860
19861 let highlights = self
19862 .background_highlights
19863 .get(&HighlightKey::Type(TypeId::of::<
19864 items::BufferSearchHighlights,
19865 >()));
19866
19867 if let Some((_color, ranges)) = highlights {
19868 ranges
19869 .iter()
19870 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19871 .collect_vec()
19872 } else {
19873 vec![]
19874 }
19875 }
19876
19877 fn document_highlights_for_position<'a>(
19878 &'a self,
19879 position: Anchor,
19880 buffer: &'a MultiBufferSnapshot,
19881 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19882 let read_highlights = self
19883 .background_highlights
19884 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19885 .map(|h| &h.1);
19886 let write_highlights = self
19887 .background_highlights
19888 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19889 .map(|h| &h.1);
19890 let left_position = position.bias_left(buffer);
19891 let right_position = position.bias_right(buffer);
19892 read_highlights
19893 .into_iter()
19894 .chain(write_highlights)
19895 .flat_map(move |ranges| {
19896 let start_ix = match ranges.binary_search_by(|probe| {
19897 let cmp = probe.end.cmp(&left_position, buffer);
19898 if cmp.is_ge() {
19899 Ordering::Greater
19900 } else {
19901 Ordering::Less
19902 }
19903 }) {
19904 Ok(i) | Err(i) => i,
19905 };
19906
19907 ranges[start_ix..]
19908 .iter()
19909 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19910 })
19911 }
19912
19913 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19914 self.background_highlights
19915 .get(&HighlightKey::Type(TypeId::of::<T>()))
19916 .is_some_and(|(_, highlights)| !highlights.is_empty())
19917 }
19918
19919 /// Returns all background highlights for a given range.
19920 ///
19921 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
19922 pub fn background_highlights_in_range(
19923 &self,
19924 search_range: Range<Anchor>,
19925 display_snapshot: &DisplaySnapshot,
19926 theme: &Theme,
19927 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19928 let mut results = Vec::new();
19929 for (color_fetcher, ranges) in self.background_highlights.values() {
19930 let color = color_fetcher(theme);
19931 let start_ix = match ranges.binary_search_by(|probe| {
19932 let cmp = probe
19933 .end
19934 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19935 if cmp.is_gt() {
19936 Ordering::Greater
19937 } else {
19938 Ordering::Less
19939 }
19940 }) {
19941 Ok(i) | Err(i) => i,
19942 };
19943 for range in &ranges[start_ix..] {
19944 if range
19945 .start
19946 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19947 .is_ge()
19948 {
19949 break;
19950 }
19951
19952 let start = range.start.to_display_point(display_snapshot);
19953 let end = range.end.to_display_point(display_snapshot);
19954 results.push((start..end, color))
19955 }
19956 }
19957 results
19958 }
19959
19960 pub fn gutter_highlights_in_range(
19961 &self,
19962 search_range: Range<Anchor>,
19963 display_snapshot: &DisplaySnapshot,
19964 cx: &App,
19965 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19966 let mut results = Vec::new();
19967 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19968 let color = color_fetcher(cx);
19969 let start_ix = match ranges.binary_search_by(|probe| {
19970 let cmp = probe
19971 .end
19972 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19973 if cmp.is_gt() {
19974 Ordering::Greater
19975 } else {
19976 Ordering::Less
19977 }
19978 }) {
19979 Ok(i) | Err(i) => i,
19980 };
19981 for range in &ranges[start_ix..] {
19982 if range
19983 .start
19984 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19985 .is_ge()
19986 {
19987 break;
19988 }
19989
19990 let start = range.start.to_display_point(display_snapshot);
19991 let end = range.end.to_display_point(display_snapshot);
19992 results.push((start..end, color))
19993 }
19994 }
19995 results
19996 }
19997
19998 /// Get the text ranges corresponding to the redaction query
19999 pub fn redacted_ranges(
20000 &self,
20001 search_range: Range<Anchor>,
20002 display_snapshot: &DisplaySnapshot,
20003 cx: &App,
20004 ) -> Vec<Range<DisplayPoint>> {
20005 display_snapshot
20006 .buffer_snapshot
20007 .redacted_ranges(search_range, |file| {
20008 if let Some(file) = file {
20009 file.is_private()
20010 && EditorSettings::get(
20011 Some(SettingsLocation {
20012 worktree_id: file.worktree_id(cx),
20013 path: file.path().as_ref(),
20014 }),
20015 cx,
20016 )
20017 .redact_private_values
20018 } else {
20019 false
20020 }
20021 })
20022 .map(|range| {
20023 range.start.to_display_point(display_snapshot)
20024 ..range.end.to_display_point(display_snapshot)
20025 })
20026 .collect()
20027 }
20028
20029 pub fn highlight_text_key<T: 'static>(
20030 &mut self,
20031 key: usize,
20032 ranges: Vec<Range<Anchor>>,
20033 style: HighlightStyle,
20034 cx: &mut Context<Self>,
20035 ) {
20036 self.display_map.update(cx, |map, _| {
20037 map.highlight_text(
20038 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20039 ranges,
20040 style,
20041 );
20042 });
20043 cx.notify();
20044 }
20045
20046 pub fn highlight_text<T: 'static>(
20047 &mut self,
20048 ranges: Vec<Range<Anchor>>,
20049 style: HighlightStyle,
20050 cx: &mut Context<Self>,
20051 ) {
20052 self.display_map.update(cx, |map, _| {
20053 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20054 });
20055 cx.notify();
20056 }
20057
20058 pub(crate) fn highlight_inlays<T: 'static>(
20059 &mut self,
20060 highlights: Vec<InlayHighlight>,
20061 style: HighlightStyle,
20062 cx: &mut Context<Self>,
20063 ) {
20064 self.display_map.update(cx, |map, _| {
20065 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20066 });
20067 cx.notify();
20068 }
20069
20070 pub fn text_highlights<'a, T: 'static>(
20071 &'a self,
20072 cx: &'a App,
20073 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20074 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20075 }
20076
20077 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20078 let cleared = self
20079 .display_map
20080 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20081 if cleared {
20082 cx.notify();
20083 }
20084 }
20085
20086 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20087 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20088 && self.focus_handle.is_focused(window)
20089 }
20090
20091 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20092 self.show_cursor_when_unfocused = is_enabled;
20093 cx.notify();
20094 }
20095
20096 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20097 cx.notify();
20098 }
20099
20100 fn on_debug_session_event(
20101 &mut self,
20102 _session: Entity<Session>,
20103 event: &SessionEvent,
20104 cx: &mut Context<Self>,
20105 ) {
20106 if let SessionEvent::InvalidateInlineValue = event {
20107 self.refresh_inline_values(cx);
20108 }
20109 }
20110
20111 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20112 let Some(project) = self.project.clone() else {
20113 return;
20114 };
20115
20116 if !self.inline_value_cache.enabled {
20117 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20118 self.splice_inlays(&inlays, Vec::new(), cx);
20119 return;
20120 }
20121
20122 let current_execution_position = self
20123 .highlighted_rows
20124 .get(&TypeId::of::<ActiveDebugLine>())
20125 .and_then(|lines| lines.last().map(|line| line.range.end));
20126
20127 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20128 let inline_values = editor
20129 .update(cx, |editor, cx| {
20130 let Some(current_execution_position) = current_execution_position else {
20131 return Some(Task::ready(Ok(Vec::new())));
20132 };
20133
20134 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20135 let snapshot = buffer.snapshot(cx);
20136
20137 let excerpt = snapshot.excerpt_containing(
20138 current_execution_position..current_execution_position,
20139 )?;
20140
20141 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20142 })?;
20143
20144 let range =
20145 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20146
20147 project.inline_values(buffer, range, cx)
20148 })
20149 .ok()
20150 .flatten()?
20151 .await
20152 .context("refreshing debugger inlays")
20153 .log_err()?;
20154
20155 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20156
20157 for (buffer_id, inline_value) in inline_values
20158 .into_iter()
20159 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20160 {
20161 buffer_inline_values
20162 .entry(buffer_id)
20163 .or_default()
20164 .push(inline_value);
20165 }
20166
20167 editor
20168 .update(cx, |editor, cx| {
20169 let snapshot = editor.buffer.read(cx).snapshot(cx);
20170 let mut new_inlays = Vec::default();
20171
20172 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20173 let buffer_id = buffer_snapshot.remote_id();
20174 buffer_inline_values
20175 .get(&buffer_id)
20176 .into_iter()
20177 .flatten()
20178 .for_each(|hint| {
20179 let inlay = Inlay::debugger(
20180 post_inc(&mut editor.next_inlay_id),
20181 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20182 hint.text(),
20183 );
20184 if !inlay.text.chars().contains(&'\n') {
20185 new_inlays.push(inlay);
20186 }
20187 });
20188 }
20189
20190 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20191 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20192
20193 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20194 })
20195 .ok()?;
20196 Some(())
20197 });
20198 }
20199
20200 fn on_buffer_event(
20201 &mut self,
20202 multibuffer: &Entity<MultiBuffer>,
20203 event: &multi_buffer::Event,
20204 window: &mut Window,
20205 cx: &mut Context<Self>,
20206 ) {
20207 match event {
20208 multi_buffer::Event::Edited {
20209 singleton_buffer_edited,
20210 edited_buffer,
20211 } => {
20212 self.scrollbar_marker_state.dirty = true;
20213 self.active_indent_guides_state.dirty = true;
20214 self.refresh_active_diagnostics(cx);
20215 self.refresh_code_actions(window, cx);
20216 self.refresh_selected_text_highlights(true, window, cx);
20217 self.refresh_single_line_folds(window, cx);
20218 refresh_matching_bracket_highlights(self, window, cx);
20219 if self.has_active_edit_prediction() {
20220 self.update_visible_edit_prediction(window, cx);
20221 }
20222 if let Some(project) = self.project.as_ref()
20223 && let Some(edited_buffer) = edited_buffer
20224 {
20225 project.update(cx, |project, cx| {
20226 self.registered_buffers
20227 .entry(edited_buffer.read(cx).remote_id())
20228 .or_insert_with(|| {
20229 project.register_buffer_with_language_servers(edited_buffer, cx)
20230 });
20231 });
20232 }
20233 cx.emit(EditorEvent::BufferEdited);
20234 cx.emit(SearchEvent::MatchesInvalidated);
20235
20236 if let Some(buffer) = edited_buffer {
20237 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20238 }
20239
20240 if *singleton_buffer_edited {
20241 if let Some(buffer) = edited_buffer
20242 && buffer.read(cx).file().is_none()
20243 {
20244 cx.emit(EditorEvent::TitleChanged);
20245 }
20246 if let Some(project) = &self.project {
20247 #[allow(clippy::mutable_key_type)]
20248 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20249 multibuffer
20250 .all_buffers()
20251 .into_iter()
20252 .filter_map(|buffer| {
20253 buffer.update(cx, |buffer, cx| {
20254 let language = buffer.language()?;
20255 let should_discard = project.update(cx, |project, cx| {
20256 project.is_local()
20257 && !project.has_language_servers_for(buffer, cx)
20258 });
20259 should_discard.not().then_some(language.clone())
20260 })
20261 })
20262 .collect::<HashSet<_>>()
20263 });
20264 if !languages_affected.is_empty() {
20265 self.refresh_inlay_hints(
20266 InlayHintRefreshReason::BufferEdited(languages_affected),
20267 cx,
20268 );
20269 }
20270 }
20271 }
20272
20273 let Some(project) = &self.project else { return };
20274 let (telemetry, is_via_ssh) = {
20275 let project = project.read(cx);
20276 let telemetry = project.client().telemetry().clone();
20277 let is_via_ssh = project.is_via_remote_server();
20278 (telemetry, is_via_ssh)
20279 };
20280 refresh_linked_ranges(self, window, cx);
20281 telemetry.log_edit_event("editor", is_via_ssh);
20282 }
20283 multi_buffer::Event::ExcerptsAdded {
20284 buffer,
20285 predecessor,
20286 excerpts,
20287 } => {
20288 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20289 let buffer_id = buffer.read(cx).remote_id();
20290 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20291 && let Some(project) = &self.project
20292 {
20293 update_uncommitted_diff_for_buffer(
20294 cx.entity(),
20295 project,
20296 [buffer.clone()],
20297 self.buffer.clone(),
20298 cx,
20299 )
20300 .detach();
20301 }
20302 self.update_lsp_data(false, Some(buffer_id), window, cx);
20303 cx.emit(EditorEvent::ExcerptsAdded {
20304 buffer: buffer.clone(),
20305 predecessor: *predecessor,
20306 excerpts: excerpts.clone(),
20307 });
20308 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20309 }
20310 multi_buffer::Event::ExcerptsRemoved {
20311 ids,
20312 removed_buffer_ids,
20313 } => {
20314 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20315 let buffer = self.buffer.read(cx);
20316 self.registered_buffers
20317 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20318 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20319 cx.emit(EditorEvent::ExcerptsRemoved {
20320 ids: ids.clone(),
20321 removed_buffer_ids: removed_buffer_ids.clone(),
20322 });
20323 }
20324 multi_buffer::Event::ExcerptsEdited {
20325 excerpt_ids,
20326 buffer_ids,
20327 } => {
20328 self.display_map.update(cx, |map, cx| {
20329 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20330 });
20331 cx.emit(EditorEvent::ExcerptsEdited {
20332 ids: excerpt_ids.clone(),
20333 });
20334 }
20335 multi_buffer::Event::ExcerptsExpanded { ids } => {
20336 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20337 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20338 }
20339 multi_buffer::Event::Reparsed(buffer_id) => {
20340 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20341 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20342
20343 cx.emit(EditorEvent::Reparsed(*buffer_id));
20344 }
20345 multi_buffer::Event::DiffHunksToggled => {
20346 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20347 }
20348 multi_buffer::Event::LanguageChanged(buffer_id) => {
20349 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20350 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20351 cx.emit(EditorEvent::Reparsed(*buffer_id));
20352 cx.notify();
20353 }
20354 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20355 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20356 multi_buffer::Event::FileHandleChanged
20357 | multi_buffer::Event::Reloaded
20358 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20359 multi_buffer::Event::DiagnosticsUpdated => {
20360 self.update_diagnostics_state(window, cx);
20361 }
20362 _ => {}
20363 };
20364 }
20365
20366 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20367 if !self.diagnostics_enabled() {
20368 return;
20369 }
20370 self.refresh_active_diagnostics(cx);
20371 self.refresh_inline_diagnostics(true, window, cx);
20372 self.scrollbar_marker_state.dirty = true;
20373 cx.notify();
20374 }
20375
20376 pub fn start_temporary_diff_override(&mut self) {
20377 self.load_diff_task.take();
20378 self.temporary_diff_override = true;
20379 }
20380
20381 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20382 self.temporary_diff_override = false;
20383 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20384 self.buffer.update(cx, |buffer, cx| {
20385 buffer.set_all_diff_hunks_collapsed(cx);
20386 });
20387
20388 if let Some(project) = self.project.clone() {
20389 self.load_diff_task = Some(
20390 update_uncommitted_diff_for_buffer(
20391 cx.entity(),
20392 &project,
20393 self.buffer.read(cx).all_buffers(),
20394 self.buffer.clone(),
20395 cx,
20396 )
20397 .shared(),
20398 );
20399 }
20400 }
20401
20402 fn on_display_map_changed(
20403 &mut self,
20404 _: Entity<DisplayMap>,
20405 _: &mut Window,
20406 cx: &mut Context<Self>,
20407 ) {
20408 cx.notify();
20409 }
20410
20411 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20412 if self.diagnostics_enabled() {
20413 let new_severity = EditorSettings::get_global(cx)
20414 .diagnostics_max_severity
20415 .unwrap_or(DiagnosticSeverity::Hint);
20416 self.set_max_diagnostics_severity(new_severity, cx);
20417 }
20418 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20419 self.update_edit_prediction_settings(cx);
20420 self.refresh_edit_prediction(true, false, window, cx);
20421 self.refresh_inline_values(cx);
20422 self.refresh_inlay_hints(
20423 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20424 self.selections.newest_anchor().head(),
20425 &self.buffer.read(cx).snapshot(cx),
20426 cx,
20427 )),
20428 cx,
20429 );
20430
20431 let old_cursor_shape = self.cursor_shape;
20432 let old_show_breadcrumbs = self.show_breadcrumbs;
20433
20434 {
20435 let editor_settings = EditorSettings::get_global(cx);
20436 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20437 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20438 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20439 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20440 }
20441
20442 if old_cursor_shape != self.cursor_shape {
20443 cx.emit(EditorEvent::CursorShapeChanged);
20444 }
20445
20446 if old_show_breadcrumbs != self.show_breadcrumbs {
20447 cx.emit(EditorEvent::BreadcrumbsChanged);
20448 }
20449
20450 let project_settings = ProjectSettings::get_global(cx);
20451 self.serialize_dirty_buffers =
20452 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20453
20454 if self.mode.is_full() {
20455 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20456 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20457 if self.show_inline_diagnostics != show_inline_diagnostics {
20458 self.show_inline_diagnostics = show_inline_diagnostics;
20459 self.refresh_inline_diagnostics(false, window, cx);
20460 }
20461
20462 if self.git_blame_inline_enabled != inline_blame_enabled {
20463 self.toggle_git_blame_inline_internal(false, window, cx);
20464 }
20465
20466 let minimap_settings = EditorSettings::get_global(cx).minimap;
20467 if self.minimap_visibility != MinimapVisibility::Disabled {
20468 if self.minimap_visibility.settings_visibility()
20469 != minimap_settings.minimap_enabled()
20470 {
20471 self.set_minimap_visibility(
20472 MinimapVisibility::for_mode(self.mode(), cx),
20473 window,
20474 cx,
20475 );
20476 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20477 minimap_entity.update(cx, |minimap_editor, cx| {
20478 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20479 })
20480 }
20481 }
20482 }
20483
20484 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20485 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20486 }) {
20487 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20488 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20489 }
20490 self.refresh_colors(false, None, window, cx);
20491 }
20492
20493 cx.notify();
20494 }
20495
20496 pub fn set_searchable(&mut self, searchable: bool) {
20497 self.searchable = searchable;
20498 }
20499
20500 pub fn searchable(&self) -> bool {
20501 self.searchable
20502 }
20503
20504 fn open_proposed_changes_editor(
20505 &mut self,
20506 _: &OpenProposedChangesEditor,
20507 window: &mut Window,
20508 cx: &mut Context<Self>,
20509 ) {
20510 let Some(workspace) = self.workspace() else {
20511 cx.propagate();
20512 return;
20513 };
20514
20515 let selections = self.selections.all::<usize>(cx);
20516 let multi_buffer = self.buffer.read(cx);
20517 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20518 let mut new_selections_by_buffer = HashMap::default();
20519 for selection in selections {
20520 for (buffer, range, _) in
20521 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20522 {
20523 let mut range = range.to_point(buffer);
20524 range.start.column = 0;
20525 range.end.column = buffer.line_len(range.end.row);
20526 new_selections_by_buffer
20527 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20528 .or_insert(Vec::new())
20529 .push(range)
20530 }
20531 }
20532
20533 let proposed_changes_buffers = new_selections_by_buffer
20534 .into_iter()
20535 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20536 .collect::<Vec<_>>();
20537 let proposed_changes_editor = cx.new(|cx| {
20538 ProposedChangesEditor::new(
20539 "Proposed changes",
20540 proposed_changes_buffers,
20541 self.project.clone(),
20542 window,
20543 cx,
20544 )
20545 });
20546
20547 window.defer(cx, move |window, cx| {
20548 workspace.update(cx, |workspace, cx| {
20549 workspace.active_pane().update(cx, |pane, cx| {
20550 pane.add_item(
20551 Box::new(proposed_changes_editor),
20552 true,
20553 true,
20554 None,
20555 window,
20556 cx,
20557 );
20558 });
20559 });
20560 });
20561 }
20562
20563 pub fn open_excerpts_in_split(
20564 &mut self,
20565 _: &OpenExcerptsSplit,
20566 window: &mut Window,
20567 cx: &mut Context<Self>,
20568 ) {
20569 self.open_excerpts_common(None, true, window, cx)
20570 }
20571
20572 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20573 self.open_excerpts_common(None, false, window, cx)
20574 }
20575
20576 fn open_excerpts_common(
20577 &mut self,
20578 jump_data: Option<JumpData>,
20579 split: bool,
20580 window: &mut Window,
20581 cx: &mut Context<Self>,
20582 ) {
20583 let Some(workspace) = self.workspace() else {
20584 cx.propagate();
20585 return;
20586 };
20587
20588 if self.buffer.read(cx).is_singleton() {
20589 cx.propagate();
20590 return;
20591 }
20592
20593 let mut new_selections_by_buffer = HashMap::default();
20594 match &jump_data {
20595 Some(JumpData::MultiBufferPoint {
20596 excerpt_id,
20597 position,
20598 anchor,
20599 line_offset_from_top,
20600 }) => {
20601 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20602 if let Some(buffer) = multi_buffer_snapshot
20603 .buffer_id_for_excerpt(*excerpt_id)
20604 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20605 {
20606 let buffer_snapshot = buffer.read(cx).snapshot();
20607 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20608 language::ToPoint::to_point(anchor, &buffer_snapshot)
20609 } else {
20610 buffer_snapshot.clip_point(*position, Bias::Left)
20611 };
20612 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20613 new_selections_by_buffer.insert(
20614 buffer,
20615 (
20616 vec![jump_to_offset..jump_to_offset],
20617 Some(*line_offset_from_top),
20618 ),
20619 );
20620 }
20621 }
20622 Some(JumpData::MultiBufferRow {
20623 row,
20624 line_offset_from_top,
20625 }) => {
20626 let point = MultiBufferPoint::new(row.0, 0);
20627 if let Some((buffer, buffer_point, _)) =
20628 self.buffer.read(cx).point_to_buffer_point(point, cx)
20629 {
20630 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20631 new_selections_by_buffer
20632 .entry(buffer)
20633 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20634 .0
20635 .push(buffer_offset..buffer_offset)
20636 }
20637 }
20638 None => {
20639 let selections = self.selections.all::<usize>(cx);
20640 let multi_buffer = self.buffer.read(cx);
20641 for selection in selections {
20642 for (snapshot, range, _, anchor) in multi_buffer
20643 .snapshot(cx)
20644 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20645 {
20646 if let Some(anchor) = anchor {
20647 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20648 else {
20649 continue;
20650 };
20651 let offset = text::ToOffset::to_offset(
20652 &anchor.text_anchor,
20653 &buffer_handle.read(cx).snapshot(),
20654 );
20655 let range = offset..offset;
20656 new_selections_by_buffer
20657 .entry(buffer_handle)
20658 .or_insert((Vec::new(), None))
20659 .0
20660 .push(range)
20661 } else {
20662 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20663 else {
20664 continue;
20665 };
20666 new_selections_by_buffer
20667 .entry(buffer_handle)
20668 .or_insert((Vec::new(), None))
20669 .0
20670 .push(range)
20671 }
20672 }
20673 }
20674 }
20675 }
20676
20677 new_selections_by_buffer
20678 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20679
20680 if new_selections_by_buffer.is_empty() {
20681 return;
20682 }
20683
20684 // We defer the pane interaction because we ourselves are a workspace item
20685 // and activating a new item causes the pane to call a method on us reentrantly,
20686 // which panics if we're on the stack.
20687 window.defer(cx, move |window, cx| {
20688 workspace.update(cx, |workspace, cx| {
20689 let pane = if split {
20690 workspace.adjacent_pane(window, cx)
20691 } else {
20692 workspace.active_pane().clone()
20693 };
20694
20695 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20696 let editor = buffer
20697 .read(cx)
20698 .file()
20699 .is_none()
20700 .then(|| {
20701 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20702 // so `workspace.open_project_item` will never find them, always opening a new editor.
20703 // Instead, we try to activate the existing editor in the pane first.
20704 let (editor, pane_item_index) =
20705 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20706 let editor = item.downcast::<Editor>()?;
20707 let singleton_buffer =
20708 editor.read(cx).buffer().read(cx).as_singleton()?;
20709 if singleton_buffer == buffer {
20710 Some((editor, i))
20711 } else {
20712 None
20713 }
20714 })?;
20715 pane.update(cx, |pane, cx| {
20716 pane.activate_item(pane_item_index, true, true, window, cx)
20717 });
20718 Some(editor)
20719 })
20720 .flatten()
20721 .unwrap_or_else(|| {
20722 workspace.open_project_item::<Self>(
20723 pane.clone(),
20724 buffer,
20725 true,
20726 true,
20727 window,
20728 cx,
20729 )
20730 });
20731
20732 editor.update(cx, |editor, cx| {
20733 let autoscroll = match scroll_offset {
20734 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20735 None => Autoscroll::newest(),
20736 };
20737 let nav_history = editor.nav_history.take();
20738 editor.change_selections(
20739 SelectionEffects::scroll(autoscroll),
20740 window,
20741 cx,
20742 |s| {
20743 s.select_ranges(ranges);
20744 },
20745 );
20746 editor.nav_history = nav_history;
20747 });
20748 }
20749 })
20750 });
20751 }
20752
20753 // For now, don't allow opening excerpts in buffers that aren't backed by
20754 // regular project files.
20755 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20756 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20757 }
20758
20759 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20760 let snapshot = self.buffer.read(cx).read(cx);
20761 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20762 Some(
20763 ranges
20764 .iter()
20765 .map(move |range| {
20766 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20767 })
20768 .collect(),
20769 )
20770 }
20771
20772 fn selection_replacement_ranges(
20773 &self,
20774 range: Range<OffsetUtf16>,
20775 cx: &mut App,
20776 ) -> Vec<Range<OffsetUtf16>> {
20777 let selections = self.selections.all::<OffsetUtf16>(cx);
20778 let newest_selection = selections
20779 .iter()
20780 .max_by_key(|selection| selection.id)
20781 .unwrap();
20782 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20783 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20784 let snapshot = self.buffer.read(cx).read(cx);
20785 selections
20786 .into_iter()
20787 .map(|mut selection| {
20788 selection.start.0 =
20789 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20790 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20791 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20792 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20793 })
20794 .collect()
20795 }
20796
20797 fn report_editor_event(
20798 &self,
20799 reported_event: ReportEditorEvent,
20800 file_extension: Option<String>,
20801 cx: &App,
20802 ) {
20803 if cfg!(any(test, feature = "test-support")) {
20804 return;
20805 }
20806
20807 let Some(project) = &self.project else { return };
20808
20809 // If None, we are in a file without an extension
20810 let file = self
20811 .buffer
20812 .read(cx)
20813 .as_singleton()
20814 .and_then(|b| b.read(cx).file());
20815 let file_extension = file_extension.or(file
20816 .as_ref()
20817 .and_then(|file| Path::new(file.file_name(cx)).extension())
20818 .and_then(|e| e.to_str())
20819 .map(|a| a.to_string()));
20820
20821 let vim_mode = vim_enabled(cx);
20822
20823 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20824 let copilot_enabled = edit_predictions_provider
20825 == language::language_settings::EditPredictionProvider::Copilot;
20826 let copilot_enabled_for_language = self
20827 .buffer
20828 .read(cx)
20829 .language_settings(cx)
20830 .show_edit_predictions;
20831
20832 let project = project.read(cx);
20833 let event_type = reported_event.event_type();
20834
20835 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20836 telemetry::event!(
20837 event_type,
20838 type = if auto_saved {"autosave"} else {"manual"},
20839 file_extension,
20840 vim_mode,
20841 copilot_enabled,
20842 copilot_enabled_for_language,
20843 edit_predictions_provider,
20844 is_via_ssh = project.is_via_remote_server(),
20845 );
20846 } else {
20847 telemetry::event!(
20848 event_type,
20849 file_extension,
20850 vim_mode,
20851 copilot_enabled,
20852 copilot_enabled_for_language,
20853 edit_predictions_provider,
20854 is_via_ssh = project.is_via_remote_server(),
20855 );
20856 };
20857 }
20858
20859 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20860 /// with each line being an array of {text, highlight} objects.
20861 fn copy_highlight_json(
20862 &mut self,
20863 _: &CopyHighlightJson,
20864 window: &mut Window,
20865 cx: &mut Context<Self>,
20866 ) {
20867 #[derive(Serialize)]
20868 struct Chunk<'a> {
20869 text: String,
20870 highlight: Option<&'a str>,
20871 }
20872
20873 let snapshot = self.buffer.read(cx).snapshot(cx);
20874 let range = self
20875 .selected_text_range(false, window, cx)
20876 .and_then(|selection| {
20877 if selection.range.is_empty() {
20878 None
20879 } else {
20880 Some(selection.range)
20881 }
20882 })
20883 .unwrap_or_else(|| 0..snapshot.len());
20884
20885 let chunks = snapshot.chunks(range, true);
20886 let mut lines = Vec::new();
20887 let mut line: VecDeque<Chunk> = VecDeque::new();
20888
20889 let Some(style) = self.style.as_ref() else {
20890 return;
20891 };
20892
20893 for chunk in chunks {
20894 let highlight = chunk
20895 .syntax_highlight_id
20896 .and_then(|id| id.name(&style.syntax));
20897 let mut chunk_lines = chunk.text.split('\n').peekable();
20898 while let Some(text) = chunk_lines.next() {
20899 let mut merged_with_last_token = false;
20900 if let Some(last_token) = line.back_mut()
20901 && last_token.highlight == highlight
20902 {
20903 last_token.text.push_str(text);
20904 merged_with_last_token = true;
20905 }
20906
20907 if !merged_with_last_token {
20908 line.push_back(Chunk {
20909 text: text.into(),
20910 highlight,
20911 });
20912 }
20913
20914 if chunk_lines.peek().is_some() {
20915 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20916 line.pop_front();
20917 }
20918 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20919 line.pop_back();
20920 }
20921
20922 lines.push(mem::take(&mut line));
20923 }
20924 }
20925 }
20926
20927 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20928 return;
20929 };
20930 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20931 }
20932
20933 pub fn open_context_menu(
20934 &mut self,
20935 _: &OpenContextMenu,
20936 window: &mut Window,
20937 cx: &mut Context<Self>,
20938 ) {
20939 self.request_autoscroll(Autoscroll::newest(), cx);
20940 let position = self.selections.newest_display(cx).start;
20941 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20942 }
20943
20944 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20945 &self.inlay_hint_cache
20946 }
20947
20948 pub fn replay_insert_event(
20949 &mut self,
20950 text: &str,
20951 relative_utf16_range: Option<Range<isize>>,
20952 window: &mut Window,
20953 cx: &mut Context<Self>,
20954 ) {
20955 if !self.input_enabled {
20956 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20957 return;
20958 }
20959 if let Some(relative_utf16_range) = relative_utf16_range {
20960 let selections = self.selections.all::<OffsetUtf16>(cx);
20961 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20962 let new_ranges = selections.into_iter().map(|range| {
20963 let start = OffsetUtf16(
20964 range
20965 .head()
20966 .0
20967 .saturating_add_signed(relative_utf16_range.start),
20968 );
20969 let end = OffsetUtf16(
20970 range
20971 .head()
20972 .0
20973 .saturating_add_signed(relative_utf16_range.end),
20974 );
20975 start..end
20976 });
20977 s.select_ranges(new_ranges);
20978 });
20979 }
20980
20981 self.handle_input(text, window, cx);
20982 }
20983
20984 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20985 let Some(provider) = self.semantics_provider.as_ref() else {
20986 return false;
20987 };
20988
20989 let mut supports = false;
20990 self.buffer().update(cx, |this, cx| {
20991 this.for_each_buffer(|buffer| {
20992 supports |= provider.supports_inlay_hints(buffer, cx);
20993 });
20994 });
20995
20996 supports
20997 }
20998
20999 pub fn is_focused(&self, window: &Window) -> bool {
21000 self.focus_handle.is_focused(window)
21001 }
21002
21003 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21004 cx.emit(EditorEvent::Focused);
21005
21006 if let Some(descendant) = self
21007 .last_focused_descendant
21008 .take()
21009 .and_then(|descendant| descendant.upgrade())
21010 {
21011 window.focus(&descendant);
21012 } else {
21013 if let Some(blame) = self.blame.as_ref() {
21014 blame.update(cx, GitBlame::focus)
21015 }
21016
21017 self.blink_manager.update(cx, BlinkManager::enable);
21018 self.show_cursor_names(window, cx);
21019 self.buffer.update(cx, |buffer, cx| {
21020 buffer.finalize_last_transaction(cx);
21021 if self.leader_id.is_none() {
21022 buffer.set_active_selections(
21023 &self.selections.disjoint_anchors(),
21024 self.selections.line_mode,
21025 self.cursor_shape,
21026 cx,
21027 );
21028 }
21029 });
21030 }
21031 }
21032
21033 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21034 cx.emit(EditorEvent::FocusedIn)
21035 }
21036
21037 fn handle_focus_out(
21038 &mut self,
21039 event: FocusOutEvent,
21040 _window: &mut Window,
21041 cx: &mut Context<Self>,
21042 ) {
21043 if event.blurred != self.focus_handle {
21044 self.last_focused_descendant = Some(event.blurred);
21045 }
21046 self.selection_drag_state = SelectionDragState::None;
21047 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21048 }
21049
21050 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21051 self.blink_manager.update(cx, BlinkManager::disable);
21052 self.buffer
21053 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21054
21055 if let Some(blame) = self.blame.as_ref() {
21056 blame.update(cx, GitBlame::blur)
21057 }
21058 if !self.hover_state.focused(window, cx) {
21059 hide_hover(self, cx);
21060 }
21061 if !self
21062 .context_menu
21063 .borrow()
21064 .as_ref()
21065 .is_some_and(|context_menu| context_menu.focused(window, cx))
21066 {
21067 self.hide_context_menu(window, cx);
21068 }
21069 self.discard_edit_prediction(false, cx);
21070 cx.emit(EditorEvent::Blurred);
21071 cx.notify();
21072 }
21073
21074 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21075 let mut pending: String = window
21076 .pending_input_keystrokes()
21077 .into_iter()
21078 .flatten()
21079 .filter_map(|keystroke| {
21080 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21081 keystroke.key_char.clone()
21082 } else {
21083 None
21084 }
21085 })
21086 .collect();
21087
21088 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21089 pending = "".to_string();
21090 }
21091
21092 let existing_pending = self
21093 .text_highlights::<PendingInput>(cx)
21094 .map(|(_, ranges)| ranges.to_vec());
21095 if existing_pending.is_none() && pending.is_empty() {
21096 return;
21097 }
21098 let transaction =
21099 self.transact(window, cx, |this, window, cx| {
21100 let selections = this.selections.all::<usize>(cx);
21101 let edits = selections
21102 .iter()
21103 .map(|selection| (selection.end..selection.end, pending.clone()));
21104 this.edit(edits, cx);
21105 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21106 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21107 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21108 }));
21109 });
21110 if let Some(existing_ranges) = existing_pending {
21111 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21112 this.edit(edits, cx);
21113 }
21114 });
21115
21116 let snapshot = self.snapshot(window, cx);
21117 let ranges = self
21118 .selections
21119 .all::<usize>(cx)
21120 .into_iter()
21121 .map(|selection| {
21122 snapshot.buffer_snapshot.anchor_after(selection.end)
21123 ..snapshot
21124 .buffer_snapshot
21125 .anchor_before(selection.end + pending.len())
21126 })
21127 .collect();
21128
21129 if pending.is_empty() {
21130 self.clear_highlights::<PendingInput>(cx);
21131 } else {
21132 self.highlight_text::<PendingInput>(
21133 ranges,
21134 HighlightStyle {
21135 underline: Some(UnderlineStyle {
21136 thickness: px(1.),
21137 color: None,
21138 wavy: false,
21139 }),
21140 ..Default::default()
21141 },
21142 cx,
21143 );
21144 }
21145
21146 self.ime_transaction = self.ime_transaction.or(transaction);
21147 if let Some(transaction) = self.ime_transaction {
21148 self.buffer.update(cx, |buffer, cx| {
21149 buffer.group_until_transaction(transaction, cx);
21150 });
21151 }
21152
21153 if self.text_highlights::<PendingInput>(cx).is_none() {
21154 self.ime_transaction.take();
21155 }
21156 }
21157
21158 pub fn register_action_renderer(
21159 &mut self,
21160 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21161 ) -> Subscription {
21162 let id = self.next_editor_action_id.post_inc();
21163 self.editor_actions
21164 .borrow_mut()
21165 .insert(id, Box::new(listener));
21166
21167 let editor_actions = self.editor_actions.clone();
21168 Subscription::new(move || {
21169 editor_actions.borrow_mut().remove(&id);
21170 })
21171 }
21172
21173 pub fn register_action<A: Action>(
21174 &mut self,
21175 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21176 ) -> Subscription {
21177 let id = self.next_editor_action_id.post_inc();
21178 let listener = Arc::new(listener);
21179 self.editor_actions.borrow_mut().insert(
21180 id,
21181 Box::new(move |_, window, _| {
21182 let listener = listener.clone();
21183 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21184 let action = action.downcast_ref().unwrap();
21185 if phase == DispatchPhase::Bubble {
21186 listener(action, window, cx)
21187 }
21188 })
21189 }),
21190 );
21191
21192 let editor_actions = self.editor_actions.clone();
21193 Subscription::new(move || {
21194 editor_actions.borrow_mut().remove(&id);
21195 })
21196 }
21197
21198 pub fn file_header_size(&self) -> u32 {
21199 FILE_HEADER_HEIGHT
21200 }
21201
21202 pub fn restore(
21203 &mut self,
21204 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21205 window: &mut Window,
21206 cx: &mut Context<Self>,
21207 ) {
21208 let workspace = self.workspace();
21209 let project = self.project();
21210 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21211 let mut tasks = Vec::new();
21212 for (buffer_id, changes) in revert_changes {
21213 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21214 buffer.update(cx, |buffer, cx| {
21215 buffer.edit(
21216 changes
21217 .into_iter()
21218 .map(|(range, text)| (range, text.to_string())),
21219 None,
21220 cx,
21221 );
21222 });
21223
21224 if let Some(project) =
21225 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21226 {
21227 project.update(cx, |project, cx| {
21228 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21229 })
21230 }
21231 }
21232 }
21233 tasks
21234 });
21235 cx.spawn_in(window, async move |_, cx| {
21236 for (buffer, task) in save_tasks {
21237 let result = task.await;
21238 if result.is_err() {
21239 let Some(path) = buffer
21240 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21241 .ok()
21242 else {
21243 continue;
21244 };
21245 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21246 let Some(task) = cx
21247 .update_window_entity(workspace, |workspace, window, cx| {
21248 workspace
21249 .open_path_preview(path, None, false, false, false, window, cx)
21250 })
21251 .ok()
21252 else {
21253 continue;
21254 };
21255 task.await.log_err();
21256 }
21257 }
21258 }
21259 })
21260 .detach();
21261 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21262 selections.refresh()
21263 });
21264 }
21265
21266 pub fn to_pixel_point(
21267 &self,
21268 source: multi_buffer::Anchor,
21269 editor_snapshot: &EditorSnapshot,
21270 window: &mut Window,
21271 ) -> Option<gpui::Point<Pixels>> {
21272 let source_point = source.to_display_point(editor_snapshot);
21273 self.display_to_pixel_point(source_point, editor_snapshot, window)
21274 }
21275
21276 pub fn display_to_pixel_point(
21277 &self,
21278 source: DisplayPoint,
21279 editor_snapshot: &EditorSnapshot,
21280 window: &mut Window,
21281 ) -> Option<gpui::Point<Pixels>> {
21282 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21283 let text_layout_details = self.text_layout_details(window);
21284 let scroll_top = text_layout_details
21285 .scroll_anchor
21286 .scroll_position(editor_snapshot)
21287 .y;
21288
21289 if source.row().as_f32() < scroll_top.floor() {
21290 return None;
21291 }
21292 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21293 let source_y = line_height * (source.row().as_f32() - scroll_top);
21294 Some(gpui::Point::new(source_x, source_y))
21295 }
21296
21297 pub fn has_visible_completions_menu(&self) -> bool {
21298 !self.edit_prediction_preview_is_active()
21299 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21300 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21301 })
21302 }
21303
21304 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21305 if self.mode.is_minimap() {
21306 return;
21307 }
21308 self.addons
21309 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21310 }
21311
21312 pub fn unregister_addon<T: Addon>(&mut self) {
21313 self.addons.remove(&std::any::TypeId::of::<T>());
21314 }
21315
21316 pub fn addon<T: Addon>(&self) -> Option<&T> {
21317 let type_id = std::any::TypeId::of::<T>();
21318 self.addons
21319 .get(&type_id)
21320 .and_then(|item| item.to_any().downcast_ref::<T>())
21321 }
21322
21323 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21324 let type_id = std::any::TypeId::of::<T>();
21325 self.addons
21326 .get_mut(&type_id)
21327 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21328 }
21329
21330 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21331 let text_layout_details = self.text_layout_details(window);
21332 let style = &text_layout_details.editor_style;
21333 let font_id = window.text_system().resolve_font(&style.text.font());
21334 let font_size = style.text.font_size.to_pixels(window.rem_size());
21335 let line_height = style.text.line_height_in_pixels(window.rem_size());
21336 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21337 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21338
21339 CharacterDimensions {
21340 em_width,
21341 em_advance,
21342 line_height,
21343 }
21344 }
21345
21346 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21347 self.load_diff_task.clone()
21348 }
21349
21350 fn read_metadata_from_db(
21351 &mut self,
21352 item_id: u64,
21353 workspace_id: WorkspaceId,
21354 window: &mut Window,
21355 cx: &mut Context<Editor>,
21356 ) {
21357 if self.is_singleton(cx)
21358 && !self.mode.is_minimap()
21359 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21360 {
21361 let buffer_snapshot = OnceCell::new();
21362
21363 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21364 && !folds.is_empty()
21365 {
21366 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21367 self.fold_ranges(
21368 folds
21369 .into_iter()
21370 .map(|(start, end)| {
21371 snapshot.clip_offset(start, Bias::Left)
21372 ..snapshot.clip_offset(end, Bias::Right)
21373 })
21374 .collect(),
21375 false,
21376 window,
21377 cx,
21378 );
21379 }
21380
21381 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21382 && !selections.is_empty()
21383 {
21384 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21385 // skip adding the initial selection to selection history
21386 self.selection_history.mode = SelectionHistoryMode::Skipping;
21387 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21388 s.select_ranges(selections.into_iter().map(|(start, end)| {
21389 snapshot.clip_offset(start, Bias::Left)
21390 ..snapshot.clip_offset(end, Bias::Right)
21391 }));
21392 });
21393 self.selection_history.mode = SelectionHistoryMode::Normal;
21394 };
21395 }
21396
21397 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21398 }
21399
21400 fn update_lsp_data(
21401 &mut self,
21402 ignore_cache: bool,
21403 for_buffer: Option<BufferId>,
21404 window: &mut Window,
21405 cx: &mut Context<'_, Self>,
21406 ) {
21407 self.pull_diagnostics(for_buffer, window, cx);
21408 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21409 }
21410}
21411
21412fn vim_enabled(cx: &App) -> bool {
21413 cx.global::<SettingsStore>()
21414 .raw_user_settings()
21415 .get("vim_mode")
21416 == Some(&serde_json::Value::Bool(true))
21417}
21418
21419fn process_completion_for_edit(
21420 completion: &Completion,
21421 intent: CompletionIntent,
21422 buffer: &Entity<Buffer>,
21423 cursor_position: &text::Anchor,
21424 cx: &mut Context<Editor>,
21425) -> CompletionEdit {
21426 let buffer = buffer.read(cx);
21427 let buffer_snapshot = buffer.snapshot();
21428 let (snippet, new_text) = if completion.is_snippet() {
21429 // Workaround for typescript language server issues so that methods don't expand within
21430 // strings and functions with type expressions. The previous point is used because the query
21431 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21432 let mut snippet_source = completion.new_text.clone();
21433 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21434 previous_point.column = previous_point.column.saturating_sub(1);
21435 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21436 && scope.prefers_label_for_snippet_in_completion()
21437 && let Some(label) = completion.label()
21438 && matches!(
21439 completion.kind(),
21440 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21441 )
21442 {
21443 snippet_source = label;
21444 }
21445 match Snippet::parse(&snippet_source).log_err() {
21446 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21447 None => (None, completion.new_text.clone()),
21448 }
21449 } else {
21450 (None, completion.new_text.clone())
21451 };
21452
21453 let mut range_to_replace = {
21454 let replace_range = &completion.replace_range;
21455 if let CompletionSource::Lsp {
21456 insert_range: Some(insert_range),
21457 ..
21458 } = &completion.source
21459 {
21460 debug_assert_eq!(
21461 insert_range.start, replace_range.start,
21462 "insert_range and replace_range should start at the same position"
21463 );
21464 debug_assert!(
21465 insert_range
21466 .start
21467 .cmp(cursor_position, &buffer_snapshot)
21468 .is_le(),
21469 "insert_range should start before or at cursor position"
21470 );
21471 debug_assert!(
21472 replace_range
21473 .start
21474 .cmp(cursor_position, &buffer_snapshot)
21475 .is_le(),
21476 "replace_range should start before or at cursor position"
21477 );
21478
21479 let should_replace = match intent {
21480 CompletionIntent::CompleteWithInsert => false,
21481 CompletionIntent::CompleteWithReplace => true,
21482 CompletionIntent::Complete | CompletionIntent::Compose => {
21483 let insert_mode =
21484 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21485 .completions
21486 .lsp_insert_mode;
21487 match insert_mode {
21488 LspInsertMode::Insert => false,
21489 LspInsertMode::Replace => true,
21490 LspInsertMode::ReplaceSubsequence => {
21491 let mut text_to_replace = buffer.chars_for_range(
21492 buffer.anchor_before(replace_range.start)
21493 ..buffer.anchor_after(replace_range.end),
21494 );
21495 let mut current_needle = text_to_replace.next();
21496 for haystack_ch in completion.label.text.chars() {
21497 if let Some(needle_ch) = current_needle
21498 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21499 {
21500 current_needle = text_to_replace.next();
21501 }
21502 }
21503 current_needle.is_none()
21504 }
21505 LspInsertMode::ReplaceSuffix => {
21506 if replace_range
21507 .end
21508 .cmp(cursor_position, &buffer_snapshot)
21509 .is_gt()
21510 {
21511 let range_after_cursor = *cursor_position..replace_range.end;
21512 let text_after_cursor = buffer
21513 .text_for_range(
21514 buffer.anchor_before(range_after_cursor.start)
21515 ..buffer.anchor_after(range_after_cursor.end),
21516 )
21517 .collect::<String>()
21518 .to_ascii_lowercase();
21519 completion
21520 .label
21521 .text
21522 .to_ascii_lowercase()
21523 .ends_with(&text_after_cursor)
21524 } else {
21525 true
21526 }
21527 }
21528 }
21529 }
21530 };
21531
21532 if should_replace {
21533 replace_range.clone()
21534 } else {
21535 insert_range.clone()
21536 }
21537 } else {
21538 replace_range.clone()
21539 }
21540 };
21541
21542 if range_to_replace
21543 .end
21544 .cmp(cursor_position, &buffer_snapshot)
21545 .is_lt()
21546 {
21547 range_to_replace.end = *cursor_position;
21548 }
21549
21550 CompletionEdit {
21551 new_text,
21552 replace_range: range_to_replace.to_offset(buffer),
21553 snippet,
21554 }
21555}
21556
21557struct CompletionEdit {
21558 new_text: String,
21559 replace_range: Range<usize>,
21560 snippet: Option<Snippet>,
21561}
21562
21563fn insert_extra_newline_brackets(
21564 buffer: &MultiBufferSnapshot,
21565 range: Range<usize>,
21566 language: &language::LanguageScope,
21567) -> bool {
21568 let leading_whitespace_len = buffer
21569 .reversed_chars_at(range.start)
21570 .take_while(|c| c.is_whitespace() && *c != '\n')
21571 .map(|c| c.len_utf8())
21572 .sum::<usize>();
21573 let trailing_whitespace_len = buffer
21574 .chars_at(range.end)
21575 .take_while(|c| c.is_whitespace() && *c != '\n')
21576 .map(|c| c.len_utf8())
21577 .sum::<usize>();
21578 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21579
21580 language.brackets().any(|(pair, enabled)| {
21581 let pair_start = pair.start.trim_end();
21582 let pair_end = pair.end.trim_start();
21583
21584 enabled
21585 && pair.newline
21586 && buffer.contains_str_at(range.end, pair_end)
21587 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21588 })
21589}
21590
21591fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21592 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21593 [(buffer, range, _)] => (*buffer, range.clone()),
21594 _ => return false,
21595 };
21596 let pair = {
21597 let mut result: Option<BracketMatch> = None;
21598
21599 for pair in buffer
21600 .all_bracket_ranges(range.clone())
21601 .filter(move |pair| {
21602 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21603 })
21604 {
21605 let len = pair.close_range.end - pair.open_range.start;
21606
21607 if let Some(existing) = &result {
21608 let existing_len = existing.close_range.end - existing.open_range.start;
21609 if len > existing_len {
21610 continue;
21611 }
21612 }
21613
21614 result = Some(pair);
21615 }
21616
21617 result
21618 };
21619 let Some(pair) = pair else {
21620 return false;
21621 };
21622 pair.newline_only
21623 && buffer
21624 .chars_for_range(pair.open_range.end..range.start)
21625 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21626 .all(|c| c.is_whitespace() && c != '\n')
21627}
21628
21629fn update_uncommitted_diff_for_buffer(
21630 editor: Entity<Editor>,
21631 project: &Entity<Project>,
21632 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21633 buffer: Entity<MultiBuffer>,
21634 cx: &mut App,
21635) -> Task<()> {
21636 let mut tasks = Vec::new();
21637 project.update(cx, |project, cx| {
21638 for buffer in buffers {
21639 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21640 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21641 }
21642 }
21643 });
21644 cx.spawn(async move |cx| {
21645 let diffs = future::join_all(tasks).await;
21646 if editor
21647 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21648 .unwrap_or(false)
21649 {
21650 return;
21651 }
21652
21653 buffer
21654 .update(cx, |buffer, cx| {
21655 for diff in diffs.into_iter().flatten() {
21656 buffer.add_diff(diff, cx);
21657 }
21658 })
21659 .ok();
21660 })
21661}
21662
21663fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21664 let tab_size = tab_size.get() as usize;
21665 let mut width = offset;
21666
21667 for ch in text.chars() {
21668 width += if ch == '\t' {
21669 tab_size - (width % tab_size)
21670 } else {
21671 1
21672 };
21673 }
21674
21675 width - offset
21676}
21677
21678#[cfg(test)]
21679mod tests {
21680 use super::*;
21681
21682 #[test]
21683 fn test_string_size_with_expanded_tabs() {
21684 let nz = |val| NonZeroU32::new(val).unwrap();
21685 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21686 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21687 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21688 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21689 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21690 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21691 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21692 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21693 }
21694}
21695
21696/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21697struct WordBreakingTokenizer<'a> {
21698 input: &'a str,
21699}
21700
21701impl<'a> WordBreakingTokenizer<'a> {
21702 fn new(input: &'a str) -> Self {
21703 Self { input }
21704 }
21705}
21706
21707fn is_char_ideographic(ch: char) -> bool {
21708 use unicode_script::Script::*;
21709 use unicode_script::UnicodeScript;
21710 matches!(ch.script(), Han | Tangut | Yi)
21711}
21712
21713fn is_grapheme_ideographic(text: &str) -> bool {
21714 text.chars().any(is_char_ideographic)
21715}
21716
21717fn is_grapheme_whitespace(text: &str) -> bool {
21718 text.chars().any(|x| x.is_whitespace())
21719}
21720
21721fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21722 text.chars()
21723 .next()
21724 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21725}
21726
21727#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21728enum WordBreakToken<'a> {
21729 Word { token: &'a str, grapheme_len: usize },
21730 InlineWhitespace { token: &'a str, grapheme_len: usize },
21731 Newline,
21732}
21733
21734impl<'a> Iterator for WordBreakingTokenizer<'a> {
21735 /// Yields a span, the count of graphemes in the token, and whether it was
21736 /// whitespace. Note that it also breaks at word boundaries.
21737 type Item = WordBreakToken<'a>;
21738
21739 fn next(&mut self) -> Option<Self::Item> {
21740 use unicode_segmentation::UnicodeSegmentation;
21741 if self.input.is_empty() {
21742 return None;
21743 }
21744
21745 let mut iter = self.input.graphemes(true).peekable();
21746 let mut offset = 0;
21747 let mut grapheme_len = 0;
21748 if let Some(first_grapheme) = iter.next() {
21749 let is_newline = first_grapheme == "\n";
21750 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21751 offset += first_grapheme.len();
21752 grapheme_len += 1;
21753 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21754 if let Some(grapheme) = iter.peek().copied()
21755 && should_stay_with_preceding_ideograph(grapheme)
21756 {
21757 offset += grapheme.len();
21758 grapheme_len += 1;
21759 }
21760 } else {
21761 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21762 let mut next_word_bound = words.peek().copied();
21763 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21764 next_word_bound = words.next();
21765 }
21766 while let Some(grapheme) = iter.peek().copied() {
21767 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21768 break;
21769 };
21770 if is_grapheme_whitespace(grapheme) != is_whitespace
21771 || (grapheme == "\n") != is_newline
21772 {
21773 break;
21774 };
21775 offset += grapheme.len();
21776 grapheme_len += 1;
21777 iter.next();
21778 }
21779 }
21780 let token = &self.input[..offset];
21781 self.input = &self.input[offset..];
21782 if token == "\n" {
21783 Some(WordBreakToken::Newline)
21784 } else if is_whitespace {
21785 Some(WordBreakToken::InlineWhitespace {
21786 token,
21787 grapheme_len,
21788 })
21789 } else {
21790 Some(WordBreakToken::Word {
21791 token,
21792 grapheme_len,
21793 })
21794 }
21795 } else {
21796 None
21797 }
21798 }
21799}
21800
21801#[test]
21802fn test_word_breaking_tokenizer() {
21803 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21804 ("", &[]),
21805 (" ", &[whitespace(" ", 2)]),
21806 ("Ʒ", &[word("Ʒ", 1)]),
21807 ("Ǽ", &[word("Ǽ", 1)]),
21808 ("⋑", &[word("⋑", 1)]),
21809 ("⋑⋑", &[word("⋑⋑", 2)]),
21810 (
21811 "原理,进而",
21812 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21813 ),
21814 (
21815 "hello world",
21816 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21817 ),
21818 (
21819 "hello, world",
21820 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21821 ),
21822 (
21823 " hello world",
21824 &[
21825 whitespace(" ", 2),
21826 word("hello", 5),
21827 whitespace(" ", 1),
21828 word("world", 5),
21829 ],
21830 ),
21831 (
21832 "这是什么 \n 钢笔",
21833 &[
21834 word("这", 1),
21835 word("是", 1),
21836 word("什", 1),
21837 word("么", 1),
21838 whitespace(" ", 1),
21839 newline(),
21840 whitespace(" ", 1),
21841 word("钢", 1),
21842 word("笔", 1),
21843 ],
21844 ),
21845 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21846 ];
21847
21848 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21849 WordBreakToken::Word {
21850 token,
21851 grapheme_len,
21852 }
21853 }
21854
21855 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21856 WordBreakToken::InlineWhitespace {
21857 token,
21858 grapheme_len,
21859 }
21860 }
21861
21862 fn newline() -> WordBreakToken<'static> {
21863 WordBreakToken::Newline
21864 }
21865
21866 for (input, result) in tests {
21867 assert_eq!(
21868 WordBreakingTokenizer::new(input)
21869 .collect::<Vec<_>>()
21870 .as_slice(),
21871 *result,
21872 );
21873 }
21874}
21875
21876fn wrap_with_prefix(
21877 first_line_prefix: String,
21878 subsequent_lines_prefix: String,
21879 unwrapped_text: String,
21880 wrap_column: usize,
21881 tab_size: NonZeroU32,
21882 preserve_existing_whitespace: bool,
21883) -> String {
21884 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21885 let subsequent_lines_prefix_len =
21886 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21887 let mut wrapped_text = String::new();
21888 let mut current_line = first_line_prefix;
21889 let mut is_first_line = true;
21890
21891 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21892 let mut current_line_len = first_line_prefix_len;
21893 let mut in_whitespace = false;
21894 for token in tokenizer {
21895 let have_preceding_whitespace = in_whitespace;
21896 match token {
21897 WordBreakToken::Word {
21898 token,
21899 grapheme_len,
21900 } => {
21901 in_whitespace = false;
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 && current_line_len != current_prefix_len
21909 {
21910 wrapped_text.push_str(current_line.trim_end());
21911 wrapped_text.push('\n');
21912 is_first_line = false;
21913 current_line = subsequent_lines_prefix.clone();
21914 current_line_len = subsequent_lines_prefix_len;
21915 }
21916 current_line.push_str(token);
21917 current_line_len += grapheme_len;
21918 }
21919 WordBreakToken::InlineWhitespace {
21920 mut token,
21921 mut grapheme_len,
21922 } => {
21923 in_whitespace = true;
21924 if have_preceding_whitespace && !preserve_existing_whitespace {
21925 continue;
21926 }
21927 if !preserve_existing_whitespace {
21928 token = " ";
21929 grapheme_len = 1;
21930 }
21931 let current_prefix_len = if is_first_line {
21932 first_line_prefix_len
21933 } else {
21934 subsequent_lines_prefix_len
21935 };
21936 if current_line_len + grapheme_len > wrap_column {
21937 wrapped_text.push_str(current_line.trim_end());
21938 wrapped_text.push('\n');
21939 is_first_line = false;
21940 current_line = subsequent_lines_prefix.clone();
21941 current_line_len = subsequent_lines_prefix_len;
21942 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21943 current_line.push_str(token);
21944 current_line_len += grapheme_len;
21945 }
21946 }
21947 WordBreakToken::Newline => {
21948 in_whitespace = true;
21949 let current_prefix_len = if is_first_line {
21950 first_line_prefix_len
21951 } else {
21952 subsequent_lines_prefix_len
21953 };
21954 if preserve_existing_whitespace {
21955 wrapped_text.push_str(current_line.trim_end());
21956 wrapped_text.push('\n');
21957 is_first_line = false;
21958 current_line = subsequent_lines_prefix.clone();
21959 current_line_len = subsequent_lines_prefix_len;
21960 } else if have_preceding_whitespace {
21961 continue;
21962 } else if current_line_len + 1 > wrap_column
21963 && current_line_len != current_prefix_len
21964 {
21965 wrapped_text.push_str(current_line.trim_end());
21966 wrapped_text.push('\n');
21967 is_first_line = false;
21968 current_line = subsequent_lines_prefix.clone();
21969 current_line_len = subsequent_lines_prefix_len;
21970 } else if current_line_len != current_prefix_len {
21971 current_line.push(' ');
21972 current_line_len += 1;
21973 }
21974 }
21975 }
21976 }
21977
21978 if !current_line.is_empty() {
21979 wrapped_text.push_str(¤t_line);
21980 }
21981 wrapped_text
21982}
21983
21984#[test]
21985fn test_wrap_with_prefix() {
21986 assert_eq!(
21987 wrap_with_prefix(
21988 "# ".to_string(),
21989 "# ".to_string(),
21990 "abcdefg".to_string(),
21991 4,
21992 NonZeroU32::new(4).unwrap(),
21993 false,
21994 ),
21995 "# abcdefg"
21996 );
21997 assert_eq!(
21998 wrap_with_prefix(
21999 "".to_string(),
22000 "".to_string(),
22001 "\thello world".to_string(),
22002 8,
22003 NonZeroU32::new(4).unwrap(),
22004 false,
22005 ),
22006 "hello\nworld"
22007 );
22008 assert_eq!(
22009 wrap_with_prefix(
22010 "// ".to_string(),
22011 "// ".to_string(),
22012 "xx \nyy zz aa bb cc".to_string(),
22013 12,
22014 NonZeroU32::new(4).unwrap(),
22015 false,
22016 ),
22017 "// xx yy zz\n// aa bb cc"
22018 );
22019 assert_eq!(
22020 wrap_with_prefix(
22021 String::new(),
22022 String::new(),
22023 "这是什么 \n 钢笔".to_string(),
22024 3,
22025 NonZeroU32::new(4).unwrap(),
22026 false,
22027 ),
22028 "这是什\n么 钢\n笔"
22029 );
22030}
22031
22032pub trait CollaborationHub {
22033 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22034 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22035 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22036}
22037
22038impl CollaborationHub for Entity<Project> {
22039 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22040 self.read(cx).collaborators()
22041 }
22042
22043 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22044 self.read(cx).user_store().read(cx).participant_indices()
22045 }
22046
22047 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22048 let this = self.read(cx);
22049 let user_ids = this.collaborators().values().map(|c| c.user_id);
22050 this.user_store().read(cx).participant_names(user_ids, cx)
22051 }
22052}
22053
22054pub trait SemanticsProvider {
22055 fn hover(
22056 &self,
22057 buffer: &Entity<Buffer>,
22058 position: text::Anchor,
22059 cx: &mut App,
22060 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22061
22062 fn inline_values(
22063 &self,
22064 buffer_handle: Entity<Buffer>,
22065 range: Range<text::Anchor>,
22066 cx: &mut App,
22067 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22068
22069 fn inlay_hints(
22070 &self,
22071 buffer_handle: Entity<Buffer>,
22072 range: Range<text::Anchor>,
22073 cx: &mut App,
22074 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22075
22076 fn resolve_inlay_hint(
22077 &self,
22078 hint: InlayHint,
22079 buffer_handle: Entity<Buffer>,
22080 server_id: LanguageServerId,
22081 cx: &mut App,
22082 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22083
22084 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22085
22086 fn document_highlights(
22087 &self,
22088 buffer: &Entity<Buffer>,
22089 position: text::Anchor,
22090 cx: &mut App,
22091 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22092
22093 fn definitions(
22094 &self,
22095 buffer: &Entity<Buffer>,
22096 position: text::Anchor,
22097 kind: GotoDefinitionKind,
22098 cx: &mut App,
22099 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22100
22101 fn range_for_rename(
22102 &self,
22103 buffer: &Entity<Buffer>,
22104 position: text::Anchor,
22105 cx: &mut App,
22106 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22107
22108 fn perform_rename(
22109 &self,
22110 buffer: &Entity<Buffer>,
22111 position: text::Anchor,
22112 new_name: String,
22113 cx: &mut App,
22114 ) -> Option<Task<Result<ProjectTransaction>>>;
22115}
22116
22117pub trait CompletionProvider {
22118 fn completions(
22119 &self,
22120 excerpt_id: ExcerptId,
22121 buffer: &Entity<Buffer>,
22122 buffer_position: text::Anchor,
22123 trigger: CompletionContext,
22124 window: &mut Window,
22125 cx: &mut Context<Editor>,
22126 ) -> Task<Result<Vec<CompletionResponse>>>;
22127
22128 fn resolve_completions(
22129 &self,
22130 _buffer: Entity<Buffer>,
22131 _completion_indices: Vec<usize>,
22132 _completions: Rc<RefCell<Box<[Completion]>>>,
22133 _cx: &mut Context<Editor>,
22134 ) -> Task<Result<bool>> {
22135 Task::ready(Ok(false))
22136 }
22137
22138 fn apply_additional_edits_for_completion(
22139 &self,
22140 _buffer: Entity<Buffer>,
22141 _completions: Rc<RefCell<Box<[Completion]>>>,
22142 _completion_index: usize,
22143 _push_to_history: bool,
22144 _cx: &mut Context<Editor>,
22145 ) -> Task<Result<Option<language::Transaction>>> {
22146 Task::ready(Ok(None))
22147 }
22148
22149 fn is_completion_trigger(
22150 &self,
22151 buffer: &Entity<Buffer>,
22152 position: language::Anchor,
22153 text: &str,
22154 trigger_in_words: bool,
22155 menu_is_open: bool,
22156 cx: &mut Context<Editor>,
22157 ) -> bool;
22158
22159 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22160
22161 fn sort_completions(&self) -> bool {
22162 true
22163 }
22164
22165 fn filter_completions(&self) -> bool {
22166 true
22167 }
22168}
22169
22170pub trait CodeActionProvider {
22171 fn id(&self) -> Arc<str>;
22172
22173 fn code_actions(
22174 &self,
22175 buffer: &Entity<Buffer>,
22176 range: Range<text::Anchor>,
22177 window: &mut Window,
22178 cx: &mut App,
22179 ) -> Task<Result<Vec<CodeAction>>>;
22180
22181 fn apply_code_action(
22182 &self,
22183 buffer_handle: Entity<Buffer>,
22184 action: CodeAction,
22185 excerpt_id: ExcerptId,
22186 push_to_history: bool,
22187 window: &mut Window,
22188 cx: &mut App,
22189 ) -> Task<Result<ProjectTransaction>>;
22190}
22191
22192impl CodeActionProvider for Entity<Project> {
22193 fn id(&self) -> Arc<str> {
22194 "project".into()
22195 }
22196
22197 fn code_actions(
22198 &self,
22199 buffer: &Entity<Buffer>,
22200 range: Range<text::Anchor>,
22201 _window: &mut Window,
22202 cx: &mut App,
22203 ) -> Task<Result<Vec<CodeAction>>> {
22204 self.update(cx, |project, cx| {
22205 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22206 let code_actions = project.code_actions(buffer, range, None, cx);
22207 cx.background_spawn(async move {
22208 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22209 Ok(code_lens_actions
22210 .context("code lens fetch")?
22211 .into_iter()
22212 .flatten()
22213 .chain(
22214 code_actions
22215 .context("code action fetch")?
22216 .into_iter()
22217 .flatten(),
22218 )
22219 .collect())
22220 })
22221 })
22222 }
22223
22224 fn apply_code_action(
22225 &self,
22226 buffer_handle: Entity<Buffer>,
22227 action: CodeAction,
22228 _excerpt_id: ExcerptId,
22229 push_to_history: bool,
22230 _window: &mut Window,
22231 cx: &mut App,
22232 ) -> Task<Result<ProjectTransaction>> {
22233 self.update(cx, |project, cx| {
22234 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22235 })
22236 }
22237}
22238
22239fn snippet_completions(
22240 project: &Project,
22241 buffer: &Entity<Buffer>,
22242 buffer_position: text::Anchor,
22243 cx: &mut App,
22244) -> Task<Result<CompletionResponse>> {
22245 let languages = buffer.read(cx).languages_at(buffer_position);
22246 let snippet_store = project.snippets().read(cx);
22247
22248 let scopes: Vec<_> = languages
22249 .iter()
22250 .filter_map(|language| {
22251 let language_name = language.lsp_id();
22252 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22253
22254 if snippets.is_empty() {
22255 None
22256 } else {
22257 Some((language.default_scope(), snippets))
22258 }
22259 })
22260 .collect();
22261
22262 if scopes.is_empty() {
22263 return Task::ready(Ok(CompletionResponse {
22264 completions: vec![],
22265 display_options: CompletionDisplayOptions::default(),
22266 is_incomplete: false,
22267 }));
22268 }
22269
22270 let snapshot = buffer.read(cx).text_snapshot();
22271 let chars: String = snapshot
22272 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22273 .collect();
22274 let executor = cx.background_executor().clone();
22275
22276 cx.background_spawn(async move {
22277 let mut is_incomplete = false;
22278 let mut completions: Vec<Completion> = Vec::new();
22279 for (scope, snippets) in scopes.into_iter() {
22280 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22281 let mut last_word = chars
22282 .chars()
22283 .take_while(|c| classifier.is_word(*c))
22284 .collect::<String>();
22285 last_word = last_word.chars().rev().collect();
22286
22287 if last_word.is_empty() {
22288 return Ok(CompletionResponse {
22289 completions: vec![],
22290 display_options: CompletionDisplayOptions::default(),
22291 is_incomplete: true,
22292 });
22293 }
22294
22295 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22296 let to_lsp = |point: &text::Anchor| {
22297 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22298 point_to_lsp(end)
22299 };
22300 let lsp_end = to_lsp(&buffer_position);
22301
22302 let candidates = snippets
22303 .iter()
22304 .enumerate()
22305 .flat_map(|(ix, snippet)| {
22306 snippet
22307 .prefix
22308 .iter()
22309 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22310 })
22311 .collect::<Vec<StringMatchCandidate>>();
22312
22313 const MAX_RESULTS: usize = 100;
22314 let mut matches = fuzzy::match_strings(
22315 &candidates,
22316 &last_word,
22317 last_word.chars().any(|c| c.is_uppercase()),
22318 true,
22319 MAX_RESULTS,
22320 &Default::default(),
22321 executor.clone(),
22322 )
22323 .await;
22324
22325 if matches.len() >= MAX_RESULTS {
22326 is_incomplete = true;
22327 }
22328
22329 // Remove all candidates where the query's start does not match the start of any word in the candidate
22330 if let Some(query_start) = last_word.chars().next() {
22331 matches.retain(|string_match| {
22332 split_words(&string_match.string).any(|word| {
22333 // Check that the first codepoint of the word as lowercase matches the first
22334 // codepoint of the query as lowercase
22335 word.chars()
22336 .flat_map(|codepoint| codepoint.to_lowercase())
22337 .zip(query_start.to_lowercase())
22338 .all(|(word_cp, query_cp)| word_cp == query_cp)
22339 })
22340 });
22341 }
22342
22343 let matched_strings = matches
22344 .into_iter()
22345 .map(|m| m.string)
22346 .collect::<HashSet<_>>();
22347
22348 completions.extend(snippets.iter().filter_map(|snippet| {
22349 let matching_prefix = snippet
22350 .prefix
22351 .iter()
22352 .find(|prefix| matched_strings.contains(*prefix))?;
22353 let start = as_offset - last_word.len();
22354 let start = snapshot.anchor_before(start);
22355 let range = start..buffer_position;
22356 let lsp_start = to_lsp(&start);
22357 let lsp_range = lsp::Range {
22358 start: lsp_start,
22359 end: lsp_end,
22360 };
22361 Some(Completion {
22362 replace_range: range,
22363 new_text: snippet.body.clone(),
22364 source: CompletionSource::Lsp {
22365 insert_range: None,
22366 server_id: LanguageServerId(usize::MAX),
22367 resolved: true,
22368 lsp_completion: Box::new(lsp::CompletionItem {
22369 label: snippet.prefix.first().unwrap().clone(),
22370 kind: Some(CompletionItemKind::SNIPPET),
22371 label_details: snippet.description.as_ref().map(|description| {
22372 lsp::CompletionItemLabelDetails {
22373 detail: Some(description.clone()),
22374 description: None,
22375 }
22376 }),
22377 insert_text_format: Some(InsertTextFormat::SNIPPET),
22378 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22379 lsp::InsertReplaceEdit {
22380 new_text: snippet.body.clone(),
22381 insert: lsp_range,
22382 replace: lsp_range,
22383 },
22384 )),
22385 filter_text: Some(snippet.body.clone()),
22386 sort_text: Some(char::MAX.to_string()),
22387 ..lsp::CompletionItem::default()
22388 }),
22389 lsp_defaults: None,
22390 },
22391 label: CodeLabel {
22392 text: matching_prefix.clone(),
22393 runs: Vec::new(),
22394 filter_range: 0..matching_prefix.len(),
22395 },
22396 icon_path: None,
22397 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22398 single_line: snippet.name.clone().into(),
22399 plain_text: snippet
22400 .description
22401 .clone()
22402 .map(|description| description.into()),
22403 }),
22404 insert_text_mode: None,
22405 confirm: None,
22406 })
22407 }))
22408 }
22409
22410 Ok(CompletionResponse {
22411 completions,
22412 display_options: CompletionDisplayOptions::default(),
22413 is_incomplete,
22414 })
22415 })
22416}
22417
22418impl CompletionProvider for Entity<Project> {
22419 fn completions(
22420 &self,
22421 _excerpt_id: ExcerptId,
22422 buffer: &Entity<Buffer>,
22423 buffer_position: text::Anchor,
22424 options: CompletionContext,
22425 _window: &mut Window,
22426 cx: &mut Context<Editor>,
22427 ) -> Task<Result<Vec<CompletionResponse>>> {
22428 self.update(cx, |project, cx| {
22429 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22430 let project_completions = project.completions(buffer, buffer_position, options, cx);
22431 cx.background_spawn(async move {
22432 let mut responses = project_completions.await?;
22433 let snippets = snippets.await?;
22434 if !snippets.completions.is_empty() {
22435 responses.push(snippets);
22436 }
22437 Ok(responses)
22438 })
22439 })
22440 }
22441
22442 fn resolve_completions(
22443 &self,
22444 buffer: Entity<Buffer>,
22445 completion_indices: Vec<usize>,
22446 completions: Rc<RefCell<Box<[Completion]>>>,
22447 cx: &mut Context<Editor>,
22448 ) -> Task<Result<bool>> {
22449 self.update(cx, |project, cx| {
22450 project.lsp_store().update(cx, |lsp_store, cx| {
22451 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22452 })
22453 })
22454 }
22455
22456 fn apply_additional_edits_for_completion(
22457 &self,
22458 buffer: Entity<Buffer>,
22459 completions: Rc<RefCell<Box<[Completion]>>>,
22460 completion_index: usize,
22461 push_to_history: bool,
22462 cx: &mut Context<Editor>,
22463 ) -> Task<Result<Option<language::Transaction>>> {
22464 self.update(cx, |project, cx| {
22465 project.lsp_store().update(cx, |lsp_store, cx| {
22466 lsp_store.apply_additional_edits_for_completion(
22467 buffer,
22468 completions,
22469 completion_index,
22470 push_to_history,
22471 cx,
22472 )
22473 })
22474 })
22475 }
22476
22477 fn is_completion_trigger(
22478 &self,
22479 buffer: &Entity<Buffer>,
22480 position: language::Anchor,
22481 text: &str,
22482 trigger_in_words: bool,
22483 menu_is_open: bool,
22484 cx: &mut Context<Editor>,
22485 ) -> bool {
22486 let mut chars = text.chars();
22487 let char = if let Some(char) = chars.next() {
22488 char
22489 } else {
22490 return false;
22491 };
22492 if chars.next().is_some() {
22493 return false;
22494 }
22495
22496 let buffer = buffer.read(cx);
22497 let snapshot = buffer.snapshot();
22498 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22499 return false;
22500 }
22501 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22502 if trigger_in_words && classifier.is_word(char) {
22503 return true;
22504 }
22505
22506 buffer.completion_triggers().contains(text)
22507 }
22508}
22509
22510impl SemanticsProvider for Entity<Project> {
22511 fn hover(
22512 &self,
22513 buffer: &Entity<Buffer>,
22514 position: text::Anchor,
22515 cx: &mut App,
22516 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22517 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22518 }
22519
22520 fn document_highlights(
22521 &self,
22522 buffer: &Entity<Buffer>,
22523 position: text::Anchor,
22524 cx: &mut App,
22525 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22526 Some(self.update(cx, |project, cx| {
22527 project.document_highlights(buffer, position, cx)
22528 }))
22529 }
22530
22531 fn definitions(
22532 &self,
22533 buffer: &Entity<Buffer>,
22534 position: text::Anchor,
22535 kind: GotoDefinitionKind,
22536 cx: &mut App,
22537 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22538 Some(self.update(cx, |project, cx| match kind {
22539 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22540 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22541 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22542 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22543 }))
22544 }
22545
22546 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22547 self.update(cx, |project, cx| {
22548 if project
22549 .active_debug_session(cx)
22550 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22551 {
22552 return true;
22553 }
22554
22555 buffer.update(cx, |buffer, cx| {
22556 project.any_language_server_supports_inlay_hints(buffer, cx)
22557 })
22558 })
22559 }
22560
22561 fn inline_values(
22562 &self,
22563 buffer_handle: Entity<Buffer>,
22564 range: Range<text::Anchor>,
22565 cx: &mut App,
22566 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22567 self.update(cx, |project, cx| {
22568 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22569
22570 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22571 })
22572 }
22573
22574 fn inlay_hints(
22575 &self,
22576 buffer_handle: Entity<Buffer>,
22577 range: Range<text::Anchor>,
22578 cx: &mut App,
22579 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22580 Some(self.update(cx, |project, cx| {
22581 project.inlay_hints(buffer_handle, range, cx)
22582 }))
22583 }
22584
22585 fn resolve_inlay_hint(
22586 &self,
22587 hint: InlayHint,
22588 buffer_handle: Entity<Buffer>,
22589 server_id: LanguageServerId,
22590 cx: &mut App,
22591 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22592 Some(self.update(cx, |project, cx| {
22593 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22594 }))
22595 }
22596
22597 fn range_for_rename(
22598 &self,
22599 buffer: &Entity<Buffer>,
22600 position: text::Anchor,
22601 cx: &mut App,
22602 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22603 Some(self.update(cx, |project, cx| {
22604 let buffer = buffer.clone();
22605 let task = project.prepare_rename(buffer.clone(), position, cx);
22606 cx.spawn(async move |_, cx| {
22607 Ok(match task.await? {
22608 PrepareRenameResponse::Success(range) => Some(range),
22609 PrepareRenameResponse::InvalidPosition => None,
22610 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22611 // Fallback on using TreeSitter info to determine identifier range
22612 buffer.read_with(cx, |buffer, _| {
22613 let snapshot = buffer.snapshot();
22614 let (range, kind) = snapshot.surrounding_word(position, false);
22615 if kind != Some(CharKind::Word) {
22616 return None;
22617 }
22618 Some(
22619 snapshot.anchor_before(range.start)
22620 ..snapshot.anchor_after(range.end),
22621 )
22622 })?
22623 }
22624 })
22625 })
22626 }))
22627 }
22628
22629 fn perform_rename(
22630 &self,
22631 buffer: &Entity<Buffer>,
22632 position: text::Anchor,
22633 new_name: String,
22634 cx: &mut App,
22635 ) -> Option<Task<Result<ProjectTransaction>>> {
22636 Some(self.update(cx, |project, cx| {
22637 project.perform_rename(buffer.clone(), position, new_name, cx)
22638 }))
22639 }
22640}
22641
22642fn inlay_hint_settings(
22643 location: Anchor,
22644 snapshot: &MultiBufferSnapshot,
22645 cx: &mut Context<Editor>,
22646) -> InlayHintSettings {
22647 let file = snapshot.file_at(location);
22648 let language = snapshot.language_at(location).map(|l| l.name());
22649 language_settings(language, file, cx).inlay_hints
22650}
22651
22652fn consume_contiguous_rows(
22653 contiguous_row_selections: &mut Vec<Selection<Point>>,
22654 selection: &Selection<Point>,
22655 display_map: &DisplaySnapshot,
22656 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22657) -> (MultiBufferRow, MultiBufferRow) {
22658 contiguous_row_selections.push(selection.clone());
22659 let start_row = starting_row(selection, display_map);
22660 let mut end_row = ending_row(selection, display_map);
22661
22662 while let Some(next_selection) = selections.peek() {
22663 if next_selection.start.row <= end_row.0 {
22664 end_row = ending_row(next_selection, display_map);
22665 contiguous_row_selections.push(selections.next().unwrap().clone());
22666 } else {
22667 break;
22668 }
22669 }
22670 (start_row, end_row)
22671}
22672
22673fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22674 if selection.start.column > 0 {
22675 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22676 } else {
22677 MultiBufferRow(selection.start.row)
22678 }
22679}
22680
22681fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22682 if next_selection.end.column > 0 || next_selection.is_empty() {
22683 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22684 } else {
22685 MultiBufferRow(next_selection.end.row)
22686 }
22687}
22688
22689impl EditorSnapshot {
22690 pub fn remote_selections_in_range<'a>(
22691 &'a self,
22692 range: &'a Range<Anchor>,
22693 collaboration_hub: &dyn CollaborationHub,
22694 cx: &'a App,
22695 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22696 let participant_names = collaboration_hub.user_names(cx);
22697 let participant_indices = collaboration_hub.user_participant_indices(cx);
22698 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22699 let collaborators_by_replica_id = collaborators_by_peer_id
22700 .values()
22701 .map(|collaborator| (collaborator.replica_id, collaborator))
22702 .collect::<HashMap<_, _>>();
22703 self.buffer_snapshot
22704 .selections_in_range(range, false)
22705 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22706 if replica_id == AGENT_REPLICA_ID {
22707 Some(RemoteSelection {
22708 replica_id,
22709 selection,
22710 cursor_shape,
22711 line_mode,
22712 collaborator_id: CollaboratorId::Agent,
22713 user_name: Some("Agent".into()),
22714 color: cx.theme().players().agent(),
22715 })
22716 } else {
22717 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22718 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22719 let user_name = participant_names.get(&collaborator.user_id).cloned();
22720 Some(RemoteSelection {
22721 replica_id,
22722 selection,
22723 cursor_shape,
22724 line_mode,
22725 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22726 user_name,
22727 color: if let Some(index) = participant_index {
22728 cx.theme().players().color_for_participant(index.0)
22729 } else {
22730 cx.theme().players().absent()
22731 },
22732 })
22733 }
22734 })
22735 }
22736
22737 pub fn hunks_for_ranges(
22738 &self,
22739 ranges: impl IntoIterator<Item = Range<Point>>,
22740 ) -> Vec<MultiBufferDiffHunk> {
22741 let mut hunks = Vec::new();
22742 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22743 HashMap::default();
22744 for query_range in ranges {
22745 let query_rows =
22746 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22747 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22748 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22749 ) {
22750 // Include deleted hunks that are adjacent to the query range, because
22751 // otherwise they would be missed.
22752 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22753 if hunk.status().is_deleted() {
22754 intersects_range |= hunk.row_range.start == query_rows.end;
22755 intersects_range |= hunk.row_range.end == query_rows.start;
22756 }
22757 if intersects_range {
22758 if !processed_buffer_rows
22759 .entry(hunk.buffer_id)
22760 .or_default()
22761 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22762 {
22763 continue;
22764 }
22765 hunks.push(hunk);
22766 }
22767 }
22768 }
22769
22770 hunks
22771 }
22772
22773 fn display_diff_hunks_for_rows<'a>(
22774 &'a self,
22775 display_rows: Range<DisplayRow>,
22776 folded_buffers: &'a HashSet<BufferId>,
22777 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22778 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22779 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22780
22781 self.buffer_snapshot
22782 .diff_hunks_in_range(buffer_start..buffer_end)
22783 .filter_map(|hunk| {
22784 if folded_buffers.contains(&hunk.buffer_id) {
22785 return None;
22786 }
22787
22788 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22789 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22790
22791 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22792 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22793
22794 let display_hunk = if hunk_display_start.column() != 0 {
22795 DisplayDiffHunk::Folded {
22796 display_row: hunk_display_start.row(),
22797 }
22798 } else {
22799 let mut end_row = hunk_display_end.row();
22800 if hunk_display_end.column() > 0 {
22801 end_row.0 += 1;
22802 }
22803 let is_created_file = hunk.is_created_file();
22804 DisplayDiffHunk::Unfolded {
22805 status: hunk.status(),
22806 diff_base_byte_range: hunk.diff_base_byte_range,
22807 display_row_range: hunk_display_start.row()..end_row,
22808 multi_buffer_range: Anchor::range_in_buffer(
22809 hunk.excerpt_id,
22810 hunk.buffer_id,
22811 hunk.buffer_range,
22812 ),
22813 is_created_file,
22814 }
22815 };
22816
22817 Some(display_hunk)
22818 })
22819 }
22820
22821 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22822 self.display_snapshot.buffer_snapshot.language_at(position)
22823 }
22824
22825 pub fn is_focused(&self) -> bool {
22826 self.is_focused
22827 }
22828
22829 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22830 self.placeholder_text.as_ref()
22831 }
22832
22833 pub fn scroll_position(&self) -> gpui::Point<f32> {
22834 self.scroll_anchor.scroll_position(&self.display_snapshot)
22835 }
22836
22837 fn gutter_dimensions(
22838 &self,
22839 font_id: FontId,
22840 font_size: Pixels,
22841 max_line_number_width: Pixels,
22842 cx: &App,
22843 ) -> Option<GutterDimensions> {
22844 if !self.show_gutter {
22845 return None;
22846 }
22847
22848 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22849 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22850
22851 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22852 matches!(
22853 ProjectSettings::get_global(cx).git.git_gutter,
22854 Some(GitGutterSetting::TrackedFiles)
22855 )
22856 });
22857 let gutter_settings = EditorSettings::get_global(cx).gutter;
22858 let show_line_numbers = self
22859 .show_line_numbers
22860 .unwrap_or(gutter_settings.line_numbers);
22861 let line_gutter_width = if show_line_numbers {
22862 // Avoid flicker-like gutter resizes when the line number gains another digit by
22863 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22864 let min_width_for_number_on_gutter =
22865 ch_advance * gutter_settings.min_line_number_digits as f32;
22866 max_line_number_width.max(min_width_for_number_on_gutter)
22867 } else {
22868 0.0.into()
22869 };
22870
22871 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22872 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22873
22874 let git_blame_entries_width =
22875 self.git_blame_gutter_max_author_length
22876 .map(|max_author_length| {
22877 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22878 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22879
22880 /// The number of characters to dedicate to gaps and margins.
22881 const SPACING_WIDTH: usize = 4;
22882
22883 let max_char_count = max_author_length.min(renderer.max_author_length())
22884 + ::git::SHORT_SHA_LENGTH
22885 + MAX_RELATIVE_TIMESTAMP.len()
22886 + SPACING_WIDTH;
22887
22888 ch_advance * max_char_count
22889 });
22890
22891 let is_singleton = self.buffer_snapshot.is_singleton();
22892
22893 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22894 left_padding += if !is_singleton {
22895 ch_width * 4.0
22896 } else if show_runnables || show_breakpoints {
22897 ch_width * 3.0
22898 } else if show_git_gutter && show_line_numbers {
22899 ch_width * 2.0
22900 } else if show_git_gutter || show_line_numbers {
22901 ch_width
22902 } else {
22903 px(0.)
22904 };
22905
22906 let shows_folds = is_singleton && gutter_settings.folds;
22907
22908 let right_padding = if shows_folds && show_line_numbers {
22909 ch_width * 4.0
22910 } else if shows_folds || (!is_singleton && show_line_numbers) {
22911 ch_width * 3.0
22912 } else if show_line_numbers {
22913 ch_width
22914 } else {
22915 px(0.)
22916 };
22917
22918 Some(GutterDimensions {
22919 left_padding,
22920 right_padding,
22921 width: line_gutter_width + left_padding + right_padding,
22922 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22923 git_blame_entries_width,
22924 })
22925 }
22926
22927 pub fn render_crease_toggle(
22928 &self,
22929 buffer_row: MultiBufferRow,
22930 row_contains_cursor: bool,
22931 editor: Entity<Editor>,
22932 window: &mut Window,
22933 cx: &mut App,
22934 ) -> Option<AnyElement> {
22935 let folded = self.is_line_folded(buffer_row);
22936 let mut is_foldable = false;
22937
22938 if let Some(crease) = self
22939 .crease_snapshot
22940 .query_row(buffer_row, &self.buffer_snapshot)
22941 {
22942 is_foldable = true;
22943 match crease {
22944 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22945 if let Some(render_toggle) = render_toggle {
22946 let toggle_callback =
22947 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22948 if folded {
22949 editor.update(cx, |editor, cx| {
22950 editor.fold_at(buffer_row, window, cx)
22951 });
22952 } else {
22953 editor.update(cx, |editor, cx| {
22954 editor.unfold_at(buffer_row, window, cx)
22955 });
22956 }
22957 });
22958 return Some((render_toggle)(
22959 buffer_row,
22960 folded,
22961 toggle_callback,
22962 window,
22963 cx,
22964 ));
22965 }
22966 }
22967 }
22968 }
22969
22970 is_foldable |= self.starts_indent(buffer_row);
22971
22972 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22973 Some(
22974 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22975 .toggle_state(folded)
22976 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22977 if folded {
22978 this.unfold_at(buffer_row, window, cx);
22979 } else {
22980 this.fold_at(buffer_row, window, cx);
22981 }
22982 }))
22983 .into_any_element(),
22984 )
22985 } else {
22986 None
22987 }
22988 }
22989
22990 pub fn render_crease_trailer(
22991 &self,
22992 buffer_row: MultiBufferRow,
22993 window: &mut Window,
22994 cx: &mut App,
22995 ) -> Option<AnyElement> {
22996 let folded = self.is_line_folded(buffer_row);
22997 if let Crease::Inline { render_trailer, .. } = self
22998 .crease_snapshot
22999 .query_row(buffer_row, &self.buffer_snapshot)?
23000 {
23001 let render_trailer = render_trailer.as_ref()?;
23002 Some(render_trailer(buffer_row, folded, window, cx))
23003 } else {
23004 None
23005 }
23006 }
23007}
23008
23009impl Deref for EditorSnapshot {
23010 type Target = DisplaySnapshot;
23011
23012 fn deref(&self) -> &Self::Target {
23013 &self.display_snapshot
23014 }
23015}
23016
23017#[derive(Clone, Debug, PartialEq, Eq)]
23018pub enum EditorEvent {
23019 InputIgnored {
23020 text: Arc<str>,
23021 },
23022 InputHandled {
23023 utf16_range_to_replace: Option<Range<isize>>,
23024 text: Arc<str>,
23025 },
23026 ExcerptsAdded {
23027 buffer: Entity<Buffer>,
23028 predecessor: ExcerptId,
23029 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23030 },
23031 ExcerptsRemoved {
23032 ids: Vec<ExcerptId>,
23033 removed_buffer_ids: Vec<BufferId>,
23034 },
23035 BufferFoldToggled {
23036 ids: Vec<ExcerptId>,
23037 folded: bool,
23038 },
23039 ExcerptsEdited {
23040 ids: Vec<ExcerptId>,
23041 },
23042 ExcerptsExpanded {
23043 ids: Vec<ExcerptId>,
23044 },
23045 BufferEdited,
23046 Edited {
23047 transaction_id: clock::Lamport,
23048 },
23049 Reparsed(BufferId),
23050 Focused,
23051 FocusedIn,
23052 Blurred,
23053 DirtyChanged,
23054 Saved,
23055 TitleChanged,
23056 SelectionsChanged {
23057 local: bool,
23058 },
23059 ScrollPositionChanged {
23060 local: bool,
23061 autoscroll: bool,
23062 },
23063 TransactionUndone {
23064 transaction_id: clock::Lamport,
23065 },
23066 TransactionBegun {
23067 transaction_id: clock::Lamport,
23068 },
23069 CursorShapeChanged,
23070 BreadcrumbsChanged,
23071 PushedToNavHistory {
23072 anchor: Anchor,
23073 is_deactivate: bool,
23074 },
23075}
23076
23077impl EventEmitter<EditorEvent> for Editor {}
23078
23079impl Focusable for Editor {
23080 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23081 self.focus_handle.clone()
23082 }
23083}
23084
23085impl Render for Editor {
23086 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23087 let settings = ThemeSettings::get_global(cx);
23088
23089 let mut text_style = match self.mode {
23090 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23091 color: cx.theme().colors().editor_foreground,
23092 font_family: settings.ui_font.family.clone(),
23093 font_features: settings.ui_font.features.clone(),
23094 font_fallbacks: settings.ui_font.fallbacks.clone(),
23095 font_size: rems(0.875).into(),
23096 font_weight: settings.ui_font.weight,
23097 line_height: relative(settings.buffer_line_height.value()),
23098 ..Default::default()
23099 },
23100 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23101 color: cx.theme().colors().editor_foreground,
23102 font_family: settings.buffer_font.family.clone(),
23103 font_features: settings.buffer_font.features.clone(),
23104 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23105 font_size: settings.buffer_font_size(cx).into(),
23106 font_weight: settings.buffer_font.weight,
23107 line_height: relative(settings.buffer_line_height.value()),
23108 ..Default::default()
23109 },
23110 };
23111 if let Some(text_style_refinement) = &self.text_style_refinement {
23112 text_style.refine(text_style_refinement)
23113 }
23114
23115 let background = match self.mode {
23116 EditorMode::SingleLine => cx.theme().system().transparent,
23117 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23118 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23119 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23120 };
23121
23122 EditorElement::new(
23123 &cx.entity(),
23124 EditorStyle {
23125 background,
23126 border: cx.theme().colors().border,
23127 local_player: cx.theme().players().local(),
23128 text: text_style,
23129 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23130 syntax: cx.theme().syntax().clone(),
23131 status: cx.theme().status().clone(),
23132 inlay_hints_style: make_inlay_hints_style(cx),
23133 edit_prediction_styles: make_suggestion_styles(cx),
23134 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23135 show_underlines: self.diagnostics_enabled(),
23136 },
23137 )
23138 }
23139}
23140
23141impl EntityInputHandler for Editor {
23142 fn text_for_range(
23143 &mut self,
23144 range_utf16: Range<usize>,
23145 adjusted_range: &mut Option<Range<usize>>,
23146 _: &mut Window,
23147 cx: &mut Context<Self>,
23148 ) -> Option<String> {
23149 let snapshot = self.buffer.read(cx).read(cx);
23150 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23151 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23152 if (start.0..end.0) != range_utf16 {
23153 adjusted_range.replace(start.0..end.0);
23154 }
23155 Some(snapshot.text_for_range(start..end).collect())
23156 }
23157
23158 fn selected_text_range(
23159 &mut self,
23160 ignore_disabled_input: bool,
23161 _: &mut Window,
23162 cx: &mut Context<Self>,
23163 ) -> Option<UTF16Selection> {
23164 // Prevent the IME menu from appearing when holding down an alphabetic key
23165 // while input is disabled.
23166 if !ignore_disabled_input && !self.input_enabled {
23167 return None;
23168 }
23169
23170 let selection = self.selections.newest::<OffsetUtf16>(cx);
23171 let range = selection.range();
23172
23173 Some(UTF16Selection {
23174 range: range.start.0..range.end.0,
23175 reversed: selection.reversed,
23176 })
23177 }
23178
23179 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23180 let snapshot = self.buffer.read(cx).read(cx);
23181 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23182 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23183 }
23184
23185 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23186 self.clear_highlights::<InputComposition>(cx);
23187 self.ime_transaction.take();
23188 }
23189
23190 fn replace_text_in_range(
23191 &mut self,
23192 range_utf16: Option<Range<usize>>,
23193 text: &str,
23194 window: &mut Window,
23195 cx: &mut Context<Self>,
23196 ) {
23197 if !self.input_enabled {
23198 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23199 return;
23200 }
23201
23202 self.transact(window, cx, |this, window, cx| {
23203 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23204 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23205 Some(this.selection_replacement_ranges(range_utf16, cx))
23206 } else {
23207 this.marked_text_ranges(cx)
23208 };
23209
23210 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23211 let newest_selection_id = this.selections.newest_anchor().id;
23212 this.selections
23213 .all::<OffsetUtf16>(cx)
23214 .iter()
23215 .zip(ranges_to_replace.iter())
23216 .find_map(|(selection, range)| {
23217 if selection.id == newest_selection_id {
23218 Some(
23219 (range.start.0 as isize - selection.head().0 as isize)
23220 ..(range.end.0 as isize - selection.head().0 as isize),
23221 )
23222 } else {
23223 None
23224 }
23225 })
23226 });
23227
23228 cx.emit(EditorEvent::InputHandled {
23229 utf16_range_to_replace: range_to_replace,
23230 text: text.into(),
23231 });
23232
23233 if let Some(new_selected_ranges) = new_selected_ranges {
23234 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23235 selections.select_ranges(new_selected_ranges)
23236 });
23237 this.backspace(&Default::default(), window, cx);
23238 }
23239
23240 this.handle_input(text, window, cx);
23241 });
23242
23243 if let Some(transaction) = self.ime_transaction {
23244 self.buffer.update(cx, |buffer, cx| {
23245 buffer.group_until_transaction(transaction, cx);
23246 });
23247 }
23248
23249 self.unmark_text(window, cx);
23250 }
23251
23252 fn replace_and_mark_text_in_range(
23253 &mut self,
23254 range_utf16: Option<Range<usize>>,
23255 text: &str,
23256 new_selected_range_utf16: Option<Range<usize>>,
23257 window: &mut Window,
23258 cx: &mut Context<Self>,
23259 ) {
23260 if !self.input_enabled {
23261 return;
23262 }
23263
23264 let transaction = self.transact(window, cx, |this, window, cx| {
23265 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23266 let snapshot = this.buffer.read(cx).read(cx);
23267 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23268 for marked_range in &mut marked_ranges {
23269 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23270 marked_range.start.0 += relative_range_utf16.start;
23271 marked_range.start =
23272 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23273 marked_range.end =
23274 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23275 }
23276 }
23277 Some(marked_ranges)
23278 } else if let Some(range_utf16) = range_utf16 {
23279 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23280 Some(this.selection_replacement_ranges(range_utf16, cx))
23281 } else {
23282 None
23283 };
23284
23285 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23286 let newest_selection_id = this.selections.newest_anchor().id;
23287 this.selections
23288 .all::<OffsetUtf16>(cx)
23289 .iter()
23290 .zip(ranges_to_replace.iter())
23291 .find_map(|(selection, range)| {
23292 if selection.id == newest_selection_id {
23293 Some(
23294 (range.start.0 as isize - selection.head().0 as isize)
23295 ..(range.end.0 as isize - selection.head().0 as isize),
23296 )
23297 } else {
23298 None
23299 }
23300 })
23301 });
23302
23303 cx.emit(EditorEvent::InputHandled {
23304 utf16_range_to_replace: range_to_replace,
23305 text: text.into(),
23306 });
23307
23308 if let Some(ranges) = ranges_to_replace {
23309 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23310 s.select_ranges(ranges)
23311 });
23312 }
23313
23314 let marked_ranges = {
23315 let snapshot = this.buffer.read(cx).read(cx);
23316 this.selections
23317 .disjoint_anchors()
23318 .iter()
23319 .map(|selection| {
23320 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23321 })
23322 .collect::<Vec<_>>()
23323 };
23324
23325 if text.is_empty() {
23326 this.unmark_text(window, cx);
23327 } else {
23328 this.highlight_text::<InputComposition>(
23329 marked_ranges.clone(),
23330 HighlightStyle {
23331 underline: Some(UnderlineStyle {
23332 thickness: px(1.),
23333 color: None,
23334 wavy: false,
23335 }),
23336 ..Default::default()
23337 },
23338 cx,
23339 );
23340 }
23341
23342 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23343 let use_autoclose = this.use_autoclose;
23344 let use_auto_surround = this.use_auto_surround;
23345 this.set_use_autoclose(false);
23346 this.set_use_auto_surround(false);
23347 this.handle_input(text, window, cx);
23348 this.set_use_autoclose(use_autoclose);
23349 this.set_use_auto_surround(use_auto_surround);
23350
23351 if let Some(new_selected_range) = new_selected_range_utf16 {
23352 let snapshot = this.buffer.read(cx).read(cx);
23353 let new_selected_ranges = marked_ranges
23354 .into_iter()
23355 .map(|marked_range| {
23356 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23357 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23358 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23359 snapshot.clip_offset_utf16(new_start, Bias::Left)
23360 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23361 })
23362 .collect::<Vec<_>>();
23363
23364 drop(snapshot);
23365 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23366 selections.select_ranges(new_selected_ranges)
23367 });
23368 }
23369 });
23370
23371 self.ime_transaction = self.ime_transaction.or(transaction);
23372 if let Some(transaction) = self.ime_transaction {
23373 self.buffer.update(cx, |buffer, cx| {
23374 buffer.group_until_transaction(transaction, cx);
23375 });
23376 }
23377
23378 if self.text_highlights::<InputComposition>(cx).is_none() {
23379 self.ime_transaction.take();
23380 }
23381 }
23382
23383 fn bounds_for_range(
23384 &mut self,
23385 range_utf16: Range<usize>,
23386 element_bounds: gpui::Bounds<Pixels>,
23387 window: &mut Window,
23388 cx: &mut Context<Self>,
23389 ) -> Option<gpui::Bounds<Pixels>> {
23390 let text_layout_details = self.text_layout_details(window);
23391 let CharacterDimensions {
23392 em_width,
23393 em_advance,
23394 line_height,
23395 } = self.character_dimensions(window);
23396
23397 let snapshot = self.snapshot(window, cx);
23398 let scroll_position = snapshot.scroll_position();
23399 let scroll_left = scroll_position.x * em_advance;
23400
23401 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23402 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23403 + self.gutter_dimensions.full_width();
23404 let y = line_height * (start.row().as_f32() - scroll_position.y);
23405
23406 Some(Bounds {
23407 origin: element_bounds.origin + point(x, y),
23408 size: size(em_width, line_height),
23409 })
23410 }
23411
23412 fn character_index_for_point(
23413 &mut self,
23414 point: gpui::Point<Pixels>,
23415 _window: &mut Window,
23416 _cx: &mut Context<Self>,
23417 ) -> Option<usize> {
23418 let position_map = self.last_position_map.as_ref()?;
23419 if !position_map.text_hitbox.contains(&point) {
23420 return None;
23421 }
23422 let display_point = position_map.point_for_position(point).previous_valid;
23423 let anchor = position_map
23424 .snapshot
23425 .display_point_to_anchor(display_point, Bias::Left);
23426 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23427 Some(utf16_offset.0)
23428 }
23429}
23430
23431trait SelectionExt {
23432 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23433 fn spanned_rows(
23434 &self,
23435 include_end_if_at_line_start: bool,
23436 map: &DisplaySnapshot,
23437 ) -> Range<MultiBufferRow>;
23438}
23439
23440impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23441 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23442 let start = self
23443 .start
23444 .to_point(&map.buffer_snapshot)
23445 .to_display_point(map);
23446 let end = self
23447 .end
23448 .to_point(&map.buffer_snapshot)
23449 .to_display_point(map);
23450 if self.reversed {
23451 end..start
23452 } else {
23453 start..end
23454 }
23455 }
23456
23457 fn spanned_rows(
23458 &self,
23459 include_end_if_at_line_start: bool,
23460 map: &DisplaySnapshot,
23461 ) -> Range<MultiBufferRow> {
23462 let start = self.start.to_point(&map.buffer_snapshot);
23463 let mut end = self.end.to_point(&map.buffer_snapshot);
23464 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23465 end.row -= 1;
23466 }
23467
23468 let buffer_start = map.prev_line_boundary(start).0;
23469 let buffer_end = map.next_line_boundary(end).0;
23470 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23471 }
23472}
23473
23474impl<T: InvalidationRegion> InvalidationStack<T> {
23475 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23476 where
23477 S: Clone + ToOffset,
23478 {
23479 while let Some(region) = self.last() {
23480 let all_selections_inside_invalidation_ranges =
23481 if selections.len() == region.ranges().len() {
23482 selections
23483 .iter()
23484 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23485 .all(|(selection, invalidation_range)| {
23486 let head = selection.head().to_offset(buffer);
23487 invalidation_range.start <= head && invalidation_range.end >= head
23488 })
23489 } else {
23490 false
23491 };
23492
23493 if all_selections_inside_invalidation_ranges {
23494 break;
23495 } else {
23496 self.pop();
23497 }
23498 }
23499 }
23500}
23501
23502impl<T> Default for InvalidationStack<T> {
23503 fn default() -> Self {
23504 Self(Default::default())
23505 }
23506}
23507
23508impl<T> Deref for InvalidationStack<T> {
23509 type Target = Vec<T>;
23510
23511 fn deref(&self) -> &Self::Target {
23512 &self.0
23513 }
23514}
23515
23516impl<T> DerefMut for InvalidationStack<T> {
23517 fn deref_mut(&mut self) -> &mut Self::Target {
23518 &mut self.0
23519 }
23520}
23521
23522impl InvalidationRegion for SnippetState {
23523 fn ranges(&self) -> &[Range<Anchor>] {
23524 &self.ranges[self.active_index]
23525 }
23526}
23527
23528fn edit_prediction_edit_text(
23529 current_snapshot: &BufferSnapshot,
23530 edits: &[(Range<Anchor>, String)],
23531 edit_preview: &EditPreview,
23532 include_deletions: bool,
23533 cx: &App,
23534) -> HighlightedText {
23535 let edits = edits
23536 .iter()
23537 .map(|(anchor, text)| {
23538 (
23539 anchor.start.text_anchor..anchor.end.text_anchor,
23540 text.clone(),
23541 )
23542 })
23543 .collect::<Vec<_>>();
23544
23545 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23546}
23547
23548fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23549 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23550 // Just show the raw edit text with basic styling
23551 let mut text = String::new();
23552 let mut highlights = Vec::new();
23553
23554 let insertion_highlight_style = HighlightStyle {
23555 color: Some(cx.theme().colors().text),
23556 ..Default::default()
23557 };
23558
23559 for (_, edit_text) in edits {
23560 let start_offset = text.len();
23561 text.push_str(edit_text);
23562 let end_offset = text.len();
23563
23564 if start_offset < end_offset {
23565 highlights.push((start_offset..end_offset, insertion_highlight_style));
23566 }
23567 }
23568
23569 HighlightedText {
23570 text: text.into(),
23571 highlights,
23572 }
23573}
23574
23575pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23576 match severity {
23577 lsp::DiagnosticSeverity::ERROR => colors.error,
23578 lsp::DiagnosticSeverity::WARNING => colors.warning,
23579 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23580 lsp::DiagnosticSeverity::HINT => colors.info,
23581 _ => colors.ignored,
23582 }
23583}
23584
23585pub fn styled_runs_for_code_label<'a>(
23586 label: &'a CodeLabel,
23587 syntax_theme: &'a theme::SyntaxTheme,
23588) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23589 let fade_out = HighlightStyle {
23590 fade_out: Some(0.35),
23591 ..Default::default()
23592 };
23593
23594 let mut prev_end = label.filter_range.end;
23595 label
23596 .runs
23597 .iter()
23598 .enumerate()
23599 .flat_map(move |(ix, (range, highlight_id))| {
23600 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23601 style
23602 } else {
23603 return Default::default();
23604 };
23605 let mut muted_style = style;
23606 muted_style.highlight(fade_out);
23607
23608 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23609 if range.start >= label.filter_range.end {
23610 if range.start > prev_end {
23611 runs.push((prev_end..range.start, fade_out));
23612 }
23613 runs.push((range.clone(), muted_style));
23614 } else if range.end <= label.filter_range.end {
23615 runs.push((range.clone(), style));
23616 } else {
23617 runs.push((range.start..label.filter_range.end, style));
23618 runs.push((label.filter_range.end..range.end, muted_style));
23619 }
23620 prev_end = cmp::max(prev_end, range.end);
23621
23622 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23623 runs.push((prev_end..label.text.len(), fade_out));
23624 }
23625
23626 runs
23627 })
23628}
23629
23630pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23631 let mut prev_index = 0;
23632 let mut prev_codepoint: Option<char> = None;
23633 text.char_indices()
23634 .chain([(text.len(), '\0')])
23635 .filter_map(move |(index, codepoint)| {
23636 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23637 let is_boundary = index == text.len()
23638 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23639 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23640 if is_boundary {
23641 let chunk = &text[prev_index..index];
23642 prev_index = index;
23643 Some(chunk)
23644 } else {
23645 None
23646 }
23647 })
23648}
23649
23650pub trait RangeToAnchorExt: Sized {
23651 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23652
23653 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23654 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23655 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23656 }
23657}
23658
23659impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23660 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23661 let start_offset = self.start.to_offset(snapshot);
23662 let end_offset = self.end.to_offset(snapshot);
23663 if start_offset == end_offset {
23664 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23665 } else {
23666 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23667 }
23668 }
23669}
23670
23671pub trait RowExt {
23672 fn as_f32(&self) -> f32;
23673
23674 fn next_row(&self) -> Self;
23675
23676 fn previous_row(&self) -> Self;
23677
23678 fn minus(&self, other: Self) -> u32;
23679}
23680
23681impl RowExt for DisplayRow {
23682 fn as_f32(&self) -> f32 {
23683 self.0 as f32
23684 }
23685
23686 fn next_row(&self) -> Self {
23687 Self(self.0 + 1)
23688 }
23689
23690 fn previous_row(&self) -> Self {
23691 Self(self.0.saturating_sub(1))
23692 }
23693
23694 fn minus(&self, other: Self) -> u32 {
23695 self.0 - other.0
23696 }
23697}
23698
23699impl RowExt for MultiBufferRow {
23700 fn as_f32(&self) -> f32 {
23701 self.0 as f32
23702 }
23703
23704 fn next_row(&self) -> Self {
23705 Self(self.0 + 1)
23706 }
23707
23708 fn previous_row(&self) -> Self {
23709 Self(self.0.saturating_sub(1))
23710 }
23711
23712 fn minus(&self, other: Self) -> u32 {
23713 self.0 - other.0
23714 }
23715}
23716
23717trait RowRangeExt {
23718 type Row;
23719
23720 fn len(&self) -> usize;
23721
23722 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23723}
23724
23725impl RowRangeExt for Range<MultiBufferRow> {
23726 type Row = MultiBufferRow;
23727
23728 fn len(&self) -> usize {
23729 (self.end.0 - self.start.0) as usize
23730 }
23731
23732 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23733 (self.start.0..self.end.0).map(MultiBufferRow)
23734 }
23735}
23736
23737impl RowRangeExt for Range<DisplayRow> {
23738 type Row = DisplayRow;
23739
23740 fn len(&self) -> usize {
23741 (self.end.0 - self.start.0) as usize
23742 }
23743
23744 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23745 (self.start.0..self.end.0).map(DisplayRow)
23746 }
23747}
23748
23749/// If select range has more than one line, we
23750/// just point the cursor to range.start.
23751fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23752 if range.start.row == range.end.row {
23753 range
23754 } else {
23755 range.start..range.start
23756 }
23757}
23758pub struct KillRing(ClipboardItem);
23759impl Global for KillRing {}
23760
23761const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23762
23763enum BreakpointPromptEditAction {
23764 Log,
23765 Condition,
23766 HitCondition,
23767}
23768
23769struct BreakpointPromptEditor {
23770 pub(crate) prompt: Entity<Editor>,
23771 editor: WeakEntity<Editor>,
23772 breakpoint_anchor: Anchor,
23773 breakpoint: Breakpoint,
23774 edit_action: BreakpointPromptEditAction,
23775 block_ids: HashSet<CustomBlockId>,
23776 editor_margins: Arc<Mutex<EditorMargins>>,
23777 _subscriptions: Vec<Subscription>,
23778}
23779
23780impl BreakpointPromptEditor {
23781 const MAX_LINES: u8 = 4;
23782
23783 fn new(
23784 editor: WeakEntity<Editor>,
23785 breakpoint_anchor: Anchor,
23786 breakpoint: Breakpoint,
23787 edit_action: BreakpointPromptEditAction,
23788 window: &mut Window,
23789 cx: &mut Context<Self>,
23790 ) -> Self {
23791 let base_text = match edit_action {
23792 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23793 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23794 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23795 }
23796 .map(|msg| msg.to_string())
23797 .unwrap_or_default();
23798
23799 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23800 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23801
23802 let prompt = cx.new(|cx| {
23803 let mut prompt = Editor::new(
23804 EditorMode::AutoHeight {
23805 min_lines: 1,
23806 max_lines: Some(Self::MAX_LINES as usize),
23807 },
23808 buffer,
23809 None,
23810 window,
23811 cx,
23812 );
23813 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23814 prompt.set_show_cursor_when_unfocused(false, cx);
23815 prompt.set_placeholder_text(
23816 match edit_action {
23817 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23818 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23819 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23820 },
23821 cx,
23822 );
23823
23824 prompt
23825 });
23826
23827 Self {
23828 prompt,
23829 editor,
23830 breakpoint_anchor,
23831 breakpoint,
23832 edit_action,
23833 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23834 block_ids: Default::default(),
23835 _subscriptions: vec![],
23836 }
23837 }
23838
23839 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23840 self.block_ids.extend(block_ids)
23841 }
23842
23843 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23844 if let Some(editor) = self.editor.upgrade() {
23845 let message = self
23846 .prompt
23847 .read(cx)
23848 .buffer
23849 .read(cx)
23850 .as_singleton()
23851 .expect("A multi buffer in breakpoint prompt isn't possible")
23852 .read(cx)
23853 .as_rope()
23854 .to_string();
23855
23856 editor.update(cx, |editor, cx| {
23857 editor.edit_breakpoint_at_anchor(
23858 self.breakpoint_anchor,
23859 self.breakpoint.clone(),
23860 match self.edit_action {
23861 BreakpointPromptEditAction::Log => {
23862 BreakpointEditAction::EditLogMessage(message.into())
23863 }
23864 BreakpointPromptEditAction::Condition => {
23865 BreakpointEditAction::EditCondition(message.into())
23866 }
23867 BreakpointPromptEditAction::HitCondition => {
23868 BreakpointEditAction::EditHitCondition(message.into())
23869 }
23870 },
23871 cx,
23872 );
23873
23874 editor.remove_blocks(self.block_ids.clone(), None, cx);
23875 cx.focus_self(window);
23876 });
23877 }
23878 }
23879
23880 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23881 self.editor
23882 .update(cx, |editor, cx| {
23883 editor.remove_blocks(self.block_ids.clone(), None, cx);
23884 window.focus(&editor.focus_handle);
23885 })
23886 .log_err();
23887 }
23888
23889 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23890 let settings = ThemeSettings::get_global(cx);
23891 let text_style = TextStyle {
23892 color: if self.prompt.read(cx).read_only(cx) {
23893 cx.theme().colors().text_disabled
23894 } else {
23895 cx.theme().colors().text
23896 },
23897 font_family: settings.buffer_font.family.clone(),
23898 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23899 font_size: settings.buffer_font_size(cx).into(),
23900 font_weight: settings.buffer_font.weight,
23901 line_height: relative(settings.buffer_line_height.value()),
23902 ..Default::default()
23903 };
23904 EditorElement::new(
23905 &self.prompt,
23906 EditorStyle {
23907 background: cx.theme().colors().editor_background,
23908 local_player: cx.theme().players().local(),
23909 text: text_style,
23910 ..Default::default()
23911 },
23912 )
23913 }
23914}
23915
23916impl Render for BreakpointPromptEditor {
23917 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23918 let editor_margins = *self.editor_margins.lock();
23919 let gutter_dimensions = editor_margins.gutter;
23920 h_flex()
23921 .key_context("Editor")
23922 .bg(cx.theme().colors().editor_background)
23923 .border_y_1()
23924 .border_color(cx.theme().status().info_border)
23925 .size_full()
23926 .py(window.line_height() / 2.5)
23927 .on_action(cx.listener(Self::confirm))
23928 .on_action(cx.listener(Self::cancel))
23929 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23930 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23931 }
23932}
23933
23934impl Focusable for BreakpointPromptEditor {
23935 fn focus_handle(&self, cx: &App) -> FocusHandle {
23936 self.prompt.focus_handle(cx)
23937 }
23938}
23939
23940fn all_edits_insertions_or_deletions(
23941 edits: &Vec<(Range<Anchor>, String)>,
23942 snapshot: &MultiBufferSnapshot,
23943) -> bool {
23944 let mut all_insertions = true;
23945 let mut all_deletions = true;
23946
23947 for (range, new_text) in edits.iter() {
23948 let range_is_empty = range.to_offset(snapshot).is_empty();
23949 let text_is_empty = new_text.is_empty();
23950
23951 if range_is_empty != text_is_empty {
23952 if range_is_empty {
23953 all_deletions = false;
23954 } else {
23955 all_insertions = false;
23956 }
23957 } else {
23958 return false;
23959 }
23960
23961 if !all_insertions && !all_deletions {
23962 return false;
23963 }
23964 }
23965 all_insertions || all_deletions
23966}
23967
23968struct MissingEditPredictionKeybindingTooltip;
23969
23970impl Render for MissingEditPredictionKeybindingTooltip {
23971 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23972 ui::tooltip_container(window, cx, |container, _, cx| {
23973 container
23974 .flex_shrink_0()
23975 .max_w_80()
23976 .min_h(rems_from_px(124.))
23977 .justify_between()
23978 .child(
23979 v_flex()
23980 .flex_1()
23981 .text_ui_sm(cx)
23982 .child(Label::new("Conflict with Accept Keybinding"))
23983 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23984 )
23985 .child(
23986 h_flex()
23987 .pb_1()
23988 .gap_1()
23989 .items_end()
23990 .w_full()
23991 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23992 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23993 }))
23994 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23995 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23996 })),
23997 )
23998 })
23999 }
24000}
24001
24002#[derive(Debug, Clone, Copy, PartialEq)]
24003pub struct LineHighlight {
24004 pub background: Background,
24005 pub border: Option<gpui::Hsla>,
24006 pub include_gutter: bool,
24007 pub type_id: Option<TypeId>,
24008}
24009
24010struct LineManipulationResult {
24011 pub new_text: String,
24012 pub line_count_before: usize,
24013 pub line_count_after: usize,
24014}
24015
24016fn render_diff_hunk_controls(
24017 row: u32,
24018 status: &DiffHunkStatus,
24019 hunk_range: Range<Anchor>,
24020 is_created_file: bool,
24021 line_height: Pixels,
24022 editor: &Entity<Editor>,
24023 _window: &mut Window,
24024 cx: &mut App,
24025) -> AnyElement {
24026 h_flex()
24027 .h(line_height)
24028 .mr_1()
24029 .gap_1()
24030 .px_0p5()
24031 .pb_1()
24032 .border_x_1()
24033 .border_b_1()
24034 .border_color(cx.theme().colors().border_variant)
24035 .rounded_b_lg()
24036 .bg(cx.theme().colors().editor_background)
24037 .gap_1()
24038 .block_mouse_except_scroll()
24039 .shadow_md()
24040 .child(if status.has_secondary_hunk() {
24041 Button::new(("stage", row as u64), "Stage")
24042 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24043 .tooltip({
24044 let focus_handle = editor.focus_handle(cx);
24045 move |window, cx| {
24046 Tooltip::for_action_in(
24047 "Stage Hunk",
24048 &::git::ToggleStaged,
24049 &focus_handle,
24050 window,
24051 cx,
24052 )
24053 }
24054 })
24055 .on_click({
24056 let editor = editor.clone();
24057 move |_event, _window, cx| {
24058 editor.update(cx, |editor, cx| {
24059 editor.stage_or_unstage_diff_hunks(
24060 true,
24061 vec![hunk_range.start..hunk_range.start],
24062 cx,
24063 );
24064 });
24065 }
24066 })
24067 } else {
24068 Button::new(("unstage", row as u64), "Unstage")
24069 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24070 .tooltip({
24071 let focus_handle = editor.focus_handle(cx);
24072 move |window, cx| {
24073 Tooltip::for_action_in(
24074 "Unstage Hunk",
24075 &::git::ToggleStaged,
24076 &focus_handle,
24077 window,
24078 cx,
24079 )
24080 }
24081 })
24082 .on_click({
24083 let editor = editor.clone();
24084 move |_event, _window, cx| {
24085 editor.update(cx, |editor, cx| {
24086 editor.stage_or_unstage_diff_hunks(
24087 false,
24088 vec![hunk_range.start..hunk_range.start],
24089 cx,
24090 );
24091 });
24092 }
24093 })
24094 })
24095 .child(
24096 Button::new(("restore", row as u64), "Restore")
24097 .tooltip({
24098 let focus_handle = editor.focus_handle(cx);
24099 move |window, cx| {
24100 Tooltip::for_action_in(
24101 "Restore Hunk",
24102 &::git::Restore,
24103 &focus_handle,
24104 window,
24105 cx,
24106 )
24107 }
24108 })
24109 .on_click({
24110 let editor = editor.clone();
24111 move |_event, window, cx| {
24112 editor.update(cx, |editor, cx| {
24113 let snapshot = editor.snapshot(window, cx);
24114 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24115 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24116 });
24117 }
24118 })
24119 .disabled(is_created_file),
24120 )
24121 .when(
24122 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24123 |el| {
24124 el.child(
24125 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24126 .shape(IconButtonShape::Square)
24127 .icon_size(IconSize::Small)
24128 // .disabled(!has_multiple_hunks)
24129 .tooltip({
24130 let focus_handle = editor.focus_handle(cx);
24131 move |window, cx| {
24132 Tooltip::for_action_in(
24133 "Next Hunk",
24134 &GoToHunk,
24135 &focus_handle,
24136 window,
24137 cx,
24138 )
24139 }
24140 })
24141 .on_click({
24142 let editor = editor.clone();
24143 move |_event, window, cx| {
24144 editor.update(cx, |editor, cx| {
24145 let snapshot = editor.snapshot(window, cx);
24146 let position =
24147 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24148 editor.go_to_hunk_before_or_after_position(
24149 &snapshot,
24150 position,
24151 Direction::Next,
24152 window,
24153 cx,
24154 );
24155 editor.expand_selected_diff_hunks(cx);
24156 });
24157 }
24158 }),
24159 )
24160 .child(
24161 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24162 .shape(IconButtonShape::Square)
24163 .icon_size(IconSize::Small)
24164 // .disabled(!has_multiple_hunks)
24165 .tooltip({
24166 let focus_handle = editor.focus_handle(cx);
24167 move |window, cx| {
24168 Tooltip::for_action_in(
24169 "Previous Hunk",
24170 &GoToPreviousHunk,
24171 &focus_handle,
24172 window,
24173 cx,
24174 )
24175 }
24176 })
24177 .on_click({
24178 let editor = editor.clone();
24179 move |_event, window, cx| {
24180 editor.update(cx, |editor, cx| {
24181 let snapshot = editor.snapshot(window, cx);
24182 let point =
24183 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24184 editor.go_to_hunk_before_or_after_position(
24185 &snapshot,
24186 point,
24187 Direction::Prev,
24188 window,
24189 cx,
24190 );
24191 editor.expand_selected_diff_hunks(cx);
24192 });
24193 }
24194 }),
24195 )
24196 },
24197 )
24198 .into_any_element()
24199}
24200
24201pub fn multibuffer_context_lines(cx: &App) -> u32 {
24202 EditorSettings::try_get(cx)
24203 .map(|settings| settings.excerpt_context_lines)
24204 .unwrap_or(2)
24205 .clamp(1, 32)
24206}