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, CompletionIntent, CompletionResponse,
151 CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, Location, LocationLink,
152 PrepareRenameResponse, Project, ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::breakpoint_store::Breakpoint,
154 debugger::{
155 breakpoint_store::{
156 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
157 BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter},
164 project_settings::{GitGutterSetting, ProjectSettings},
165};
166use rand::{seq::SliceRandom, thread_rng};
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
169use selections_collection::{
170 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
171};
172use serde::{Deserialize, Serialize};
173use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::TypeId,
178 borrow::Cow,
179 cell::OnceCell,
180 cell::RefCell,
181 cmp::{self, Ordering, Reverse},
182 iter::Peekable,
183 mem,
184 num::NonZeroU32,
185 ops::Not,
186 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use sum_tree::TreeMap;
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);
229const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
230
231pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268struct InlineValueCache {
269 enabled: bool,
270 inlays: Vec<InlayId>,
271 refresh_task: Task<Option<()>>,
272}
273
274impl InlineValueCache {
275 fn new(enabled: bool) -> Self {
276 Self {
277 enabled,
278 inlays: Vec::new(),
279 refresh_task: Task::ready(None),
280 }
281 }
282}
283
284#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
285pub enum InlayId {
286 EditPrediction(usize),
287 DebuggerValue(usize),
288 // LSP
289 Hint(usize),
290 Color(usize),
291}
292
293impl InlayId {
294 fn id(&self) -> usize {
295 match self {
296 Self::EditPrediction(id) => *id,
297 Self::DebuggerValue(id) => *id,
298 Self::Hint(id) => *id,
299 Self::Color(id) => *id,
300 }
301 }
302}
303
304pub enum ActiveDebugLine {}
305pub enum DebugStackFrameLine {}
306enum DocumentHighlightRead {}
307enum DocumentHighlightWrite {}
308enum InputComposition {}
309pub enum PendingInput {}
310enum SelectedTextHighlight {}
311
312pub enum ConflictsOuter {}
313pub enum ConflictsOurs {}
314pub enum ConflictsTheirs {}
315pub enum ConflictsOursMarker {}
316pub enum ConflictsTheirsMarker {}
317
318#[derive(Debug, Copy, Clone, PartialEq, Eq)]
319pub enum Navigated {
320 Yes,
321 No,
322}
323
324impl Navigated {
325 pub fn from_bool(yes: bool) -> Navigated {
326 if yes { Navigated::Yes } else { Navigated::No }
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331enum DisplayDiffHunk {
332 Folded {
333 display_row: DisplayRow,
334 },
335 Unfolded {
336 is_created_file: bool,
337 diff_base_byte_range: Range<usize>,
338 display_row_range: Range<DisplayRow>,
339 multi_buffer_range: Range<Anchor>,
340 status: DiffHunkStatus,
341 },
342}
343
344pub enum HideMouseCursorOrigin {
345 TypingAction,
346 MovementAction,
347}
348
349pub fn init_settings(cx: &mut App) {
350 EditorSettings::register(cx);
351}
352
353pub fn init(cx: &mut App) {
354 init_settings(cx);
355
356 cx.set_global(GlobalBlameRenderer(Arc::new(())));
357
358 workspace::register_project_item::<Editor>(cx);
359 workspace::FollowableViewRegistry::register::<Editor>(cx);
360 workspace::register_serializable_item::<Editor>(cx);
361
362 cx.observe_new(
363 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
364 workspace.register_action(Editor::new_file);
365 workspace.register_action(Editor::new_file_vertical);
366 workspace.register_action(Editor::new_file_horizontal);
367 workspace.register_action(Editor::cancel_language_server_work);
368 workspace.register_action(Editor::toggle_focus);
369 },
370 )
371 .detach();
372
373 cx.on_action(move |_: &workspace::NewFile, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387 cx.on_action(move |_: &workspace::NewWindow, cx| {
388 let app_state = workspace::AppState::global(cx);
389 if let Some(app_state) = app_state.upgrade() {
390 workspace::open_new(
391 Default::default(),
392 app_state,
393 cx,
394 |workspace, window, cx| {
395 cx.activate(true);
396 Editor::new_file(workspace, &Default::default(), window, cx)
397 },
398 )
399 .detach();
400 }
401 });
402}
403
404pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
405 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
406}
407
408pub trait DiagnosticRenderer {
409 fn render_group(
410 &self,
411 diagnostic_group: Vec<DiagnosticEntry<Point>>,
412 buffer_id: BufferId,
413 snapshot: EditorSnapshot,
414 editor: WeakEntity<Editor>,
415 cx: &mut App,
416 ) -> Vec<BlockProperties<Anchor>>;
417
418 fn render_hover(
419 &self,
420 diagnostic_group: Vec<DiagnosticEntry<Point>>,
421 range: Range<Point>,
422 buffer_id: BufferId,
423 cx: &mut App,
424 ) -> Option<Entity<markdown::Markdown>>;
425
426 fn open_link(
427 &self,
428 editor: &mut Editor,
429 link: SharedString,
430 window: &mut Window,
431 cx: &mut Context<Editor>,
432 );
433}
434
435pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
436
437impl GlobalDiagnosticRenderer {
438 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
439 cx.try_global::<Self>().map(|g| g.0.clone())
440 }
441}
442
443impl gpui::Global for GlobalDiagnosticRenderer {}
444pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
445 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
446}
447
448pub struct SearchWithinRange;
449
450trait InvalidationRegion {
451 fn ranges(&self) -> &[Range<Anchor>];
452}
453
454#[derive(Clone, Debug, PartialEq)]
455pub enum SelectPhase {
456 Begin {
457 position: DisplayPoint,
458 add: bool,
459 click_count: usize,
460 },
461 BeginColumnar {
462 position: DisplayPoint,
463 reset: bool,
464 mode: ColumnarMode,
465 goal_column: u32,
466 },
467 Extend {
468 position: DisplayPoint,
469 click_count: usize,
470 },
471 Update {
472 position: DisplayPoint,
473 goal_column: u32,
474 scroll_delta: gpui::Point<f32>,
475 },
476 End,
477}
478
479#[derive(Clone, Debug, PartialEq)]
480pub enum ColumnarMode {
481 FromMouse,
482 FromSelection,
483}
484
485#[derive(Clone, Debug)]
486pub enum SelectMode {
487 Character,
488 Word(Range<Anchor>),
489 Line(Range<Anchor>),
490 All,
491}
492
493#[derive(Clone, PartialEq, Eq, Debug)]
494pub enum EditorMode {
495 SingleLine,
496 AutoHeight {
497 min_lines: usize,
498 max_lines: Option<usize>,
499 },
500 Full {
501 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
502 scale_ui_elements_with_buffer_font_size: bool,
503 /// When set to `true`, the editor will render a background for the active line.
504 show_active_line_background: bool,
505 /// When set to `true`, the editor's height will be determined by its content.
506 sized_by_content: bool,
507 },
508 Minimap {
509 parent: WeakEntity<Editor>,
510 },
511}
512
513impl EditorMode {
514 pub fn full() -> Self {
515 Self::Full {
516 scale_ui_elements_with_buffer_font_size: true,
517 show_active_line_background: true,
518 sized_by_content: false,
519 }
520 }
521
522 #[inline]
523 pub fn is_full(&self) -> bool {
524 matches!(self, Self::Full { .. })
525 }
526
527 #[inline]
528 pub fn is_single_line(&self) -> bool {
529 matches!(self, Self::SingleLine { .. })
530 }
531
532 #[inline]
533 fn is_minimap(&self) -> bool {
534 matches!(self, Self::Minimap { .. })
535 }
536}
537
538#[derive(Copy, Clone, Debug)]
539pub enum SoftWrap {
540 /// Prefer not to wrap at all.
541 ///
542 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
543 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
544 GitDiff,
545 /// Prefer a single line generally, unless an overly long line is encountered.
546 None,
547 /// Soft wrap lines that exceed the editor width.
548 EditorWidth,
549 /// Soft wrap lines at the preferred line length.
550 Column(u32),
551 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
552 Bounded(u32),
553}
554
555#[derive(Clone)]
556pub struct EditorStyle {
557 pub background: Hsla,
558 pub border: Hsla,
559 pub local_player: PlayerColor,
560 pub text: TextStyle,
561 pub scrollbar_width: Pixels,
562 pub syntax: Arc<SyntaxTheme>,
563 pub status: StatusColors,
564 pub inlay_hints_style: HighlightStyle,
565 pub edit_prediction_styles: EditPredictionStyles,
566 pub unnecessary_code_fade: f32,
567 pub show_underlines: bool,
568}
569
570impl Default for EditorStyle {
571 fn default() -> Self {
572 Self {
573 background: Hsla::default(),
574 border: Hsla::default(),
575 local_player: PlayerColor::default(),
576 text: TextStyle::default(),
577 scrollbar_width: Pixels::default(),
578 syntax: Default::default(),
579 // HACK: Status colors don't have a real default.
580 // We should look into removing the status colors from the editor
581 // style and retrieve them directly from the theme.
582 status: StatusColors::dark(),
583 inlay_hints_style: HighlightStyle::default(),
584 edit_prediction_styles: EditPredictionStyles {
585 insertion: HighlightStyle::default(),
586 whitespace: HighlightStyle::default(),
587 },
588 unnecessary_code_fade: Default::default(),
589 show_underlines: true,
590 }
591 }
592}
593
594pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
595 let show_background = language_settings::language_settings(None, None, cx)
596 .inlay_hints
597 .show_background;
598
599 HighlightStyle {
600 color: Some(cx.theme().status().hint),
601 background_color: show_background.then(|| cx.theme().status().hint_background),
602 ..HighlightStyle::default()
603 }
604}
605
606pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
607 EditPredictionStyles {
608 insertion: HighlightStyle {
609 color: Some(cx.theme().status().predictive),
610 ..HighlightStyle::default()
611 },
612 whitespace: HighlightStyle {
613 background_color: Some(cx.theme().status().created_background),
614 ..HighlightStyle::default()
615 },
616 }
617}
618
619type CompletionId = usize;
620
621pub(crate) enum EditDisplayMode {
622 TabAccept,
623 DiffPopover,
624 Inline,
625}
626
627enum EditPrediction {
628 Edit {
629 edits: Vec<(Range<Anchor>, String)>,
630 edit_preview: Option<EditPreview>,
631 display_mode: EditDisplayMode,
632 snapshot: BufferSnapshot,
633 },
634 Move {
635 target: Anchor,
636 snapshot: BufferSnapshot,
637 },
638}
639
640struct EditPredictionState {
641 inlay_ids: Vec<InlayId>,
642 completion: EditPrediction,
643 completion_id: Option<SharedString>,
644 invalidation_range: Range<Anchor>,
645}
646
647enum EditPredictionSettings {
648 Disabled,
649 Enabled {
650 show_in_menu: bool,
651 preview_requires_modifier: bool,
652 },
653}
654
655enum EditPredictionHighlight {}
656
657#[derive(Debug, Clone)]
658struct InlineDiagnostic {
659 message: SharedString,
660 group_id: usize,
661 is_primary: bool,
662 start: Point,
663 severity: lsp::DiagnosticSeverity,
664}
665
666pub enum MenuEditPredictionsPolicy {
667 Never,
668 ByProvider,
669}
670
671pub enum EditPredictionPreview {
672 /// Modifier is not pressed
673 Inactive { released_too_fast: bool },
674 /// Modifier pressed
675 Active {
676 since: Instant,
677 previous_scroll_position: Option<ScrollAnchor>,
678 },
679}
680
681impl EditPredictionPreview {
682 pub fn released_too_fast(&self) -> bool {
683 match self {
684 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
685 EditPredictionPreview::Active { .. } => false,
686 }
687 }
688
689 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
690 if let EditPredictionPreview::Active {
691 previous_scroll_position,
692 ..
693 } = self
694 {
695 *previous_scroll_position = scroll_position;
696 }
697 }
698}
699
700pub struct ContextMenuOptions {
701 pub min_entries_visible: usize,
702 pub max_entries_visible: usize,
703 pub placement: Option<ContextMenuPlacement>,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq)]
707pub enum ContextMenuPlacement {
708 Above,
709 Below,
710}
711
712#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
713struct EditorActionId(usize);
714
715impl EditorActionId {
716 pub fn post_inc(&mut self) -> Self {
717 let answer = self.0;
718
719 *self = Self(answer + 1);
720
721 Self(answer)
722 }
723}
724
725// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
726// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
727
728type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
729type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
730
731#[derive(Default)]
732struct ScrollbarMarkerState {
733 scrollbar_size: Size<Pixels>,
734 dirty: bool,
735 markers: Arc<[PaintQuad]>,
736 pending_refresh: Option<Task<Result<()>>>,
737}
738
739impl ScrollbarMarkerState {
740 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
741 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
742 }
743}
744
745#[derive(Clone, Copy, PartialEq, Eq)]
746pub enum MinimapVisibility {
747 Disabled,
748 Enabled {
749 /// The configuration currently present in the users settings.
750 setting_configuration: bool,
751 /// Whether to override the currently set visibility from the users setting.
752 toggle_override: bool,
753 },
754}
755
756impl MinimapVisibility {
757 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
758 if mode.is_full() {
759 Self::Enabled {
760 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
761 toggle_override: false,
762 }
763 } else {
764 Self::Disabled
765 }
766 }
767
768 fn hidden(&self) -> Self {
769 match *self {
770 Self::Enabled {
771 setting_configuration,
772 ..
773 } => Self::Enabled {
774 setting_configuration,
775 toggle_override: setting_configuration,
776 },
777 Self::Disabled => Self::Disabled,
778 }
779 }
780
781 fn disabled(&self) -> bool {
782 matches!(*self, Self::Disabled)
783 }
784
785 fn settings_visibility(&self) -> bool {
786 match *self {
787 Self::Enabled {
788 setting_configuration,
789 ..
790 } => setting_configuration,
791 _ => false,
792 }
793 }
794
795 fn visible(&self) -> bool {
796 match *self {
797 Self::Enabled {
798 setting_configuration,
799 toggle_override,
800 } => setting_configuration ^ toggle_override,
801 _ => false,
802 }
803 }
804
805 fn toggle_visibility(&self) -> Self {
806 match *self {
807 Self::Enabled {
808 toggle_override,
809 setting_configuration,
810 } => Self::Enabled {
811 setting_configuration,
812 toggle_override: !toggle_override,
813 },
814 Self::Disabled => Self::Disabled,
815 }
816 }
817}
818
819#[derive(Clone, Debug)]
820struct RunnableTasks {
821 templates: Vec<(TaskSourceKind, TaskTemplate)>,
822 offset: multi_buffer::Anchor,
823 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
824 column: u32,
825 // Values of all named captures, including those starting with '_'
826 extra_variables: HashMap<String, String>,
827 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
828 context_range: Range<BufferOffset>,
829}
830
831impl RunnableTasks {
832 fn resolve<'a>(
833 &'a self,
834 cx: &'a task::TaskContext,
835 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
836 self.templates.iter().filter_map(|(kind, template)| {
837 template
838 .resolve_task(&kind.to_id_base(), cx)
839 .map(|task| (kind.clone(), task))
840 })
841 }
842}
843
844#[derive(Clone)]
845pub struct ResolvedTasks {
846 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
847 position: Anchor,
848}
849
850#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
851struct BufferOffset(usize);
852
853// Addons allow storing per-editor state in other crates (e.g. Vim)
854pub trait Addon: 'static {
855 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
856
857 fn render_buffer_header_controls(
858 &self,
859 _: &ExcerptInfo,
860 _: &Window,
861 _: &App,
862 ) -> Option<AnyElement> {
863 None
864 }
865
866 fn to_any(&self) -> &dyn std::any::Any;
867
868 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
869 None
870 }
871}
872
873struct ChangeLocation {
874 current: Option<Vec<Anchor>>,
875 original: Vec<Anchor>,
876}
877impl ChangeLocation {
878 fn locations(&self) -> &[Anchor] {
879 self.current.as_ref().unwrap_or(&self.original)
880 }
881}
882
883/// A set of caret positions, registered when the editor was edited.
884pub struct ChangeList {
885 changes: Vec<ChangeLocation>,
886 /// Currently "selected" change.
887 position: Option<usize>,
888}
889
890impl ChangeList {
891 pub fn new() -> Self {
892 Self {
893 changes: Vec::new(),
894 position: None,
895 }
896 }
897
898 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
899 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
900 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
901 if self.changes.is_empty() {
902 return None;
903 }
904
905 let prev = self.position.unwrap_or(self.changes.len());
906 let next = if direction == Direction::Prev {
907 prev.saturating_sub(count)
908 } else {
909 (prev + count).min(self.changes.len() - 1)
910 };
911 self.position = Some(next);
912 self.changes.get(next).map(|change| change.locations())
913 }
914
915 /// Adds a new change to the list, resetting the change list position.
916 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
917 self.position.take();
918 if let Some(last) = self.changes.last_mut()
919 && group
920 {
921 last.current = Some(new_positions)
922 } else {
923 self.changes.push(ChangeLocation {
924 original: new_positions,
925 current: None,
926 });
927 }
928 }
929
930 pub fn last(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.locations())
932 }
933
934 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
935 self.changes.last().map(|change| change.original.as_slice())
936 }
937
938 pub fn invert_last_group(&mut self) {
939 if let Some(last) = self.changes.last_mut()
940 && let Some(current) = last.current.as_mut()
941 {
942 mem::swap(&mut last.original, current);
943 }
944 }
945}
946
947#[derive(Clone)]
948struct InlineBlamePopoverState {
949 scroll_handle: ScrollHandle,
950 commit_message: Option<ParsedCommitMessage>,
951 markdown: Entity<Markdown>,
952}
953
954struct InlineBlamePopover {
955 position: gpui::Point<Pixels>,
956 hide_task: Option<Task<()>>,
957 popover_bounds: Option<Bounds<Pixels>>,
958 popover_state: InlineBlamePopoverState,
959 keyboard_grace: bool,
960}
961
962enum SelectionDragState {
963 /// State when no drag related activity is detected.
964 None,
965 /// State when the mouse is down on a selection that is about to be dragged.
966 ReadyToDrag {
967 selection: Selection<Anchor>,
968 click_position: gpui::Point<Pixels>,
969 mouse_down_time: Instant,
970 },
971 /// State when the mouse is dragging the selection in the editor.
972 Dragging {
973 selection: Selection<Anchor>,
974 drop_cursor: Selection<Anchor>,
975 hide_drop_cursor: bool,
976 },
977}
978
979enum ColumnarSelectionState {
980 FromMouse {
981 selection_tail: Anchor,
982 display_point: Option<DisplayPoint>,
983 },
984 FromSelection {
985 selection_tail: Anchor,
986 },
987}
988
989/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
990/// a breakpoint on them.
991#[derive(Clone, Copy, Debug, PartialEq, Eq)]
992struct PhantomBreakpointIndicator {
993 display_row: DisplayRow,
994 /// There's a small debounce between hovering over the line and showing the indicator.
995 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
996 is_active: bool,
997 collides_with_existing_breakpoint: bool,
998}
999
1000/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1001///
1002/// See the [module level documentation](self) for more information.
1003pub struct Editor {
1004 focus_handle: FocusHandle,
1005 last_focused_descendant: Option<WeakFocusHandle>,
1006 /// The text buffer being edited
1007 buffer: Entity<MultiBuffer>,
1008 /// Map of how text in the buffer should be displayed.
1009 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1010 pub display_map: Entity<DisplayMap>,
1011 pub selections: SelectionsCollection,
1012 pub scroll_manager: ScrollManager,
1013 /// When inline assist editors are linked, they all render cursors because
1014 /// typing enters text into each of them, even the ones that aren't focused.
1015 pub(crate) show_cursor_when_unfocused: bool,
1016 columnar_selection_state: Option<ColumnarSelectionState>,
1017 add_selections_state: Option<AddSelectionsState>,
1018 select_next_state: Option<SelectNextState>,
1019 select_prev_state: Option<SelectNextState>,
1020 selection_history: SelectionHistory,
1021 defer_selection_effects: bool,
1022 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1023 autoclose_regions: Vec<AutocloseRegion>,
1024 snippet_stack: InvalidationStack<SnippetState>,
1025 select_syntax_node_history: SelectSyntaxNodeHistory,
1026 ime_transaction: Option<TransactionId>,
1027 pub diagnostics_max_severity: DiagnosticSeverity,
1028 active_diagnostics: ActiveDiagnostic,
1029 show_inline_diagnostics: bool,
1030 inline_diagnostics_update: Task<()>,
1031 inline_diagnostics_enabled: bool,
1032 diagnostics_enabled: bool,
1033 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1034 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1035 hard_wrap: Option<usize>,
1036 project: Option<Entity<Project>>,
1037 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1038 completion_provider: Option<Rc<dyn CompletionProvider>>,
1039 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1040 blink_manager: Entity<BlinkManager>,
1041 show_cursor_names: bool,
1042 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1043 pub show_local_selections: bool,
1044 mode: EditorMode,
1045 show_breadcrumbs: bool,
1046 show_gutter: bool,
1047 show_scrollbars: ScrollbarAxes,
1048 minimap_visibility: MinimapVisibility,
1049 offset_content: bool,
1050 disable_expand_excerpt_buttons: bool,
1051 show_line_numbers: Option<bool>,
1052 use_relative_line_numbers: Option<bool>,
1053 show_git_diff_gutter: Option<bool>,
1054 show_code_actions: Option<bool>,
1055 show_runnables: Option<bool>,
1056 show_breakpoints: Option<bool>,
1057 show_wrap_guides: Option<bool>,
1058 show_indent_guides: Option<bool>,
1059 placeholder_text: Option<Arc<str>>,
1060 highlight_order: usize,
1061 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1062 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1063 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1064 scrollbar_marker_state: ScrollbarMarkerState,
1065 active_indent_guides_state: ActiveIndentGuidesState,
1066 nav_history: Option<ItemNavHistory>,
1067 context_menu: RefCell<Option<CodeContextMenu>>,
1068 context_menu_options: Option<ContextMenuOptions>,
1069 mouse_context_menu: Option<MouseContextMenu>,
1070 completion_tasks: Vec<(CompletionId, Task<()>)>,
1071 inline_blame_popover: Option<InlineBlamePopover>,
1072 inline_blame_popover_show_task: Option<Task<()>>,
1073 signature_help_state: SignatureHelpState,
1074 auto_signature_help: Option<bool>,
1075 find_all_references_task_sources: Vec<Anchor>,
1076 next_completion_id: CompletionId,
1077 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1078 code_actions_task: Option<Task<Result<()>>>,
1079 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1080 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 document_highlights_task: Option<Task<()>>,
1082 linked_editing_range_task: Option<Task<Option<()>>>,
1083 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1084 pending_rename: Option<RenameState>,
1085 searchable: bool,
1086 cursor_shape: CursorShape,
1087 current_line_highlight: Option<CurrentLineHighlight>,
1088 collapse_matches: bool,
1089 autoindent_mode: Option<AutoindentMode>,
1090 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1091 input_enabled: bool,
1092 use_modal_editing: bool,
1093 read_only: bool,
1094 leader_id: Option<CollaboratorId>,
1095 remote_id: Option<ViewId>,
1096 pub hover_state: HoverState,
1097 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1098 gutter_hovered: bool,
1099 hovered_link_state: Option<HoveredLinkState>,
1100 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1101 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1102 active_edit_prediction: Option<EditPredictionState>,
1103 /// Used to prevent flickering as the user types while the menu is open
1104 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1105 edit_prediction_settings: EditPredictionSettings,
1106 edit_predictions_hidden_for_vim_mode: bool,
1107 show_edit_predictions_override: Option<bool>,
1108 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1109 edit_prediction_preview: EditPredictionPreview,
1110 edit_prediction_indent_conflict: bool,
1111 edit_prediction_requires_modifier_in_indent_conflict: bool,
1112 inlay_hint_cache: InlayHintCache,
1113 next_inlay_id: usize,
1114 _subscriptions: Vec<Subscription>,
1115 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1116 gutter_dimensions: GutterDimensions,
1117 style: Option<EditorStyle>,
1118 text_style_refinement: Option<TextStyleRefinement>,
1119 next_editor_action_id: EditorActionId,
1120 editor_actions: Rc<
1121 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1122 >,
1123 use_autoclose: bool,
1124 use_auto_surround: bool,
1125 auto_replace_emoji_shortcode: bool,
1126 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1127 show_git_blame_gutter: bool,
1128 show_git_blame_inline: bool,
1129 show_git_blame_inline_delay_task: Option<Task<()>>,
1130 git_blame_inline_enabled: bool,
1131 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1132 serialize_dirty_buffers: bool,
1133 show_selection_menu: Option<bool>,
1134 blame: Option<Entity<GitBlame>>,
1135 blame_subscription: Option<Subscription>,
1136 custom_context_menu: Option<
1137 Box<
1138 dyn 'static
1139 + Fn(
1140 &mut Self,
1141 DisplayPoint,
1142 &mut Window,
1143 &mut Context<Self>,
1144 ) -> Option<Entity<ui::ContextMenu>>,
1145 >,
1146 >,
1147 last_bounds: Option<Bounds<Pixels>>,
1148 last_position_map: Option<Rc<PositionMap>>,
1149 expect_bounds_change: Option<Bounds<Pixels>>,
1150 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1151 tasks_update_task: Option<Task<()>>,
1152 breakpoint_store: Option<Entity<BreakpointStore>>,
1153 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1154 hovered_diff_hunk_row: Option<DisplayRow>,
1155 pull_diagnostics_task: Task<()>,
1156 in_project_search: bool,
1157 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1158 breadcrumb_header: Option<String>,
1159 focused_block: Option<FocusedBlock>,
1160 next_scroll_position: NextScrollCursorCenterTopBottom,
1161 addons: HashMap<TypeId, Box<dyn Addon>>,
1162 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1163 load_diff_task: Option<Shared<Task<()>>>,
1164 /// Whether we are temporarily displaying a diff other than git's
1165 temporary_diff_override: bool,
1166 selection_mark_mode: bool,
1167 toggle_fold_multiple_buffers: Task<()>,
1168 _scroll_cursor_center_top_bottom_task: Task<()>,
1169 serialize_selections: Task<()>,
1170 serialize_folds: Task<()>,
1171 mouse_cursor_hidden: bool,
1172 minimap: Option<Entity<Self>>,
1173 hide_mouse_mode: HideMouseMode,
1174 pub change_list: ChangeList,
1175 inline_value_cache: InlineValueCache,
1176 selection_drag_state: SelectionDragState,
1177 next_color_inlay_id: usize,
1178 colors: Option<LspColorData>,
1179 folding_newlines: Task<()>,
1180}
1181
1182#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1183enum NextScrollCursorCenterTopBottom {
1184 #[default]
1185 Center,
1186 Top,
1187 Bottom,
1188}
1189
1190impl NextScrollCursorCenterTopBottom {
1191 fn next(&self) -> Self {
1192 match self {
1193 Self::Center => Self::Top,
1194 Self::Top => Self::Bottom,
1195 Self::Bottom => Self::Center,
1196 }
1197 }
1198}
1199
1200#[derive(Clone)]
1201pub struct EditorSnapshot {
1202 pub mode: EditorMode,
1203 show_gutter: bool,
1204 show_line_numbers: Option<bool>,
1205 show_git_diff_gutter: Option<bool>,
1206 show_code_actions: Option<bool>,
1207 show_runnables: Option<bool>,
1208 show_breakpoints: Option<bool>,
1209 git_blame_gutter_max_author_length: Option<usize>,
1210 pub display_snapshot: DisplaySnapshot,
1211 pub placeholder_text: Option<Arc<str>>,
1212 is_focused: bool,
1213 scroll_anchor: ScrollAnchor,
1214 ongoing_scroll: OngoingScroll,
1215 current_line_highlight: CurrentLineHighlight,
1216 gutter_hovered: bool,
1217}
1218
1219#[derive(Default, Debug, Clone, Copy)]
1220pub struct GutterDimensions {
1221 pub left_padding: Pixels,
1222 pub right_padding: Pixels,
1223 pub width: Pixels,
1224 pub margin: Pixels,
1225 pub git_blame_entries_width: Option<Pixels>,
1226}
1227
1228impl GutterDimensions {
1229 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1230 Self {
1231 margin: Self::default_gutter_margin(font_id, font_size, cx),
1232 ..Default::default()
1233 }
1234 }
1235
1236 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1237 -cx.text_system().descent(font_id, font_size)
1238 }
1239 /// The full width of the space taken up by the gutter.
1240 pub fn full_width(&self) -> Pixels {
1241 self.margin + self.width
1242 }
1243
1244 /// The width of the space reserved for the fold indicators,
1245 /// use alongside 'justify_end' and `gutter_width` to
1246 /// right align content with the line numbers
1247 pub fn fold_area_width(&self) -> Pixels {
1248 self.margin + self.right_padding
1249 }
1250}
1251
1252struct CharacterDimensions {
1253 em_width: Pixels,
1254 em_advance: Pixels,
1255 line_height: Pixels,
1256}
1257
1258#[derive(Debug)]
1259pub struct RemoteSelection {
1260 pub replica_id: ReplicaId,
1261 pub selection: Selection<Anchor>,
1262 pub cursor_shape: CursorShape,
1263 pub collaborator_id: CollaboratorId,
1264 pub line_mode: bool,
1265 pub user_name: Option<SharedString>,
1266 pub color: PlayerColor,
1267}
1268
1269#[derive(Clone, Debug)]
1270struct SelectionHistoryEntry {
1271 selections: Arc<[Selection<Anchor>]>,
1272 select_next_state: Option<SelectNextState>,
1273 select_prev_state: Option<SelectNextState>,
1274 add_selections_state: Option<AddSelectionsState>,
1275}
1276
1277#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1278enum SelectionHistoryMode {
1279 Normal,
1280 Undoing,
1281 Redoing,
1282 Skipping,
1283}
1284
1285#[derive(Clone, PartialEq, Eq, Hash)]
1286struct HoveredCursor {
1287 replica_id: u16,
1288 selection_id: usize,
1289}
1290
1291impl Default for SelectionHistoryMode {
1292 fn default() -> Self {
1293 Self::Normal
1294 }
1295}
1296
1297#[derive(Debug)]
1298/// SelectionEffects controls the side-effects of updating the selection.
1299///
1300/// The default behaviour does "what you mostly want":
1301/// - it pushes to the nav history if the cursor moved by >10 lines
1302/// - it re-triggers completion requests
1303/// - it scrolls to fit
1304///
1305/// You might want to modify these behaviours. For example when doing a "jump"
1306/// like go to definition, we always want to add to nav history; but when scrolling
1307/// in vim mode we never do.
1308///
1309/// Similarly, you might want to disable scrolling if you don't want the viewport to
1310/// move.
1311#[derive(Clone)]
1312pub struct SelectionEffects {
1313 nav_history: Option<bool>,
1314 completions: bool,
1315 scroll: Option<Autoscroll>,
1316}
1317
1318impl Default for SelectionEffects {
1319 fn default() -> Self {
1320 Self {
1321 nav_history: None,
1322 completions: true,
1323 scroll: Some(Autoscroll::fit()),
1324 }
1325 }
1326}
1327impl SelectionEffects {
1328 pub fn scroll(scroll: Autoscroll) -> Self {
1329 Self {
1330 scroll: Some(scroll),
1331 ..Default::default()
1332 }
1333 }
1334
1335 pub fn no_scroll() -> Self {
1336 Self {
1337 scroll: None,
1338 ..Default::default()
1339 }
1340 }
1341
1342 pub fn completions(self, completions: bool) -> Self {
1343 Self {
1344 completions,
1345 ..self
1346 }
1347 }
1348
1349 pub fn nav_history(self, nav_history: bool) -> Self {
1350 Self {
1351 nav_history: Some(nav_history),
1352 ..self
1353 }
1354 }
1355}
1356
1357struct DeferredSelectionEffectsState {
1358 changed: bool,
1359 effects: SelectionEffects,
1360 old_cursor_position: Anchor,
1361 history_entry: SelectionHistoryEntry,
1362}
1363
1364#[derive(Default)]
1365struct SelectionHistory {
1366 #[allow(clippy::type_complexity)]
1367 selections_by_transaction:
1368 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1369 mode: SelectionHistoryMode,
1370 undo_stack: VecDeque<SelectionHistoryEntry>,
1371 redo_stack: VecDeque<SelectionHistoryEntry>,
1372}
1373
1374impl SelectionHistory {
1375 #[track_caller]
1376 fn insert_transaction(
1377 &mut self,
1378 transaction_id: TransactionId,
1379 selections: Arc<[Selection<Anchor>]>,
1380 ) {
1381 if selections.is_empty() {
1382 log::error!(
1383 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1384 std::panic::Location::caller()
1385 );
1386 return;
1387 }
1388 self.selections_by_transaction
1389 .insert(transaction_id, (selections, None));
1390 }
1391
1392 #[allow(clippy::type_complexity)]
1393 fn transaction(
1394 &self,
1395 transaction_id: TransactionId,
1396 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1397 self.selections_by_transaction.get(&transaction_id)
1398 }
1399
1400 #[allow(clippy::type_complexity)]
1401 fn transaction_mut(
1402 &mut self,
1403 transaction_id: TransactionId,
1404 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1405 self.selections_by_transaction.get_mut(&transaction_id)
1406 }
1407
1408 fn push(&mut self, entry: SelectionHistoryEntry) {
1409 if !entry.selections.is_empty() {
1410 match self.mode {
1411 SelectionHistoryMode::Normal => {
1412 self.push_undo(entry);
1413 self.redo_stack.clear();
1414 }
1415 SelectionHistoryMode::Undoing => self.push_redo(entry),
1416 SelectionHistoryMode::Redoing => self.push_undo(entry),
1417 SelectionHistoryMode::Skipping => {}
1418 }
1419 }
1420 }
1421
1422 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1423 if self
1424 .undo_stack
1425 .back()
1426 .is_none_or(|e| e.selections != entry.selections)
1427 {
1428 self.undo_stack.push_back(entry);
1429 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1430 self.undo_stack.pop_front();
1431 }
1432 }
1433 }
1434
1435 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1436 if self
1437 .redo_stack
1438 .back()
1439 .is_none_or(|e| e.selections != entry.selections)
1440 {
1441 self.redo_stack.push_back(entry);
1442 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1443 self.redo_stack.pop_front();
1444 }
1445 }
1446 }
1447}
1448
1449#[derive(Clone, Copy)]
1450pub struct RowHighlightOptions {
1451 pub autoscroll: bool,
1452 pub include_gutter: bool,
1453}
1454
1455impl Default for RowHighlightOptions {
1456 fn default() -> Self {
1457 Self {
1458 autoscroll: Default::default(),
1459 include_gutter: true,
1460 }
1461 }
1462}
1463
1464struct RowHighlight {
1465 index: usize,
1466 range: Range<Anchor>,
1467 color: Hsla,
1468 options: RowHighlightOptions,
1469 type_id: TypeId,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsState {
1474 groups: Vec<AddSelectionsGroup>,
1475}
1476
1477#[derive(Clone, Debug)]
1478struct AddSelectionsGroup {
1479 above: bool,
1480 stack: Vec<usize>,
1481}
1482
1483#[derive(Clone)]
1484struct SelectNextState {
1485 query: AhoCorasick,
1486 wordwise: bool,
1487 done: bool,
1488}
1489
1490impl std::fmt::Debug for SelectNextState {
1491 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1492 f.debug_struct(std::any::type_name::<Self>())
1493 .field("wordwise", &self.wordwise)
1494 .field("done", &self.done)
1495 .finish()
1496 }
1497}
1498
1499#[derive(Debug)]
1500struct AutocloseRegion {
1501 selection_id: usize,
1502 range: Range<Anchor>,
1503 pair: BracketPair,
1504}
1505
1506#[derive(Debug)]
1507struct SnippetState {
1508 ranges: Vec<Vec<Range<Anchor>>>,
1509 active_index: usize,
1510 choices: Vec<Option<Vec<String>>>,
1511}
1512
1513#[doc(hidden)]
1514pub struct RenameState {
1515 pub range: Range<Anchor>,
1516 pub old_name: Arc<str>,
1517 pub editor: Entity<Editor>,
1518 block_id: CustomBlockId,
1519}
1520
1521struct InvalidationStack<T>(Vec<T>);
1522
1523struct RegisteredEditPredictionProvider {
1524 provider: Arc<dyn EditPredictionProviderHandle>,
1525 _subscription: Subscription,
1526}
1527
1528#[derive(Debug, PartialEq, Eq)]
1529pub struct ActiveDiagnosticGroup {
1530 pub active_range: Range<Anchor>,
1531 pub active_message: String,
1532 pub group_id: usize,
1533 pub blocks: HashSet<CustomBlockId>,
1534}
1535
1536#[derive(Debug, PartialEq, Eq)]
1537
1538pub(crate) enum ActiveDiagnostic {
1539 None,
1540 All,
1541 Group(ActiveDiagnosticGroup),
1542}
1543
1544#[derive(Serialize, Deserialize, Clone, Debug)]
1545pub struct ClipboardSelection {
1546 /// The number of bytes in this selection.
1547 pub len: usize,
1548 /// Whether this was a full-line selection.
1549 pub is_entire_line: bool,
1550 /// The indentation of the first line when this content was originally copied.
1551 pub first_line_indent: u32,
1552}
1553
1554// selections, scroll behavior, was newest selection reversed
1555type SelectSyntaxNodeHistoryState = (
1556 Box<[Selection<usize>]>,
1557 SelectSyntaxNodeScrollBehavior,
1558 bool,
1559);
1560
1561#[derive(Default)]
1562struct SelectSyntaxNodeHistory {
1563 stack: Vec<SelectSyntaxNodeHistoryState>,
1564 // disable temporarily to allow changing selections without losing the stack
1565 pub disable_clearing: bool,
1566}
1567
1568impl SelectSyntaxNodeHistory {
1569 pub fn try_clear(&mut self) {
1570 if !self.disable_clearing {
1571 self.stack.clear();
1572 }
1573 }
1574
1575 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1576 self.stack.push(selection);
1577 }
1578
1579 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1580 self.stack.pop()
1581 }
1582}
1583
1584enum SelectSyntaxNodeScrollBehavior {
1585 CursorTop,
1586 FitSelection,
1587 CursorBottom,
1588}
1589
1590#[derive(Debug)]
1591pub(crate) struct NavigationData {
1592 cursor_anchor: Anchor,
1593 cursor_position: Point,
1594 scroll_anchor: ScrollAnchor,
1595 scroll_top_row: u32,
1596}
1597
1598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1599pub enum GotoDefinitionKind {
1600 Symbol,
1601 Declaration,
1602 Type,
1603 Implementation,
1604}
1605
1606#[derive(Debug, Clone)]
1607enum InlayHintRefreshReason {
1608 ModifiersChanged(bool),
1609 Toggle(bool),
1610 SettingsChange(InlayHintSettings),
1611 NewLinesShown,
1612 BufferEdited(HashSet<Arc<Language>>),
1613 RefreshRequested,
1614 ExcerptsRemoved(Vec<ExcerptId>),
1615}
1616
1617impl InlayHintRefreshReason {
1618 fn description(&self) -> &'static str {
1619 match self {
1620 Self::ModifiersChanged(_) => "modifiers changed",
1621 Self::Toggle(_) => "toggle",
1622 Self::SettingsChange(_) => "settings change",
1623 Self::NewLinesShown => "new lines shown",
1624 Self::BufferEdited(_) => "buffer edited",
1625 Self::RefreshRequested => "refresh requested",
1626 Self::ExcerptsRemoved(_) => "excerpts removed",
1627 }
1628 }
1629}
1630
1631pub enum FormatTarget {
1632 Buffers(HashSet<Entity<Buffer>>),
1633 Ranges(Vec<Range<MultiBufferPoint>>),
1634}
1635
1636pub(crate) struct FocusedBlock {
1637 id: BlockId,
1638 focus_handle: WeakFocusHandle,
1639}
1640
1641#[derive(Clone)]
1642enum JumpData {
1643 MultiBufferRow {
1644 row: MultiBufferRow,
1645 line_offset_from_top: u32,
1646 },
1647 MultiBufferPoint {
1648 excerpt_id: ExcerptId,
1649 position: Point,
1650 anchor: text::Anchor,
1651 line_offset_from_top: u32,
1652 },
1653}
1654
1655pub enum MultibufferSelectionMode {
1656 First,
1657 All,
1658}
1659
1660#[derive(Clone, Copy, Debug, Default)]
1661pub struct RewrapOptions {
1662 pub override_language_settings: bool,
1663 pub preserve_existing_whitespace: bool,
1664}
1665
1666impl Editor {
1667 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1671 }
1672
1673 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::full(), buffer, None, window, cx)
1677 }
1678
1679 pub fn auto_height(
1680 min_lines: usize,
1681 max_lines: usize,
1682 window: &mut Window,
1683 cx: &mut Context<Self>,
1684 ) -> Self {
1685 let buffer = cx.new(|cx| Buffer::local("", cx));
1686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1687 Self::new(
1688 EditorMode::AutoHeight {
1689 min_lines,
1690 max_lines: Some(max_lines),
1691 },
1692 buffer,
1693 None,
1694 window,
1695 cx,
1696 )
1697 }
1698
1699 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1700 /// The editor grows as tall as needed to fit its content.
1701 pub fn auto_height_unbounded(
1702 min_lines: usize,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 let buffer = cx.new(|cx| Buffer::local("", cx));
1707 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1708 Self::new(
1709 EditorMode::AutoHeight {
1710 min_lines,
1711 max_lines: None,
1712 },
1713 buffer,
1714 None,
1715 window,
1716 cx,
1717 )
1718 }
1719
1720 pub fn for_buffer(
1721 buffer: Entity<Buffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1727 Self::new(EditorMode::full(), buffer, project, window, cx)
1728 }
1729
1730 pub fn for_multibuffer(
1731 buffer: Entity<MultiBuffer>,
1732 project: Option<Entity<Project>>,
1733 window: &mut Window,
1734 cx: &mut Context<Self>,
1735 ) -> Self {
1736 Self::new(EditorMode::full(), buffer, project, window, cx)
1737 }
1738
1739 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1740 let mut clone = Self::new(
1741 self.mode.clone(),
1742 self.buffer.clone(),
1743 self.project.clone(),
1744 window,
1745 cx,
1746 );
1747 self.display_map.update(cx, |display_map, cx| {
1748 let snapshot = display_map.snapshot(cx);
1749 clone.display_map.update(cx, |display_map, cx| {
1750 display_map.set_state(&snapshot, cx);
1751 });
1752 });
1753 clone.folds_did_change(cx);
1754 clone.selections.clone_state(&self.selections);
1755 clone.scroll_manager.clone_state(&self.scroll_manager);
1756 clone.searchable = self.searchable;
1757 clone.read_only = self.read_only;
1758 clone
1759 }
1760
1761 pub fn new(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 window: &mut Window,
1766 cx: &mut Context<Self>,
1767 ) -> Self {
1768 Editor::new_internal(mode, buffer, project, None, window, cx)
1769 }
1770
1771 fn new_internal(
1772 mode: EditorMode,
1773 buffer: Entity<MultiBuffer>,
1774 project: Option<Entity<Project>>,
1775 display_map: Option<Entity<DisplayMap>>,
1776 window: &mut Window,
1777 cx: &mut Context<Self>,
1778 ) -> Self {
1779 debug_assert!(
1780 display_map.is_none() || mode.is_minimap(),
1781 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1782 );
1783
1784 let full_mode = mode.is_full();
1785 let is_minimap = mode.is_minimap();
1786 let diagnostics_max_severity = if full_mode {
1787 EditorSettings::get_global(cx)
1788 .diagnostics_max_severity
1789 .unwrap_or(DiagnosticSeverity::Hint)
1790 } else {
1791 DiagnosticSeverity::Off
1792 };
1793 let style = window.text_style();
1794 let font_size = style.font_size.to_pixels(window.rem_size());
1795 let editor = cx.entity().downgrade();
1796 let fold_placeholder = FoldPlaceholder {
1797 constrain_width: true,
1798 render: Arc::new(move |fold_id, fold_range, cx| {
1799 let editor = editor.clone();
1800 div()
1801 .id(fold_id)
1802 .bg(cx.theme().colors().ghost_element_background)
1803 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1804 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1805 .rounded_xs()
1806 .size_full()
1807 .cursor_pointer()
1808 .child("⋯")
1809 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1810 .on_click(move |_, _window, cx| {
1811 editor
1812 .update(cx, |editor, cx| {
1813 editor.unfold_ranges(
1814 &[fold_range.start..fold_range.end],
1815 true,
1816 false,
1817 cx,
1818 );
1819 cx.stop_propagation();
1820 })
1821 .ok();
1822 })
1823 .into_any()
1824 }),
1825 merge_adjacent: true,
1826 ..FoldPlaceholder::default()
1827 };
1828 let display_map = display_map.unwrap_or_else(|| {
1829 cx.new(|cx| {
1830 DisplayMap::new(
1831 buffer.clone(),
1832 style.font(),
1833 font_size,
1834 None,
1835 FILE_HEADER_HEIGHT,
1836 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1837 fold_placeholder,
1838 diagnostics_max_severity,
1839 cx,
1840 )
1841 })
1842 });
1843
1844 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1845
1846 let blink_manager = cx.new(|cx| {
1847 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1848 if is_minimap {
1849 blink_manager.disable(cx);
1850 }
1851 blink_manager
1852 });
1853
1854 let soft_wrap_mode_override =
1855 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1856
1857 let mut project_subscriptions = Vec::new();
1858 if full_mode && let Some(project) = project.as_ref() {
1859 project_subscriptions.push(cx.subscribe_in(
1860 project,
1861 window,
1862 |editor, _, event, window, cx| match event {
1863 project::Event::RefreshCodeLens => {
1864 // we always query lens with actions, without storing them, always refreshing them
1865 }
1866 project::Event::RefreshInlayHints => {
1867 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1868 }
1869 project::Event::LanguageServerAdded(..)
1870 | project::Event::LanguageServerRemoved(..) => {
1871 if editor.tasks_update_task.is_none() {
1872 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1873 }
1874 }
1875 project::Event::SnippetEdit(id, snippet_edits) => {
1876 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1877 let focus_handle = editor.focus_handle(cx);
1878 if focus_handle.is_focused(window) {
1879 let snapshot = buffer.read(cx).snapshot();
1880 for (range, snippet) in snippet_edits {
1881 let editor_range =
1882 language::range_from_lsp(*range).to_offset(&snapshot);
1883 editor
1884 .insert_snippet(
1885 &[editor_range],
1886 snippet.clone(),
1887 window,
1888 cx,
1889 )
1890 .ok();
1891 }
1892 }
1893 }
1894 }
1895 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1896 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1897 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1898 }
1899 }
1900
1901 project::Event::EntryRenamed(transaction) => {
1902 let Some(workspace) = editor.workspace() else {
1903 return;
1904 };
1905 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1906 else {
1907 return;
1908 };
1909 if active_editor.entity_id() == cx.entity_id() {
1910 let edited_buffers_already_open = {
1911 let other_editors: Vec<Entity<Editor>> = workspace
1912 .read(cx)
1913 .panes()
1914 .iter()
1915 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1916 .filter(|editor| editor.entity_id() != cx.entity_id())
1917 .collect();
1918
1919 transaction.0.keys().all(|buffer| {
1920 other_editors.iter().any(|editor| {
1921 let multi_buffer = editor.read(cx).buffer();
1922 multi_buffer.read(cx).is_singleton()
1923 && multi_buffer.read(cx).as_singleton().map_or(
1924 false,
1925 |singleton| {
1926 singleton.entity_id() == buffer.entity_id()
1927 },
1928 )
1929 })
1930 })
1931 };
1932
1933 if !edited_buffers_already_open {
1934 let workspace = workspace.downgrade();
1935 let transaction = transaction.clone();
1936 cx.defer_in(window, move |_, window, cx| {
1937 cx.spawn_in(window, async move |editor, cx| {
1938 Self::open_project_transaction(
1939 &editor,
1940 workspace,
1941 transaction,
1942 "Rename".to_string(),
1943 cx,
1944 )
1945 .await
1946 .ok()
1947 })
1948 .detach();
1949 });
1950 }
1951 }
1952 }
1953
1954 _ => {}
1955 },
1956 ));
1957 if let Some(task_inventory) = project
1958 .read(cx)
1959 .task_store()
1960 .read(cx)
1961 .task_inventory()
1962 .cloned()
1963 {
1964 project_subscriptions.push(cx.observe_in(
1965 &task_inventory,
1966 window,
1967 |editor, _, window, cx| {
1968 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1969 },
1970 ));
1971 };
1972
1973 project_subscriptions.push(cx.subscribe_in(
1974 &project.read(cx).breakpoint_store(),
1975 window,
1976 |editor, _, event, window, cx| match event {
1977 BreakpointStoreEvent::ClearDebugLines => {
1978 editor.clear_row_highlights::<ActiveDebugLine>();
1979 editor.refresh_inline_values(cx);
1980 }
1981 BreakpointStoreEvent::SetDebugLine => {
1982 if editor.go_to_active_debug_line(window, cx) {
1983 cx.stop_propagation();
1984 }
1985
1986 editor.refresh_inline_values(cx);
1987 }
1988 _ => {}
1989 },
1990 ));
1991 let git_store = project.read(cx).git_store().clone();
1992 let project = project.clone();
1993 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1994 if let GitStoreEvent::RepositoryUpdated(
1995 _,
1996 RepositoryEvent::Updated {
1997 new_instance: true, ..
1998 },
1999 _,
2000 ) = event
2001 {
2002 this.load_diff_task = Some(
2003 update_uncommitted_diff_for_buffer(
2004 cx.entity(),
2005 &project,
2006 this.buffer.read(cx).all_buffers(),
2007 this.buffer.clone(),
2008 cx,
2009 )
2010 .shared(),
2011 );
2012 }
2013 }));
2014 }
2015
2016 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2017
2018 let inlay_hint_settings =
2019 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2020 let focus_handle = cx.focus_handle();
2021 if !is_minimap {
2022 cx.on_focus(&focus_handle, window, Self::handle_focus)
2023 .detach();
2024 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2025 .detach();
2026 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2027 .detach();
2028 cx.on_blur(&focus_handle, window, Self::handle_blur)
2029 .detach();
2030 cx.observe_pending_input(window, Self::observe_pending_input)
2031 .detach();
2032 }
2033
2034 let show_indent_guides =
2035 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2036 Some(false)
2037 } else {
2038 None
2039 };
2040
2041 let breakpoint_store = match (&mode, project.as_ref()) {
2042 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2043 _ => None,
2044 };
2045
2046 let mut code_action_providers = Vec::new();
2047 let mut load_uncommitted_diff = None;
2048 if let Some(project) = project.clone() {
2049 load_uncommitted_diff = Some(
2050 update_uncommitted_diff_for_buffer(
2051 cx.entity(),
2052 &project,
2053 buffer.read(cx).all_buffers(),
2054 buffer.clone(),
2055 cx,
2056 )
2057 .shared(),
2058 );
2059 code_action_providers.push(Rc::new(project) as Rc<_>);
2060 }
2061
2062 let mut editor = Self {
2063 focus_handle,
2064 show_cursor_when_unfocused: false,
2065 last_focused_descendant: None,
2066 buffer: buffer.clone(),
2067 display_map: display_map.clone(),
2068 selections,
2069 scroll_manager: ScrollManager::new(cx),
2070 columnar_selection_state: None,
2071 add_selections_state: None,
2072 select_next_state: None,
2073 select_prev_state: None,
2074 selection_history: SelectionHistory::default(),
2075 defer_selection_effects: false,
2076 deferred_selection_effects_state: None,
2077 autoclose_regions: Vec::new(),
2078 snippet_stack: InvalidationStack::default(),
2079 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2080 ime_transaction: None,
2081 active_diagnostics: ActiveDiagnostic::None,
2082 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2083 inline_diagnostics_update: Task::ready(()),
2084 inline_diagnostics: Vec::new(),
2085 soft_wrap_mode_override,
2086 diagnostics_max_severity,
2087 hard_wrap: None,
2088 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2089 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2090 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2091 project,
2092 blink_manager: blink_manager.clone(),
2093 show_local_selections: true,
2094 show_scrollbars: ScrollbarAxes {
2095 horizontal: full_mode,
2096 vertical: full_mode,
2097 },
2098 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2099 offset_content: !matches!(mode, EditorMode::SingleLine),
2100 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2101 show_gutter: full_mode,
2102 show_line_numbers: (!full_mode).then_some(false),
2103 use_relative_line_numbers: None,
2104 disable_expand_excerpt_buttons: !full_mode,
2105 show_git_diff_gutter: None,
2106 show_code_actions: None,
2107 show_runnables: None,
2108 show_breakpoints: None,
2109 show_wrap_guides: None,
2110 show_indent_guides,
2111 placeholder_text: None,
2112 highlight_order: 0,
2113 highlighted_rows: HashMap::default(),
2114 background_highlights: TreeMap::default(),
2115 gutter_highlights: TreeMap::default(),
2116 scrollbar_marker_state: ScrollbarMarkerState::default(),
2117 active_indent_guides_state: ActiveIndentGuidesState::default(),
2118 nav_history: None,
2119 context_menu: RefCell::new(None),
2120 context_menu_options: None,
2121 mouse_context_menu: None,
2122 completion_tasks: Vec::new(),
2123 inline_blame_popover: None,
2124 inline_blame_popover_show_task: None,
2125 signature_help_state: SignatureHelpState::default(),
2126 auto_signature_help: None,
2127 find_all_references_task_sources: Vec::new(),
2128 next_completion_id: 0,
2129 next_inlay_id: 0,
2130 code_action_providers,
2131 available_code_actions: None,
2132 code_actions_task: None,
2133 quick_selection_highlight_task: None,
2134 debounced_selection_highlight_task: None,
2135 document_highlights_task: None,
2136 linked_editing_range_task: None,
2137 pending_rename: None,
2138 searchable: !is_minimap,
2139 cursor_shape: EditorSettings::get_global(cx)
2140 .cursor_shape
2141 .unwrap_or_default(),
2142 current_line_highlight: None,
2143 autoindent_mode: Some(AutoindentMode::EachLine),
2144 collapse_matches: false,
2145 workspace: None,
2146 input_enabled: !is_minimap,
2147 use_modal_editing: full_mode,
2148 read_only: is_minimap,
2149 use_autoclose: true,
2150 use_auto_surround: true,
2151 auto_replace_emoji_shortcode: false,
2152 jsx_tag_auto_close_enabled_in_any_buffer: false,
2153 leader_id: None,
2154 remote_id: None,
2155 hover_state: HoverState::default(),
2156 pending_mouse_down: None,
2157 hovered_link_state: None,
2158 edit_prediction_provider: None,
2159 active_edit_prediction: None,
2160 stale_edit_prediction_in_menu: None,
2161 edit_prediction_preview: EditPredictionPreview::Inactive {
2162 released_too_fast: false,
2163 },
2164 inline_diagnostics_enabled: full_mode,
2165 diagnostics_enabled: full_mode,
2166 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2167 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2168 gutter_hovered: false,
2169 pixel_position_of_newest_cursor: None,
2170 last_bounds: None,
2171 last_position_map: None,
2172 expect_bounds_change: None,
2173 gutter_dimensions: GutterDimensions::default(),
2174 style: None,
2175 show_cursor_names: false,
2176 hovered_cursors: HashMap::default(),
2177 next_editor_action_id: EditorActionId::default(),
2178 editor_actions: Rc::default(),
2179 edit_predictions_hidden_for_vim_mode: false,
2180 show_edit_predictions_override: None,
2181 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2182 edit_prediction_settings: EditPredictionSettings::Disabled,
2183 edit_prediction_indent_conflict: false,
2184 edit_prediction_requires_modifier_in_indent_conflict: true,
2185 custom_context_menu: None,
2186 show_git_blame_gutter: false,
2187 show_git_blame_inline: false,
2188 show_selection_menu: None,
2189 show_git_blame_inline_delay_task: None,
2190 git_blame_inline_enabled: full_mode
2191 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2192 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2193 serialize_dirty_buffers: !is_minimap
2194 && ProjectSettings::get_global(cx)
2195 .session
2196 .restore_unsaved_buffers,
2197 blame: None,
2198 blame_subscription: None,
2199 tasks: BTreeMap::default(),
2200
2201 breakpoint_store,
2202 gutter_breakpoint_indicator: (None, None),
2203 hovered_diff_hunk_row: None,
2204 _subscriptions: (!is_minimap)
2205 .then(|| {
2206 vec![
2207 cx.observe(&buffer, Self::on_buffer_changed),
2208 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2209 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2210 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2211 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2212 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2213 cx.observe_window_activation(window, |editor, window, cx| {
2214 let active = window.is_window_active();
2215 editor.blink_manager.update(cx, |blink_manager, cx| {
2216 if active {
2217 blink_manager.enable(cx);
2218 } else {
2219 blink_manager.disable(cx);
2220 }
2221 });
2222 if active {
2223 editor.show_mouse_cursor(cx);
2224 }
2225 }),
2226 ]
2227 })
2228 .unwrap_or_default(),
2229 tasks_update_task: None,
2230 pull_diagnostics_task: Task::ready(()),
2231 colors: None,
2232 next_color_inlay_id: 0,
2233 linked_edit_ranges: Default::default(),
2234 in_project_search: false,
2235 previous_search_ranges: None,
2236 breadcrumb_header: None,
2237 focused_block: None,
2238 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2239 addons: HashMap::default(),
2240 registered_buffers: HashMap::default(),
2241 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2242 selection_mark_mode: false,
2243 toggle_fold_multiple_buffers: Task::ready(()),
2244 serialize_selections: Task::ready(()),
2245 serialize_folds: Task::ready(()),
2246 text_style_refinement: None,
2247 load_diff_task: load_uncommitted_diff,
2248 temporary_diff_override: false,
2249 mouse_cursor_hidden: false,
2250 minimap: None,
2251 hide_mouse_mode: EditorSettings::get_global(cx)
2252 .hide_mouse
2253 .unwrap_or_default(),
2254 change_list: ChangeList::new(),
2255 mode,
2256 selection_drag_state: SelectionDragState::None,
2257 folding_newlines: Task::ready(()),
2258 };
2259
2260 if is_minimap {
2261 return editor;
2262 }
2263
2264 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2265 editor
2266 ._subscriptions
2267 .push(cx.observe(breakpoints, |_, _, cx| {
2268 cx.notify();
2269 }));
2270 }
2271 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2272 editor._subscriptions.extend(project_subscriptions);
2273
2274 editor._subscriptions.push(cx.subscribe_in(
2275 &cx.entity(),
2276 window,
2277 |editor, _, e: &EditorEvent, window, cx| match e {
2278 EditorEvent::ScrollPositionChanged { local, .. } => {
2279 if *local {
2280 let new_anchor = editor.scroll_manager.anchor();
2281 let snapshot = editor.snapshot(window, cx);
2282 editor.update_restoration_data(cx, move |data| {
2283 data.scroll_position = (
2284 new_anchor.top_row(&snapshot.buffer_snapshot),
2285 new_anchor.offset,
2286 );
2287 });
2288 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2289 editor.inline_blame_popover.take();
2290 }
2291 }
2292 EditorEvent::Edited { .. } => {
2293 if !vim_enabled(cx) {
2294 let (map, selections) = editor.selections.all_adjusted_display(cx);
2295 let pop_state = editor
2296 .change_list
2297 .last()
2298 .map(|previous| {
2299 previous.len() == selections.len()
2300 && previous.iter().enumerate().all(|(ix, p)| {
2301 p.to_display_point(&map).row()
2302 == selections[ix].head().row()
2303 })
2304 })
2305 .unwrap_or(false);
2306 let new_positions = selections
2307 .into_iter()
2308 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2309 .collect();
2310 editor
2311 .change_list
2312 .push_to_change_list(pop_state, new_positions);
2313 }
2314 }
2315 _ => (),
2316 },
2317 ));
2318
2319 if let Some(dap_store) = editor
2320 .project
2321 .as_ref()
2322 .map(|project| project.read(cx).dap_store())
2323 {
2324 let weak_editor = cx.weak_entity();
2325
2326 editor
2327 ._subscriptions
2328 .push(
2329 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2330 let session_entity = cx.entity();
2331 weak_editor
2332 .update(cx, |editor, cx| {
2333 editor._subscriptions.push(
2334 cx.subscribe(&session_entity, Self::on_debug_session_event),
2335 );
2336 })
2337 .ok();
2338 }),
2339 );
2340
2341 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2342 editor
2343 ._subscriptions
2344 .push(cx.subscribe(&session, Self::on_debug_session_event));
2345 }
2346 }
2347
2348 // skip adding the initial selection to selection history
2349 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2350 editor.end_selection(window, cx);
2351 editor.selection_history.mode = SelectionHistoryMode::Normal;
2352
2353 editor.scroll_manager.show_scrollbars(window, cx);
2354 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2355
2356 if full_mode {
2357 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2358 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2359
2360 if editor.git_blame_inline_enabled {
2361 editor.start_git_blame_inline(false, window, cx);
2362 }
2363
2364 editor.go_to_active_debug_line(window, cx);
2365
2366 if let Some(buffer) = buffer.read(cx).as_singleton()
2367 && let Some(project) = editor.project()
2368 {
2369 let handle = project.update(cx, |project, cx| {
2370 project.register_buffer_with_language_servers(&buffer, cx)
2371 });
2372 editor
2373 .registered_buffers
2374 .insert(buffer.read(cx).remote_id(), handle);
2375 }
2376
2377 editor.minimap =
2378 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2379 editor.colors = Some(LspColorData::new(cx));
2380 editor.update_lsp_data(false, None, window, cx);
2381 }
2382
2383 if editor.mode.is_full() {
2384 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2385 }
2386
2387 editor
2388 }
2389
2390 pub fn deploy_mouse_context_menu(
2391 &mut self,
2392 position: gpui::Point<Pixels>,
2393 context_menu: Entity<ContextMenu>,
2394 window: &mut Window,
2395 cx: &mut Context<Self>,
2396 ) {
2397 self.mouse_context_menu = Some(MouseContextMenu::new(
2398 self,
2399 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2400 context_menu,
2401 window,
2402 cx,
2403 ));
2404 }
2405
2406 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2407 self.mouse_context_menu
2408 .as_ref()
2409 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2410 }
2411
2412 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2413 if self
2414 .selections
2415 .pending
2416 .as_ref()
2417 .is_some_and(|pending_selection| {
2418 let snapshot = self.buffer().read(cx).snapshot(cx);
2419 pending_selection
2420 .selection
2421 .range()
2422 .includes(range, &snapshot)
2423 })
2424 {
2425 return true;
2426 }
2427
2428 self.selections
2429 .disjoint_in_range::<usize>(range.clone(), cx)
2430 .into_iter()
2431 .any(|selection| {
2432 // This is needed to cover a corner case, if we just check for an existing
2433 // selection in the fold range, having a cursor at the start of the fold
2434 // marks it as selected. Non-empty selections don't cause this.
2435 let length = selection.end - selection.start;
2436 length > 0
2437 })
2438 }
2439
2440 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2441 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2442 }
2443
2444 fn key_context_internal(
2445 &self,
2446 has_active_edit_prediction: bool,
2447 window: &Window,
2448 cx: &App,
2449 ) -> KeyContext {
2450 let mut key_context = KeyContext::new_with_defaults();
2451 key_context.add("Editor");
2452 let mode = match self.mode {
2453 EditorMode::SingleLine => "single_line",
2454 EditorMode::AutoHeight { .. } => "auto_height",
2455 EditorMode::Minimap { .. } => "minimap",
2456 EditorMode::Full { .. } => "full",
2457 };
2458
2459 if EditorSettings::jupyter_enabled(cx) {
2460 key_context.add("jupyter");
2461 }
2462
2463 key_context.set("mode", mode);
2464 if self.pending_rename.is_some() {
2465 key_context.add("renaming");
2466 }
2467
2468 match self.context_menu.borrow().as_ref() {
2469 Some(CodeContextMenu::Completions(menu)) => {
2470 if menu.visible() {
2471 key_context.add("menu");
2472 key_context.add("showing_completions");
2473 }
2474 }
2475 Some(CodeContextMenu::CodeActions(menu)) => {
2476 if menu.visible() {
2477 key_context.add("menu");
2478 key_context.add("showing_code_actions")
2479 }
2480 }
2481 None => {}
2482 }
2483
2484 if self.signature_help_state.has_multiple_signatures() {
2485 key_context.add("showing_signature_help");
2486 }
2487
2488 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2489 if !self.focus_handle(cx).contains_focused(window, cx)
2490 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2491 {
2492 for addon in self.addons.values() {
2493 addon.extend_key_context(&mut key_context, cx)
2494 }
2495 }
2496
2497 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2498 if let Some(extension) = singleton_buffer
2499 .read(cx)
2500 .file()
2501 .and_then(|file| file.path().extension()?.to_str())
2502 {
2503 key_context.set("extension", extension.to_string());
2504 }
2505 } else {
2506 key_context.add("multibuffer");
2507 }
2508
2509 if has_active_edit_prediction {
2510 if self.edit_prediction_in_conflict() {
2511 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2512 } else {
2513 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2514 key_context.add("copilot_suggestion");
2515 }
2516 }
2517
2518 if self.selection_mark_mode {
2519 key_context.add("selection_mode");
2520 }
2521
2522 key_context
2523 }
2524
2525 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2526 if self.mouse_cursor_hidden {
2527 self.mouse_cursor_hidden = false;
2528 cx.notify();
2529 }
2530 }
2531
2532 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2533 let hide_mouse_cursor = match origin {
2534 HideMouseCursorOrigin::TypingAction => {
2535 matches!(
2536 self.hide_mouse_mode,
2537 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2538 )
2539 }
2540 HideMouseCursorOrigin::MovementAction => {
2541 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2542 }
2543 };
2544 if self.mouse_cursor_hidden != hide_mouse_cursor {
2545 self.mouse_cursor_hidden = hide_mouse_cursor;
2546 cx.notify();
2547 }
2548 }
2549
2550 pub fn edit_prediction_in_conflict(&self) -> bool {
2551 if !self.show_edit_predictions_in_menu() {
2552 return false;
2553 }
2554
2555 let showing_completions = self
2556 .context_menu
2557 .borrow()
2558 .as_ref()
2559 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2560
2561 showing_completions
2562 || self.edit_prediction_requires_modifier()
2563 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2564 // bindings to insert tab characters.
2565 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2566 }
2567
2568 pub fn accept_edit_prediction_keybind(
2569 &self,
2570 accept_partial: bool,
2571 window: &Window,
2572 cx: &App,
2573 ) -> AcceptEditPredictionBinding {
2574 let key_context = self.key_context_internal(true, window, cx);
2575 let in_conflict = self.edit_prediction_in_conflict();
2576
2577 let bindings = if accept_partial {
2578 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2579 } else {
2580 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2581 };
2582
2583 // TODO: if the binding contains multiple keystrokes, display all of them, not
2584 // just the first one.
2585 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2586 !in_conflict
2587 || binding
2588 .keystrokes()
2589 .first()
2590 .is_some_and(|keystroke| keystroke.modifiers().modified())
2591 }))
2592 }
2593
2594 pub fn new_file(
2595 workspace: &mut Workspace,
2596 _: &workspace::NewFile,
2597 window: &mut Window,
2598 cx: &mut Context<Workspace>,
2599 ) {
2600 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2601 "Failed to create buffer",
2602 window,
2603 cx,
2604 |e, _, _| match e.error_code() {
2605 ErrorCode::RemoteUpgradeRequired => Some(format!(
2606 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2607 e.error_tag("required").unwrap_or("the latest version")
2608 )),
2609 _ => None,
2610 },
2611 );
2612 }
2613
2614 pub fn new_in_workspace(
2615 workspace: &mut Workspace,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) -> Task<Result<Entity<Editor>>> {
2619 let project = workspace.project().clone();
2620 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2621
2622 cx.spawn_in(window, async move |workspace, cx| {
2623 let buffer = create.await?;
2624 workspace.update_in(cx, |workspace, window, cx| {
2625 let editor =
2626 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2627 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2628 editor
2629 })
2630 })
2631 }
2632
2633 fn new_file_vertical(
2634 workspace: &mut Workspace,
2635 _: &workspace::NewFileSplitVertical,
2636 window: &mut Window,
2637 cx: &mut Context<Workspace>,
2638 ) {
2639 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2640 }
2641
2642 fn new_file_horizontal(
2643 workspace: &mut Workspace,
2644 _: &workspace::NewFileSplitHorizontal,
2645 window: &mut Window,
2646 cx: &mut Context<Workspace>,
2647 ) {
2648 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2649 }
2650
2651 fn new_file_in_direction(
2652 workspace: &mut Workspace,
2653 direction: SplitDirection,
2654 window: &mut Window,
2655 cx: &mut Context<Workspace>,
2656 ) {
2657 let project = workspace.project().clone();
2658 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2659
2660 cx.spawn_in(window, async move |workspace, cx| {
2661 let buffer = create.await?;
2662 workspace.update_in(cx, move |workspace, window, cx| {
2663 workspace.split_item(
2664 direction,
2665 Box::new(
2666 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2667 ),
2668 window,
2669 cx,
2670 )
2671 })?;
2672 anyhow::Ok(())
2673 })
2674 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2675 match e.error_code() {
2676 ErrorCode::RemoteUpgradeRequired => Some(format!(
2677 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2678 e.error_tag("required").unwrap_or("the latest version")
2679 )),
2680 _ => None,
2681 }
2682 });
2683 }
2684
2685 pub fn leader_id(&self) -> Option<CollaboratorId> {
2686 self.leader_id
2687 }
2688
2689 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2690 &self.buffer
2691 }
2692
2693 pub fn project(&self) -> Option<&Entity<Project>> {
2694 self.project.as_ref()
2695 }
2696
2697 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2698 self.workspace.as_ref()?.0.upgrade()
2699 }
2700
2701 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2702 self.buffer().read(cx).title(cx)
2703 }
2704
2705 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2706 let git_blame_gutter_max_author_length = self
2707 .render_git_blame_gutter(cx)
2708 .then(|| {
2709 if let Some(blame) = self.blame.as_ref() {
2710 let max_author_length =
2711 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2712 Some(max_author_length)
2713 } else {
2714 None
2715 }
2716 })
2717 .flatten();
2718
2719 EditorSnapshot {
2720 mode: self.mode.clone(),
2721 show_gutter: self.show_gutter,
2722 show_line_numbers: self.show_line_numbers,
2723 show_git_diff_gutter: self.show_git_diff_gutter,
2724 show_code_actions: self.show_code_actions,
2725 show_runnables: self.show_runnables,
2726 show_breakpoints: self.show_breakpoints,
2727 git_blame_gutter_max_author_length,
2728 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2729 scroll_anchor: self.scroll_manager.anchor(),
2730 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2731 placeholder_text: self.placeholder_text.clone(),
2732 is_focused: self.focus_handle.is_focused(window),
2733 current_line_highlight: self
2734 .current_line_highlight
2735 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2736 gutter_hovered: self.gutter_hovered,
2737 }
2738 }
2739
2740 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2741 self.buffer.read(cx).language_at(point, cx)
2742 }
2743
2744 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2745 self.buffer.read(cx).read(cx).file_at(point).cloned()
2746 }
2747
2748 pub fn active_excerpt(
2749 &self,
2750 cx: &App,
2751 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2752 self.buffer
2753 .read(cx)
2754 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2755 }
2756
2757 pub fn mode(&self) -> &EditorMode {
2758 &self.mode
2759 }
2760
2761 pub fn set_mode(&mut self, mode: EditorMode) {
2762 self.mode = mode;
2763 }
2764
2765 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2766 self.collaboration_hub.as_deref()
2767 }
2768
2769 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2770 self.collaboration_hub = Some(hub);
2771 }
2772
2773 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2774 self.in_project_search = in_project_search;
2775 }
2776
2777 pub fn set_custom_context_menu(
2778 &mut self,
2779 f: impl 'static
2780 + Fn(
2781 &mut Self,
2782 DisplayPoint,
2783 &mut Window,
2784 &mut Context<Self>,
2785 ) -> Option<Entity<ui::ContextMenu>>,
2786 ) {
2787 self.custom_context_menu = Some(Box::new(f))
2788 }
2789
2790 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2791 self.completion_provider = provider;
2792 }
2793
2794 #[cfg(any(test, feature = "test-support"))]
2795 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2796 self.completion_provider.clone()
2797 }
2798
2799 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2800 self.semantics_provider.clone()
2801 }
2802
2803 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2804 self.semantics_provider = provider;
2805 }
2806
2807 pub fn set_edit_prediction_provider<T>(
2808 &mut self,
2809 provider: Option<Entity<T>>,
2810 window: &mut Window,
2811 cx: &mut Context<Self>,
2812 ) where
2813 T: EditPredictionProvider,
2814 {
2815 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2816 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2817 if this.focus_handle.is_focused(window) {
2818 this.update_visible_edit_prediction(window, cx);
2819 }
2820 }),
2821 provider: Arc::new(provider),
2822 });
2823 self.update_edit_prediction_settings(cx);
2824 self.refresh_edit_prediction(false, false, window, cx);
2825 }
2826
2827 pub fn placeholder_text(&self) -> Option<&str> {
2828 self.placeholder_text.as_deref()
2829 }
2830
2831 pub fn set_placeholder_text(
2832 &mut self,
2833 placeholder_text: impl Into<Arc<str>>,
2834 cx: &mut Context<Self>,
2835 ) {
2836 let placeholder_text = Some(placeholder_text.into());
2837 if self.placeholder_text != placeholder_text {
2838 self.placeholder_text = placeholder_text;
2839 cx.notify();
2840 }
2841 }
2842
2843 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2844 self.cursor_shape = cursor_shape;
2845
2846 // Disrupt blink for immediate user feedback that the cursor shape has changed
2847 self.blink_manager.update(cx, BlinkManager::show_cursor);
2848
2849 cx.notify();
2850 }
2851
2852 pub fn set_current_line_highlight(
2853 &mut self,
2854 current_line_highlight: Option<CurrentLineHighlight>,
2855 ) {
2856 self.current_line_highlight = current_line_highlight;
2857 }
2858
2859 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2860 self.collapse_matches = collapse_matches;
2861 }
2862
2863 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2864 let buffers = self.buffer.read(cx).all_buffers();
2865 let Some(project) = self.project.as_ref() else {
2866 return;
2867 };
2868 project.update(cx, |project, cx| {
2869 for buffer in buffers {
2870 self.registered_buffers
2871 .entry(buffer.read(cx).remote_id())
2872 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2873 }
2874 })
2875 }
2876
2877 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2878 if self.collapse_matches {
2879 return range.start..range.start;
2880 }
2881 range.clone()
2882 }
2883
2884 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2885 if self.display_map.read(cx).clip_at_line_ends != clip {
2886 self.display_map
2887 .update(cx, |map, _| map.clip_at_line_ends = clip);
2888 }
2889 }
2890
2891 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2892 self.input_enabled = input_enabled;
2893 }
2894
2895 pub fn set_edit_predictions_hidden_for_vim_mode(
2896 &mut self,
2897 hidden: bool,
2898 window: &mut Window,
2899 cx: &mut Context<Self>,
2900 ) {
2901 if hidden != self.edit_predictions_hidden_for_vim_mode {
2902 self.edit_predictions_hidden_for_vim_mode = hidden;
2903 if hidden {
2904 self.update_visible_edit_prediction(window, cx);
2905 } else {
2906 self.refresh_edit_prediction(true, false, window, cx);
2907 }
2908 }
2909 }
2910
2911 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2912 self.menu_edit_predictions_policy = value;
2913 }
2914
2915 pub fn set_autoindent(&mut self, autoindent: bool) {
2916 if autoindent {
2917 self.autoindent_mode = Some(AutoindentMode::EachLine);
2918 } else {
2919 self.autoindent_mode = None;
2920 }
2921 }
2922
2923 pub fn read_only(&self, cx: &App) -> bool {
2924 self.read_only || self.buffer.read(cx).read_only()
2925 }
2926
2927 pub fn set_read_only(&mut self, read_only: bool) {
2928 self.read_only = read_only;
2929 }
2930
2931 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2932 self.use_autoclose = autoclose;
2933 }
2934
2935 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2936 self.use_auto_surround = auto_surround;
2937 }
2938
2939 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2940 self.auto_replace_emoji_shortcode = auto_replace;
2941 }
2942
2943 pub fn toggle_edit_predictions(
2944 &mut self,
2945 _: &ToggleEditPrediction,
2946 window: &mut Window,
2947 cx: &mut Context<Self>,
2948 ) {
2949 if self.show_edit_predictions_override.is_some() {
2950 self.set_show_edit_predictions(None, window, cx);
2951 } else {
2952 let show_edit_predictions = !self.edit_predictions_enabled();
2953 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2954 }
2955 }
2956
2957 pub fn set_show_edit_predictions(
2958 &mut self,
2959 show_edit_predictions: Option<bool>,
2960 window: &mut Window,
2961 cx: &mut Context<Self>,
2962 ) {
2963 self.show_edit_predictions_override = show_edit_predictions;
2964 self.update_edit_prediction_settings(cx);
2965
2966 if let Some(false) = show_edit_predictions {
2967 self.discard_edit_prediction(false, cx);
2968 } else {
2969 self.refresh_edit_prediction(false, true, window, cx);
2970 }
2971 }
2972
2973 fn edit_predictions_disabled_in_scope(
2974 &self,
2975 buffer: &Entity<Buffer>,
2976 buffer_position: language::Anchor,
2977 cx: &App,
2978 ) -> bool {
2979 let snapshot = buffer.read(cx).snapshot();
2980 let settings = snapshot.settings_at(buffer_position, cx);
2981
2982 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2983 return false;
2984 };
2985
2986 scope.override_name().is_some_and(|scope_name| {
2987 settings
2988 .edit_predictions_disabled_in
2989 .iter()
2990 .any(|s| s == scope_name)
2991 })
2992 }
2993
2994 pub fn set_use_modal_editing(&mut self, to: bool) {
2995 self.use_modal_editing = to;
2996 }
2997
2998 pub fn use_modal_editing(&self) -> bool {
2999 self.use_modal_editing
3000 }
3001
3002 fn selections_did_change(
3003 &mut self,
3004 local: bool,
3005 old_cursor_position: &Anchor,
3006 effects: SelectionEffects,
3007 window: &mut Window,
3008 cx: &mut Context<Self>,
3009 ) {
3010 window.invalidate_character_coordinates();
3011
3012 // Copy selections to primary selection buffer
3013 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3014 if local {
3015 let selections = self.selections.all::<usize>(cx);
3016 let buffer_handle = self.buffer.read(cx).read(cx);
3017
3018 let mut text = String::new();
3019 for (index, selection) in selections.iter().enumerate() {
3020 let text_for_selection = buffer_handle
3021 .text_for_range(selection.start..selection.end)
3022 .collect::<String>();
3023
3024 text.push_str(&text_for_selection);
3025 if index != selections.len() - 1 {
3026 text.push('\n');
3027 }
3028 }
3029
3030 if !text.is_empty() {
3031 cx.write_to_primary(ClipboardItem::new_string(text));
3032 }
3033 }
3034
3035 let selection_anchors = self.selections.disjoint_anchors();
3036
3037 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3038 self.buffer.update(cx, |buffer, cx| {
3039 buffer.set_active_selections(
3040 &selection_anchors,
3041 self.selections.line_mode,
3042 self.cursor_shape,
3043 cx,
3044 )
3045 });
3046 }
3047 let display_map = self
3048 .display_map
3049 .update(cx, |display_map, cx| display_map.snapshot(cx));
3050 let buffer = &display_map.buffer_snapshot;
3051 if self.selections.count() == 1 {
3052 self.add_selections_state = None;
3053 }
3054 self.select_next_state = None;
3055 self.select_prev_state = None;
3056 self.select_syntax_node_history.try_clear();
3057 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3058 self.snippet_stack.invalidate(&selection_anchors, buffer);
3059 self.take_rename(false, window, cx);
3060
3061 let newest_selection = self.selections.newest_anchor();
3062 let new_cursor_position = newest_selection.head();
3063 let selection_start = newest_selection.start;
3064
3065 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3066 self.push_to_nav_history(
3067 *old_cursor_position,
3068 Some(new_cursor_position.to_point(buffer)),
3069 false,
3070 effects.nav_history == Some(true),
3071 cx,
3072 );
3073 }
3074
3075 if local {
3076 if let Some(buffer_id) = new_cursor_position.buffer_id
3077 && !self.registered_buffers.contains_key(&buffer_id)
3078 && let Some(project) = self.project.as_ref()
3079 {
3080 project.update(cx, |project, cx| {
3081 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3082 return;
3083 };
3084 self.registered_buffers.insert(
3085 buffer_id,
3086 project.register_buffer_with_language_servers(&buffer, cx),
3087 );
3088 })
3089 }
3090
3091 let mut context_menu = self.context_menu.borrow_mut();
3092 let completion_menu = match context_menu.as_ref() {
3093 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3094 Some(CodeContextMenu::CodeActions(_)) => {
3095 *context_menu = None;
3096 None
3097 }
3098 None => None,
3099 };
3100 let completion_position = completion_menu.map(|menu| menu.initial_position);
3101 drop(context_menu);
3102
3103 if effects.completions
3104 && let Some(completion_position) = completion_position
3105 {
3106 let start_offset = selection_start.to_offset(buffer);
3107 let position_matches = start_offset == completion_position.to_offset(buffer);
3108 let continue_showing = if position_matches {
3109 if self.snippet_stack.is_empty() {
3110 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3111 } else {
3112 // Snippet choices can be shown even when the cursor is in whitespace.
3113 // Dismissing the menu with actions like backspace is handled by
3114 // invalidation regions.
3115 true
3116 }
3117 } else {
3118 false
3119 };
3120
3121 if continue_showing {
3122 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3123 } else {
3124 self.hide_context_menu(window, cx);
3125 }
3126 }
3127
3128 hide_hover(self, cx);
3129
3130 if old_cursor_position.to_display_point(&display_map).row()
3131 != new_cursor_position.to_display_point(&display_map).row()
3132 {
3133 self.available_code_actions.take();
3134 }
3135 self.refresh_code_actions(window, cx);
3136 self.refresh_document_highlights(cx);
3137 self.refresh_selected_text_highlights(false, window, cx);
3138 refresh_matching_bracket_highlights(self, window, cx);
3139 self.update_visible_edit_prediction(window, cx);
3140 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3141 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3142 self.inline_blame_popover.take();
3143 if self.git_blame_inline_enabled {
3144 self.start_inline_blame_timer(window, cx);
3145 }
3146 }
3147
3148 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3149 cx.emit(EditorEvent::SelectionsChanged { local });
3150
3151 let selections = &self.selections.disjoint;
3152 if selections.len() == 1 {
3153 cx.emit(SearchEvent::ActiveMatchChanged)
3154 }
3155 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3156 let inmemory_selections = selections
3157 .iter()
3158 .map(|s| {
3159 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3160 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3161 })
3162 .collect();
3163 self.update_restoration_data(cx, |data| {
3164 data.selections = inmemory_selections;
3165 });
3166
3167 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3168 && let Some(workspace_id) =
3169 self.workspace.as_ref().and_then(|workspace| workspace.1)
3170 {
3171 let snapshot = self.buffer().read(cx).snapshot(cx);
3172 let selections = selections.clone();
3173 let background_executor = cx.background_executor().clone();
3174 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3175 self.serialize_selections = cx.background_spawn(async move {
3176 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3177 let db_selections = selections
3178 .iter()
3179 .map(|selection| {
3180 (
3181 selection.start.to_offset(&snapshot),
3182 selection.end.to_offset(&snapshot),
3183 )
3184 })
3185 .collect();
3186
3187 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3188 .await
3189 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3190 .log_err();
3191 });
3192 }
3193 }
3194
3195 cx.notify();
3196 }
3197
3198 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3199 use text::ToOffset as _;
3200 use text::ToPoint as _;
3201
3202 if self.mode.is_minimap()
3203 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3204 {
3205 return;
3206 }
3207
3208 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3209 return;
3210 };
3211
3212 let snapshot = singleton.read(cx).snapshot();
3213 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3214 let display_snapshot = display_map.snapshot(cx);
3215
3216 display_snapshot
3217 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3218 .map(|fold| {
3219 fold.range.start.text_anchor.to_point(&snapshot)
3220 ..fold.range.end.text_anchor.to_point(&snapshot)
3221 })
3222 .collect()
3223 });
3224 self.update_restoration_data(cx, |data| {
3225 data.folds = inmemory_folds;
3226 });
3227
3228 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3229 return;
3230 };
3231 let background_executor = cx.background_executor().clone();
3232 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3233 let db_folds = self.display_map.update(cx, |display_map, cx| {
3234 display_map
3235 .snapshot(cx)
3236 .folds_in_range(0..snapshot.len())
3237 .map(|fold| {
3238 (
3239 fold.range.start.text_anchor.to_offset(&snapshot),
3240 fold.range.end.text_anchor.to_offset(&snapshot),
3241 )
3242 })
3243 .collect()
3244 });
3245 self.serialize_folds = cx.background_spawn(async move {
3246 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3247 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3248 .await
3249 .with_context(|| {
3250 format!(
3251 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3252 )
3253 })
3254 .log_err();
3255 });
3256 }
3257
3258 pub fn sync_selections(
3259 &mut self,
3260 other: Entity<Editor>,
3261 cx: &mut Context<Self>,
3262 ) -> gpui::Subscription {
3263 let other_selections = other.read(cx).selections.disjoint.to_vec();
3264 self.selections.change_with(cx, |selections| {
3265 selections.select_anchors(other_selections);
3266 });
3267
3268 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3269 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3270 let other_selections = other.read(cx).selections.disjoint.to_vec();
3271 if other_selections.is_empty() {
3272 return;
3273 }
3274 this.selections.change_with(cx, |selections| {
3275 selections.select_anchors(other_selections);
3276 });
3277 }
3278 });
3279
3280 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3281 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3282 let these_selections = this.selections.disjoint.to_vec();
3283 if these_selections.is_empty() {
3284 return;
3285 }
3286 other.update(cx, |other_editor, cx| {
3287 other_editor.selections.change_with(cx, |selections| {
3288 selections.select_anchors(these_selections);
3289 })
3290 });
3291 }
3292 });
3293
3294 Subscription::join(other_subscription, this_subscription)
3295 }
3296
3297 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3298 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3299 /// effects of selection change occur at the end of the transaction.
3300 pub fn change_selections<R>(
3301 &mut self,
3302 effects: SelectionEffects,
3303 window: &mut Window,
3304 cx: &mut Context<Self>,
3305 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3306 ) -> R {
3307 if let Some(state) = &mut self.deferred_selection_effects_state {
3308 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3309 state.effects.completions = effects.completions;
3310 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3311 let (changed, result) = self.selections.change_with(cx, change);
3312 state.changed |= changed;
3313 return result;
3314 }
3315 let mut state = DeferredSelectionEffectsState {
3316 changed: false,
3317 effects,
3318 old_cursor_position: self.selections.newest_anchor().head(),
3319 history_entry: SelectionHistoryEntry {
3320 selections: self.selections.disjoint_anchors(),
3321 select_next_state: self.select_next_state.clone(),
3322 select_prev_state: self.select_prev_state.clone(),
3323 add_selections_state: self.add_selections_state.clone(),
3324 },
3325 };
3326 let (changed, result) = self.selections.change_with(cx, change);
3327 state.changed = state.changed || changed;
3328 if self.defer_selection_effects {
3329 self.deferred_selection_effects_state = Some(state);
3330 } else {
3331 self.apply_selection_effects(state, window, cx);
3332 }
3333 result
3334 }
3335
3336 /// Defers the effects of selection change, so that the effects of multiple calls to
3337 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3338 /// to selection history and the state of popovers based on selection position aren't
3339 /// erroneously updated.
3340 pub fn with_selection_effects_deferred<R>(
3341 &mut self,
3342 window: &mut Window,
3343 cx: &mut Context<Self>,
3344 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3345 ) -> R {
3346 let already_deferred = self.defer_selection_effects;
3347 self.defer_selection_effects = true;
3348 let result = update(self, window, cx);
3349 if !already_deferred {
3350 self.defer_selection_effects = false;
3351 if let Some(state) = self.deferred_selection_effects_state.take() {
3352 self.apply_selection_effects(state, window, cx);
3353 }
3354 }
3355 result
3356 }
3357
3358 fn apply_selection_effects(
3359 &mut self,
3360 state: DeferredSelectionEffectsState,
3361 window: &mut Window,
3362 cx: &mut Context<Self>,
3363 ) {
3364 if state.changed {
3365 self.selection_history.push(state.history_entry);
3366
3367 if let Some(autoscroll) = state.effects.scroll {
3368 self.request_autoscroll(autoscroll, cx);
3369 }
3370
3371 let old_cursor_position = &state.old_cursor_position;
3372
3373 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3374
3375 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3376 self.show_signature_help(&ShowSignatureHelp, window, cx);
3377 }
3378 }
3379 }
3380
3381 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3382 where
3383 I: IntoIterator<Item = (Range<S>, T)>,
3384 S: ToOffset,
3385 T: Into<Arc<str>>,
3386 {
3387 if self.read_only(cx) {
3388 return;
3389 }
3390
3391 self.buffer
3392 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3393 }
3394
3395 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3396 where
3397 I: IntoIterator<Item = (Range<S>, T)>,
3398 S: ToOffset,
3399 T: Into<Arc<str>>,
3400 {
3401 if self.read_only(cx) {
3402 return;
3403 }
3404
3405 self.buffer.update(cx, |buffer, cx| {
3406 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3407 });
3408 }
3409
3410 pub fn edit_with_block_indent<I, S, T>(
3411 &mut self,
3412 edits: I,
3413 original_indent_columns: Vec<Option<u32>>,
3414 cx: &mut Context<Self>,
3415 ) where
3416 I: IntoIterator<Item = (Range<S>, T)>,
3417 S: ToOffset,
3418 T: Into<Arc<str>>,
3419 {
3420 if self.read_only(cx) {
3421 return;
3422 }
3423
3424 self.buffer.update(cx, |buffer, cx| {
3425 buffer.edit(
3426 edits,
3427 Some(AutoindentMode::Block {
3428 original_indent_columns,
3429 }),
3430 cx,
3431 )
3432 });
3433 }
3434
3435 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3436 self.hide_context_menu(window, cx);
3437
3438 match phase {
3439 SelectPhase::Begin {
3440 position,
3441 add,
3442 click_count,
3443 } => self.begin_selection(position, add, click_count, window, cx),
3444 SelectPhase::BeginColumnar {
3445 position,
3446 goal_column,
3447 reset,
3448 mode,
3449 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3450 SelectPhase::Extend {
3451 position,
3452 click_count,
3453 } => self.extend_selection(position, click_count, window, cx),
3454 SelectPhase::Update {
3455 position,
3456 goal_column,
3457 scroll_delta,
3458 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3459 SelectPhase::End => self.end_selection(window, cx),
3460 }
3461 }
3462
3463 fn extend_selection(
3464 &mut self,
3465 position: DisplayPoint,
3466 click_count: usize,
3467 window: &mut Window,
3468 cx: &mut Context<Self>,
3469 ) {
3470 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3471 let tail = self.selections.newest::<usize>(cx).tail();
3472 self.begin_selection(position, false, click_count, window, cx);
3473
3474 let position = position.to_offset(&display_map, Bias::Left);
3475 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3476
3477 let mut pending_selection = self
3478 .selections
3479 .pending_anchor()
3480 .expect("extend_selection not called with pending selection");
3481 if position >= tail {
3482 pending_selection.start = tail_anchor;
3483 } else {
3484 pending_selection.end = tail_anchor;
3485 pending_selection.reversed = true;
3486 }
3487
3488 let mut pending_mode = self.selections.pending_mode().unwrap();
3489 match &mut pending_mode {
3490 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3491 _ => {}
3492 }
3493
3494 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3495 SelectionEffects::scroll(Autoscroll::fit())
3496 } else {
3497 SelectionEffects::no_scroll()
3498 };
3499
3500 self.change_selections(effects, window, cx, |s| {
3501 s.set_pending(pending_selection, pending_mode)
3502 });
3503 }
3504
3505 fn begin_selection(
3506 &mut self,
3507 position: DisplayPoint,
3508 add: bool,
3509 click_count: usize,
3510 window: &mut Window,
3511 cx: &mut Context<Self>,
3512 ) {
3513 if !self.focus_handle.is_focused(window) {
3514 self.last_focused_descendant = None;
3515 window.focus(&self.focus_handle);
3516 }
3517
3518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3519 let buffer = &display_map.buffer_snapshot;
3520 let position = display_map.clip_point(position, Bias::Left);
3521
3522 let start;
3523 let end;
3524 let mode;
3525 let mut auto_scroll;
3526 match click_count {
3527 1 => {
3528 start = buffer.anchor_before(position.to_point(&display_map));
3529 end = start;
3530 mode = SelectMode::Character;
3531 auto_scroll = true;
3532 }
3533 2 => {
3534 let position = display_map
3535 .clip_point(position, Bias::Left)
3536 .to_offset(&display_map, Bias::Left);
3537 let (range, _) = buffer.surrounding_word(position, false);
3538 start = buffer.anchor_before(range.start);
3539 end = buffer.anchor_before(range.end);
3540 mode = SelectMode::Word(start..end);
3541 auto_scroll = true;
3542 }
3543 3 => {
3544 let position = display_map
3545 .clip_point(position, Bias::Left)
3546 .to_point(&display_map);
3547 let line_start = display_map.prev_line_boundary(position).0;
3548 let next_line_start = buffer.clip_point(
3549 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3550 Bias::Left,
3551 );
3552 start = buffer.anchor_before(line_start);
3553 end = buffer.anchor_before(next_line_start);
3554 mode = SelectMode::Line(start..end);
3555 auto_scroll = true;
3556 }
3557 _ => {
3558 start = buffer.anchor_before(0);
3559 end = buffer.anchor_before(buffer.len());
3560 mode = SelectMode::All;
3561 auto_scroll = false;
3562 }
3563 }
3564 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3565
3566 let point_to_delete: Option<usize> = {
3567 let selected_points: Vec<Selection<Point>> =
3568 self.selections.disjoint_in_range(start..end, cx);
3569
3570 if !add || click_count > 1 {
3571 None
3572 } else if !selected_points.is_empty() {
3573 Some(selected_points[0].id)
3574 } else {
3575 let clicked_point_already_selected =
3576 self.selections.disjoint.iter().find(|selection| {
3577 selection.start.to_point(buffer) == start.to_point(buffer)
3578 || selection.end.to_point(buffer) == end.to_point(buffer)
3579 });
3580
3581 clicked_point_already_selected.map(|selection| selection.id)
3582 }
3583 };
3584
3585 let selections_count = self.selections.count();
3586 let effects = if auto_scroll {
3587 SelectionEffects::default()
3588 } else {
3589 SelectionEffects::no_scroll()
3590 };
3591
3592 self.change_selections(effects, window, cx, |s| {
3593 if let Some(point_to_delete) = point_to_delete {
3594 s.delete(point_to_delete);
3595
3596 if selections_count == 1 {
3597 s.set_pending_anchor_range(start..end, mode);
3598 }
3599 } else {
3600 if !add {
3601 s.clear_disjoint();
3602 }
3603
3604 s.set_pending_anchor_range(start..end, mode);
3605 }
3606 });
3607 }
3608
3609 fn begin_columnar_selection(
3610 &mut self,
3611 position: DisplayPoint,
3612 goal_column: u32,
3613 reset: bool,
3614 mode: ColumnarMode,
3615 window: &mut Window,
3616 cx: &mut Context<Self>,
3617 ) {
3618 if !self.focus_handle.is_focused(window) {
3619 self.last_focused_descendant = None;
3620 window.focus(&self.focus_handle);
3621 }
3622
3623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3624
3625 if reset {
3626 let pointer_position = display_map
3627 .buffer_snapshot
3628 .anchor_before(position.to_point(&display_map));
3629
3630 self.change_selections(
3631 SelectionEffects::scroll(Autoscroll::newest()),
3632 window,
3633 cx,
3634 |s| {
3635 s.clear_disjoint();
3636 s.set_pending_anchor_range(
3637 pointer_position..pointer_position,
3638 SelectMode::Character,
3639 );
3640 },
3641 );
3642 };
3643
3644 let tail = self.selections.newest::<Point>(cx).tail();
3645 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3646 self.columnar_selection_state = match mode {
3647 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3648 selection_tail: selection_anchor,
3649 display_point: if reset {
3650 if position.column() != goal_column {
3651 Some(DisplayPoint::new(position.row(), goal_column))
3652 } else {
3653 None
3654 }
3655 } else {
3656 None
3657 },
3658 }),
3659 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3660 selection_tail: selection_anchor,
3661 }),
3662 };
3663
3664 if !reset {
3665 self.select_columns(position, goal_column, &display_map, window, cx);
3666 }
3667 }
3668
3669 fn update_selection(
3670 &mut self,
3671 position: DisplayPoint,
3672 goal_column: u32,
3673 scroll_delta: gpui::Point<f32>,
3674 window: &mut Window,
3675 cx: &mut Context<Self>,
3676 ) {
3677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3678
3679 if self.columnar_selection_state.is_some() {
3680 self.select_columns(position, goal_column, &display_map, window, cx);
3681 } else if let Some(mut pending) = self.selections.pending_anchor() {
3682 let buffer = &display_map.buffer_snapshot;
3683 let head;
3684 let tail;
3685 let mode = self.selections.pending_mode().unwrap();
3686 match &mode {
3687 SelectMode::Character => {
3688 head = position.to_point(&display_map);
3689 tail = pending.tail().to_point(buffer);
3690 }
3691 SelectMode::Word(original_range) => {
3692 let offset = display_map
3693 .clip_point(position, Bias::Left)
3694 .to_offset(&display_map, Bias::Left);
3695 let original_range = original_range.to_offset(buffer);
3696
3697 let head_offset = if buffer.is_inside_word(offset, false)
3698 || original_range.contains(&offset)
3699 {
3700 let (word_range, _) = buffer.surrounding_word(offset, false);
3701 if word_range.start < original_range.start {
3702 word_range.start
3703 } else {
3704 word_range.end
3705 }
3706 } else {
3707 offset
3708 };
3709
3710 head = head_offset.to_point(buffer);
3711 if head_offset <= original_range.start {
3712 tail = original_range.end.to_point(buffer);
3713 } else {
3714 tail = original_range.start.to_point(buffer);
3715 }
3716 }
3717 SelectMode::Line(original_range) => {
3718 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3719
3720 let position = display_map
3721 .clip_point(position, Bias::Left)
3722 .to_point(&display_map);
3723 let line_start = display_map.prev_line_boundary(position).0;
3724 let next_line_start = buffer.clip_point(
3725 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3726 Bias::Left,
3727 );
3728
3729 if line_start < original_range.start {
3730 head = line_start
3731 } else {
3732 head = next_line_start
3733 }
3734
3735 if head <= original_range.start {
3736 tail = original_range.end;
3737 } else {
3738 tail = original_range.start;
3739 }
3740 }
3741 SelectMode::All => {
3742 return;
3743 }
3744 };
3745
3746 if head < tail {
3747 pending.start = buffer.anchor_before(head);
3748 pending.end = buffer.anchor_before(tail);
3749 pending.reversed = true;
3750 } else {
3751 pending.start = buffer.anchor_before(tail);
3752 pending.end = buffer.anchor_before(head);
3753 pending.reversed = false;
3754 }
3755
3756 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3757 s.set_pending(pending, mode);
3758 });
3759 } else {
3760 log::error!("update_selection dispatched with no pending selection");
3761 return;
3762 }
3763
3764 self.apply_scroll_delta(scroll_delta, window, cx);
3765 cx.notify();
3766 }
3767
3768 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3769 self.columnar_selection_state.take();
3770 if self.selections.pending_anchor().is_some() {
3771 let selections = self.selections.all::<usize>(cx);
3772 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3773 s.select(selections);
3774 s.clear_pending();
3775 });
3776 }
3777 }
3778
3779 fn select_columns(
3780 &mut self,
3781 head: DisplayPoint,
3782 goal_column: u32,
3783 display_map: &DisplaySnapshot,
3784 window: &mut Window,
3785 cx: &mut Context<Self>,
3786 ) {
3787 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3788 return;
3789 };
3790
3791 let tail = match columnar_state {
3792 ColumnarSelectionState::FromMouse {
3793 selection_tail,
3794 display_point,
3795 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3796 ColumnarSelectionState::FromSelection { selection_tail } => {
3797 selection_tail.to_display_point(display_map)
3798 }
3799 };
3800
3801 let start_row = cmp::min(tail.row(), head.row());
3802 let end_row = cmp::max(tail.row(), head.row());
3803 let start_column = cmp::min(tail.column(), goal_column);
3804 let end_column = cmp::max(tail.column(), goal_column);
3805 let reversed = start_column < tail.column();
3806
3807 let selection_ranges = (start_row.0..=end_row.0)
3808 .map(DisplayRow)
3809 .filter_map(|row| {
3810 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3811 || start_column <= display_map.line_len(row))
3812 && !display_map.is_block_line(row)
3813 {
3814 let start = display_map
3815 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3816 .to_point(display_map);
3817 let end = display_map
3818 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3819 .to_point(display_map);
3820 if reversed {
3821 Some(end..start)
3822 } else {
3823 Some(start..end)
3824 }
3825 } else {
3826 None
3827 }
3828 })
3829 .collect::<Vec<_>>();
3830
3831 let ranges = match columnar_state {
3832 ColumnarSelectionState::FromMouse { .. } => {
3833 let mut non_empty_ranges = selection_ranges
3834 .iter()
3835 .filter(|selection_range| selection_range.start != selection_range.end)
3836 .peekable();
3837 if non_empty_ranges.peek().is_some() {
3838 non_empty_ranges.cloned().collect()
3839 } else {
3840 selection_ranges
3841 }
3842 }
3843 _ => selection_ranges,
3844 };
3845
3846 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3847 s.select_ranges(ranges);
3848 });
3849 cx.notify();
3850 }
3851
3852 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3853 self.selections
3854 .all_adjusted(cx)
3855 .iter()
3856 .any(|selection| !selection.is_empty())
3857 }
3858
3859 pub fn has_pending_nonempty_selection(&self) -> bool {
3860 let pending_nonempty_selection = match self.selections.pending_anchor() {
3861 Some(Selection { start, end, .. }) => start != end,
3862 None => false,
3863 };
3864
3865 pending_nonempty_selection
3866 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3867 }
3868
3869 pub fn has_pending_selection(&self) -> bool {
3870 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3871 }
3872
3873 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3874 self.selection_mark_mode = false;
3875 self.selection_drag_state = SelectionDragState::None;
3876
3877 if self.clear_expanded_diff_hunks(cx) {
3878 cx.notify();
3879 return;
3880 }
3881 if self.dismiss_menus_and_popups(true, window, cx) {
3882 return;
3883 }
3884
3885 if self.mode.is_full()
3886 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3887 {
3888 return;
3889 }
3890
3891 cx.propagate();
3892 }
3893
3894 pub fn dismiss_menus_and_popups(
3895 &mut self,
3896 is_user_requested: bool,
3897 window: &mut Window,
3898 cx: &mut Context<Self>,
3899 ) -> bool {
3900 if self.take_rename(false, window, cx).is_some() {
3901 return true;
3902 }
3903
3904 if hide_hover(self, cx) {
3905 return true;
3906 }
3907
3908 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3909 return true;
3910 }
3911
3912 if self.hide_context_menu(window, cx).is_some() {
3913 return true;
3914 }
3915
3916 if self.mouse_context_menu.take().is_some() {
3917 return true;
3918 }
3919
3920 if is_user_requested && self.discard_edit_prediction(true, cx) {
3921 return true;
3922 }
3923
3924 if self.snippet_stack.pop().is_some() {
3925 return true;
3926 }
3927
3928 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3929 self.dismiss_diagnostics(cx);
3930 return true;
3931 }
3932
3933 false
3934 }
3935
3936 fn linked_editing_ranges_for(
3937 &self,
3938 selection: Range<text::Anchor>,
3939 cx: &App,
3940 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3941 if self.linked_edit_ranges.is_empty() {
3942 return None;
3943 }
3944 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3945 selection.end.buffer_id.and_then(|end_buffer_id| {
3946 if selection.start.buffer_id != Some(end_buffer_id) {
3947 return None;
3948 }
3949 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3950 let snapshot = buffer.read(cx).snapshot();
3951 self.linked_edit_ranges
3952 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3953 .map(|ranges| (ranges, snapshot, buffer))
3954 })?;
3955 use text::ToOffset as TO;
3956 // find offset from the start of current range to current cursor position
3957 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3958
3959 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3960 let start_difference = start_offset - start_byte_offset;
3961 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3962 let end_difference = end_offset - start_byte_offset;
3963 // Current range has associated linked ranges.
3964 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3965 for range in linked_ranges.iter() {
3966 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3967 let end_offset = start_offset + end_difference;
3968 let start_offset = start_offset + start_difference;
3969 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3970 continue;
3971 }
3972 if self.selections.disjoint_anchor_ranges().any(|s| {
3973 if s.start.buffer_id != selection.start.buffer_id
3974 || s.end.buffer_id != selection.end.buffer_id
3975 {
3976 return false;
3977 }
3978 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3979 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3980 }) {
3981 continue;
3982 }
3983 let start = buffer_snapshot.anchor_after(start_offset);
3984 let end = buffer_snapshot.anchor_after(end_offset);
3985 linked_edits
3986 .entry(buffer.clone())
3987 .or_default()
3988 .push(start..end);
3989 }
3990 Some(linked_edits)
3991 }
3992
3993 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3994 let text: Arc<str> = text.into();
3995
3996 if self.read_only(cx) {
3997 return;
3998 }
3999
4000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4001
4002 let selections = self.selections.all_adjusted(cx);
4003 let mut bracket_inserted = false;
4004 let mut edits = Vec::new();
4005 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4006 let mut new_selections = Vec::with_capacity(selections.len());
4007 let mut new_autoclose_regions = Vec::new();
4008 let snapshot = self.buffer.read(cx).read(cx);
4009 let mut clear_linked_edit_ranges = false;
4010
4011 for (selection, autoclose_region) in
4012 self.selections_with_autoclose_regions(selections, &snapshot)
4013 {
4014 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4015 // Determine if the inserted text matches the opening or closing
4016 // bracket of any of this language's bracket pairs.
4017 let mut bracket_pair = None;
4018 let mut is_bracket_pair_start = false;
4019 let mut is_bracket_pair_end = false;
4020 if !text.is_empty() {
4021 let mut bracket_pair_matching_end = None;
4022 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4023 // and they are removing the character that triggered IME popup.
4024 for (pair, enabled) in scope.brackets() {
4025 if !pair.close && !pair.surround {
4026 continue;
4027 }
4028
4029 if enabled && pair.start.ends_with(text.as_ref()) {
4030 let prefix_len = pair.start.len() - text.len();
4031 let preceding_text_matches_prefix = prefix_len == 0
4032 || (selection.start.column >= (prefix_len as u32)
4033 && snapshot.contains_str_at(
4034 Point::new(
4035 selection.start.row,
4036 selection.start.column - (prefix_len as u32),
4037 ),
4038 &pair.start[..prefix_len],
4039 ));
4040 if preceding_text_matches_prefix {
4041 bracket_pair = Some(pair.clone());
4042 is_bracket_pair_start = true;
4043 break;
4044 }
4045 }
4046 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4047 {
4048 // take first bracket pair matching end, but don't break in case a later bracket
4049 // pair matches start
4050 bracket_pair_matching_end = Some(pair.clone());
4051 }
4052 }
4053 if let Some(end) = bracket_pair_matching_end
4054 && bracket_pair.is_none()
4055 {
4056 bracket_pair = Some(end);
4057 is_bracket_pair_end = true;
4058 }
4059 }
4060
4061 if let Some(bracket_pair) = bracket_pair {
4062 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4063 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4064 let auto_surround =
4065 self.use_auto_surround && snapshot_settings.use_auto_surround;
4066 if selection.is_empty() {
4067 if is_bracket_pair_start {
4068 // If the inserted text is a suffix of an opening bracket and the
4069 // selection is preceded by the rest of the opening bracket, then
4070 // insert the closing bracket.
4071 let following_text_allows_autoclose = snapshot
4072 .chars_at(selection.start)
4073 .next()
4074 .is_none_or(|c| scope.should_autoclose_before(c));
4075
4076 let preceding_text_allows_autoclose = selection.start.column == 0
4077 || snapshot
4078 .reversed_chars_at(selection.start)
4079 .next()
4080 .is_none_or(|c| {
4081 bracket_pair.start != bracket_pair.end
4082 || !snapshot
4083 .char_classifier_at(selection.start)
4084 .is_word(c)
4085 });
4086
4087 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4088 && bracket_pair.start.len() == 1
4089 {
4090 let target = bracket_pair.start.chars().next().unwrap();
4091 let current_line_count = snapshot
4092 .reversed_chars_at(selection.start)
4093 .take_while(|&c| c != '\n')
4094 .filter(|&c| c == target)
4095 .count();
4096 current_line_count % 2 == 1
4097 } else {
4098 false
4099 };
4100
4101 if autoclose
4102 && bracket_pair.close
4103 && following_text_allows_autoclose
4104 && preceding_text_allows_autoclose
4105 && !is_closing_quote
4106 {
4107 let anchor = snapshot.anchor_before(selection.end);
4108 new_selections.push((selection.map(|_| anchor), text.len()));
4109 new_autoclose_regions.push((
4110 anchor,
4111 text.len(),
4112 selection.id,
4113 bracket_pair.clone(),
4114 ));
4115 edits.push((
4116 selection.range(),
4117 format!("{}{}", text, bracket_pair.end).into(),
4118 ));
4119 bracket_inserted = true;
4120 continue;
4121 }
4122 }
4123
4124 if let Some(region) = autoclose_region {
4125 // If the selection is followed by an auto-inserted closing bracket,
4126 // then don't insert that closing bracket again; just move the selection
4127 // past the closing bracket.
4128 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4129 && text.as_ref() == region.pair.end.as_str()
4130 && snapshot.contains_str_at(region.range.end, text.as_ref());
4131 if should_skip {
4132 let anchor = snapshot.anchor_after(selection.end);
4133 new_selections
4134 .push((selection.map(|_| anchor), region.pair.end.len()));
4135 continue;
4136 }
4137 }
4138
4139 let always_treat_brackets_as_autoclosed = snapshot
4140 .language_settings_at(selection.start, cx)
4141 .always_treat_brackets_as_autoclosed;
4142 if always_treat_brackets_as_autoclosed
4143 && is_bracket_pair_end
4144 && snapshot.contains_str_at(selection.end, text.as_ref())
4145 {
4146 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4147 // and the inserted text is a closing bracket and the selection is followed
4148 // by the closing bracket then move the selection past the closing bracket.
4149 let anchor = snapshot.anchor_after(selection.end);
4150 new_selections.push((selection.map(|_| anchor), text.len()));
4151 continue;
4152 }
4153 }
4154 // If an opening bracket is 1 character long and is typed while
4155 // text is selected, then surround that text with the bracket pair.
4156 else if auto_surround
4157 && bracket_pair.surround
4158 && is_bracket_pair_start
4159 && bracket_pair.start.chars().count() == 1
4160 {
4161 edits.push((selection.start..selection.start, text.clone()));
4162 edits.push((
4163 selection.end..selection.end,
4164 bracket_pair.end.as_str().into(),
4165 ));
4166 bracket_inserted = true;
4167 new_selections.push((
4168 Selection {
4169 id: selection.id,
4170 start: snapshot.anchor_after(selection.start),
4171 end: snapshot.anchor_before(selection.end),
4172 reversed: selection.reversed,
4173 goal: selection.goal,
4174 },
4175 0,
4176 ));
4177 continue;
4178 }
4179 }
4180 }
4181
4182 if self.auto_replace_emoji_shortcode
4183 && selection.is_empty()
4184 && text.as_ref().ends_with(':')
4185 && let Some(possible_emoji_short_code) =
4186 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4187 && !possible_emoji_short_code.is_empty()
4188 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4189 {
4190 let emoji_shortcode_start = Point::new(
4191 selection.start.row,
4192 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4193 );
4194
4195 // Remove shortcode from buffer
4196 edits.push((
4197 emoji_shortcode_start..selection.start,
4198 "".to_string().into(),
4199 ));
4200 new_selections.push((
4201 Selection {
4202 id: selection.id,
4203 start: snapshot.anchor_after(emoji_shortcode_start),
4204 end: snapshot.anchor_before(selection.start),
4205 reversed: selection.reversed,
4206 goal: selection.goal,
4207 },
4208 0,
4209 ));
4210
4211 // Insert emoji
4212 let selection_start_anchor = snapshot.anchor_after(selection.start);
4213 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4214 edits.push((selection.start..selection.end, emoji.to_string().into()));
4215
4216 continue;
4217 }
4218
4219 // If not handling any auto-close operation, then just replace the selected
4220 // text with the given input and move the selection to the end of the
4221 // newly inserted text.
4222 let anchor = snapshot.anchor_after(selection.end);
4223 if !self.linked_edit_ranges.is_empty() {
4224 let start_anchor = snapshot.anchor_before(selection.start);
4225
4226 let is_word_char = text.chars().next().is_none_or(|char| {
4227 let classifier = snapshot
4228 .char_classifier_at(start_anchor.to_offset(&snapshot))
4229 .ignore_punctuation(true);
4230 classifier.is_word(char)
4231 });
4232
4233 if is_word_char {
4234 if let Some(ranges) = self
4235 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4236 {
4237 for (buffer, edits) in ranges {
4238 linked_edits
4239 .entry(buffer.clone())
4240 .or_default()
4241 .extend(edits.into_iter().map(|range| (range, text.clone())));
4242 }
4243 }
4244 } else {
4245 clear_linked_edit_ranges = true;
4246 }
4247 }
4248
4249 new_selections.push((selection.map(|_| anchor), 0));
4250 edits.push((selection.start..selection.end, text.clone()));
4251 }
4252
4253 drop(snapshot);
4254
4255 self.transact(window, cx, |this, window, cx| {
4256 if clear_linked_edit_ranges {
4257 this.linked_edit_ranges.clear();
4258 }
4259 let initial_buffer_versions =
4260 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4261
4262 this.buffer.update(cx, |buffer, cx| {
4263 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4264 });
4265 for (buffer, edits) in linked_edits {
4266 buffer.update(cx, |buffer, cx| {
4267 let snapshot = buffer.snapshot();
4268 let edits = edits
4269 .into_iter()
4270 .map(|(range, text)| {
4271 use text::ToPoint as TP;
4272 let end_point = TP::to_point(&range.end, &snapshot);
4273 let start_point = TP::to_point(&range.start, &snapshot);
4274 (start_point..end_point, text)
4275 })
4276 .sorted_by_key(|(range, _)| range.start);
4277 buffer.edit(edits, None, cx);
4278 })
4279 }
4280 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4281 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4282 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4283 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4284 .zip(new_selection_deltas)
4285 .map(|(selection, delta)| Selection {
4286 id: selection.id,
4287 start: selection.start + delta,
4288 end: selection.end + delta,
4289 reversed: selection.reversed,
4290 goal: SelectionGoal::None,
4291 })
4292 .collect::<Vec<_>>();
4293
4294 let mut i = 0;
4295 for (position, delta, selection_id, pair) in new_autoclose_regions {
4296 let position = position.to_offset(&map.buffer_snapshot) + delta;
4297 let start = map.buffer_snapshot.anchor_before(position);
4298 let end = map.buffer_snapshot.anchor_after(position);
4299 while let Some(existing_state) = this.autoclose_regions.get(i) {
4300 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4301 Ordering::Less => i += 1,
4302 Ordering::Greater => break,
4303 Ordering::Equal => {
4304 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4305 Ordering::Less => i += 1,
4306 Ordering::Equal => break,
4307 Ordering::Greater => break,
4308 }
4309 }
4310 }
4311 }
4312 this.autoclose_regions.insert(
4313 i,
4314 AutocloseRegion {
4315 selection_id,
4316 range: start..end,
4317 pair,
4318 },
4319 );
4320 }
4321
4322 let had_active_edit_prediction = this.has_active_edit_prediction();
4323 this.change_selections(
4324 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4325 window,
4326 cx,
4327 |s| s.select(new_selections),
4328 );
4329
4330 if !bracket_inserted
4331 && let Some(on_type_format_task) =
4332 this.trigger_on_type_formatting(text.to_string(), window, cx)
4333 {
4334 on_type_format_task.detach_and_log_err(cx);
4335 }
4336
4337 let editor_settings = EditorSettings::get_global(cx);
4338 if bracket_inserted
4339 && (editor_settings.auto_signature_help
4340 || editor_settings.show_signature_help_after_edits)
4341 {
4342 this.show_signature_help(&ShowSignatureHelp, window, cx);
4343 }
4344
4345 let trigger_in_words =
4346 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4347 if this.hard_wrap.is_some() {
4348 let latest: Range<Point> = this.selections.newest(cx).range();
4349 if latest.is_empty()
4350 && this
4351 .buffer()
4352 .read(cx)
4353 .snapshot(cx)
4354 .line_len(MultiBufferRow(latest.start.row))
4355 == latest.start.column
4356 {
4357 this.rewrap_impl(
4358 RewrapOptions {
4359 override_language_settings: true,
4360 preserve_existing_whitespace: true,
4361 },
4362 cx,
4363 )
4364 }
4365 }
4366 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4367 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4368 this.refresh_edit_prediction(true, false, window, cx);
4369 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4370 });
4371 }
4372
4373 fn find_possible_emoji_shortcode_at_position(
4374 snapshot: &MultiBufferSnapshot,
4375 position: Point,
4376 ) -> Option<String> {
4377 let mut chars = Vec::new();
4378 let mut found_colon = false;
4379 for char in snapshot.reversed_chars_at(position).take(100) {
4380 // Found a possible emoji shortcode in the middle of the buffer
4381 if found_colon {
4382 if char.is_whitespace() {
4383 chars.reverse();
4384 return Some(chars.iter().collect());
4385 }
4386 // If the previous character is not a whitespace, we are in the middle of a word
4387 // and we only want to complete the shortcode if the word is made up of other emojis
4388 let mut containing_word = String::new();
4389 for ch in snapshot
4390 .reversed_chars_at(position)
4391 .skip(chars.len() + 1)
4392 .take(100)
4393 {
4394 if ch.is_whitespace() {
4395 break;
4396 }
4397 containing_word.push(ch);
4398 }
4399 let containing_word = containing_word.chars().rev().collect::<String>();
4400 if util::word_consists_of_emojis(containing_word.as_str()) {
4401 chars.reverse();
4402 return Some(chars.iter().collect());
4403 }
4404 }
4405
4406 if char.is_whitespace() || !char.is_ascii() {
4407 return None;
4408 }
4409 if char == ':' {
4410 found_colon = true;
4411 } else {
4412 chars.push(char);
4413 }
4414 }
4415 // Found a possible emoji shortcode at the beginning of the buffer
4416 chars.reverse();
4417 Some(chars.iter().collect())
4418 }
4419
4420 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4422 self.transact(window, cx, |this, window, cx| {
4423 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4424 let selections = this.selections.all::<usize>(cx);
4425 let multi_buffer = this.buffer.read(cx);
4426 let buffer = multi_buffer.snapshot(cx);
4427 selections
4428 .iter()
4429 .map(|selection| {
4430 let start_point = selection.start.to_point(&buffer);
4431 let mut existing_indent =
4432 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4433 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4434 let start = selection.start;
4435 let end = selection.end;
4436 let selection_is_empty = start == end;
4437 let language_scope = buffer.language_scope_at(start);
4438 let (
4439 comment_delimiter,
4440 doc_delimiter,
4441 insert_extra_newline,
4442 indent_on_newline,
4443 indent_on_extra_newline,
4444 ) = if let Some(language) = &language_scope {
4445 let mut insert_extra_newline =
4446 insert_extra_newline_brackets(&buffer, start..end, language)
4447 || insert_extra_newline_tree_sitter(&buffer, start..end);
4448
4449 // Comment extension on newline is allowed only for cursor selections
4450 let comment_delimiter = maybe!({
4451 if !selection_is_empty {
4452 return None;
4453 }
4454
4455 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4456 return None;
4457 }
4458
4459 let delimiters = language.line_comment_prefixes();
4460 let max_len_of_delimiter =
4461 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4462 let (snapshot, range) =
4463 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4464
4465 let num_of_whitespaces = snapshot
4466 .chars_for_range(range.clone())
4467 .take_while(|c| c.is_whitespace())
4468 .count();
4469 let comment_candidate = snapshot
4470 .chars_for_range(range.clone())
4471 .skip(num_of_whitespaces)
4472 .take(max_len_of_delimiter)
4473 .collect::<String>();
4474 let (delimiter, trimmed_len) = delimiters
4475 .iter()
4476 .filter_map(|delimiter| {
4477 let prefix = delimiter.trim_end();
4478 if comment_candidate.starts_with(prefix) {
4479 Some((delimiter, prefix.len()))
4480 } else {
4481 None
4482 }
4483 })
4484 .max_by_key(|(_, len)| *len)?;
4485
4486 if let Some(BlockCommentConfig {
4487 start: block_start, ..
4488 }) = language.block_comment()
4489 {
4490 let block_start_trimmed = block_start.trim_end();
4491 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4492 let line_content = snapshot
4493 .chars_for_range(range)
4494 .skip(num_of_whitespaces)
4495 .take(block_start_trimmed.len())
4496 .collect::<String>();
4497
4498 if line_content.starts_with(block_start_trimmed) {
4499 return None;
4500 }
4501 }
4502 }
4503
4504 let cursor_is_placed_after_comment_marker =
4505 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4506 if cursor_is_placed_after_comment_marker {
4507 Some(delimiter.clone())
4508 } else {
4509 None
4510 }
4511 });
4512
4513 let mut indent_on_newline = IndentSize::spaces(0);
4514 let mut indent_on_extra_newline = IndentSize::spaces(0);
4515
4516 let doc_delimiter = maybe!({
4517 if !selection_is_empty {
4518 return None;
4519 }
4520
4521 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4522 return None;
4523 }
4524
4525 let BlockCommentConfig {
4526 start: start_tag,
4527 end: end_tag,
4528 prefix: delimiter,
4529 tab_size: len,
4530 } = language.documentation_comment()?;
4531 let is_within_block_comment = buffer
4532 .language_scope_at(start_point)
4533 .is_some_and(|scope| scope.override_name() == Some("comment"));
4534 if !is_within_block_comment {
4535 return None;
4536 }
4537
4538 let (snapshot, range) =
4539 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4540
4541 let num_of_whitespaces = snapshot
4542 .chars_for_range(range.clone())
4543 .take_while(|c| c.is_whitespace())
4544 .count();
4545
4546 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4547 let column = start_point.column;
4548 let cursor_is_after_start_tag = {
4549 let start_tag_len = start_tag.len();
4550 let start_tag_line = snapshot
4551 .chars_for_range(range.clone())
4552 .skip(num_of_whitespaces)
4553 .take(start_tag_len)
4554 .collect::<String>();
4555 if start_tag_line.starts_with(start_tag.as_ref()) {
4556 num_of_whitespaces + start_tag_len <= column as usize
4557 } else {
4558 false
4559 }
4560 };
4561
4562 let cursor_is_after_delimiter = {
4563 let delimiter_trim = delimiter.trim_end();
4564 let delimiter_line = snapshot
4565 .chars_for_range(range.clone())
4566 .skip(num_of_whitespaces)
4567 .take(delimiter_trim.len())
4568 .collect::<String>();
4569 if delimiter_line.starts_with(delimiter_trim) {
4570 num_of_whitespaces + delimiter_trim.len() <= column as usize
4571 } else {
4572 false
4573 }
4574 };
4575
4576 let cursor_is_before_end_tag_if_exists = {
4577 let mut char_position = 0u32;
4578 let mut end_tag_offset = None;
4579
4580 'outer: for chunk in snapshot.text_for_range(range) {
4581 if let Some(byte_pos) = chunk.find(&**end_tag) {
4582 let chars_before_match =
4583 chunk[..byte_pos].chars().count() as u32;
4584 end_tag_offset =
4585 Some(char_position + chars_before_match);
4586 break 'outer;
4587 }
4588 char_position += chunk.chars().count() as u32;
4589 }
4590
4591 if let Some(end_tag_offset) = end_tag_offset {
4592 let cursor_is_before_end_tag = column <= end_tag_offset;
4593 if cursor_is_after_start_tag {
4594 if cursor_is_before_end_tag {
4595 insert_extra_newline = true;
4596 }
4597 let cursor_is_at_start_of_end_tag =
4598 column == end_tag_offset;
4599 if cursor_is_at_start_of_end_tag {
4600 indent_on_extra_newline.len = *len;
4601 }
4602 }
4603 cursor_is_before_end_tag
4604 } else {
4605 true
4606 }
4607 };
4608
4609 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4610 && cursor_is_before_end_tag_if_exists
4611 {
4612 if cursor_is_after_start_tag {
4613 indent_on_newline.len = *len;
4614 }
4615 Some(delimiter.clone())
4616 } else {
4617 None
4618 }
4619 });
4620
4621 (
4622 comment_delimiter,
4623 doc_delimiter,
4624 insert_extra_newline,
4625 indent_on_newline,
4626 indent_on_extra_newline,
4627 )
4628 } else {
4629 (
4630 None,
4631 None,
4632 false,
4633 IndentSize::default(),
4634 IndentSize::default(),
4635 )
4636 };
4637
4638 let prevent_auto_indent = doc_delimiter.is_some();
4639 let delimiter = comment_delimiter.or(doc_delimiter);
4640
4641 let capacity_for_delimiter =
4642 delimiter.as_deref().map(str::len).unwrap_or_default();
4643 let mut new_text = String::with_capacity(
4644 1 + capacity_for_delimiter
4645 + existing_indent.len as usize
4646 + indent_on_newline.len as usize
4647 + indent_on_extra_newline.len as usize,
4648 );
4649 new_text.push('\n');
4650 new_text.extend(existing_indent.chars());
4651 new_text.extend(indent_on_newline.chars());
4652
4653 if let Some(delimiter) = &delimiter {
4654 new_text.push_str(delimiter);
4655 }
4656
4657 if insert_extra_newline {
4658 new_text.push('\n');
4659 new_text.extend(existing_indent.chars());
4660 new_text.extend(indent_on_extra_newline.chars());
4661 }
4662
4663 let anchor = buffer.anchor_after(end);
4664 let new_selection = selection.map(|_| anchor);
4665 (
4666 ((start..end, new_text), prevent_auto_indent),
4667 (insert_extra_newline, new_selection),
4668 )
4669 })
4670 .unzip()
4671 };
4672
4673 let mut auto_indent_edits = Vec::new();
4674 let mut edits = Vec::new();
4675 for (edit, prevent_auto_indent) in edits_with_flags {
4676 if prevent_auto_indent {
4677 edits.push(edit);
4678 } else {
4679 auto_indent_edits.push(edit);
4680 }
4681 }
4682 if !edits.is_empty() {
4683 this.edit(edits, cx);
4684 }
4685 if !auto_indent_edits.is_empty() {
4686 this.edit_with_autoindent(auto_indent_edits, cx);
4687 }
4688
4689 let buffer = this.buffer.read(cx).snapshot(cx);
4690 let new_selections = selection_info
4691 .into_iter()
4692 .map(|(extra_newline_inserted, new_selection)| {
4693 let mut cursor = new_selection.end.to_point(&buffer);
4694 if extra_newline_inserted {
4695 cursor.row -= 1;
4696 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4697 }
4698 new_selection.map(|_| cursor)
4699 })
4700 .collect();
4701
4702 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4703 this.refresh_edit_prediction(true, false, window, cx);
4704 });
4705 }
4706
4707 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4708 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4709
4710 let buffer = self.buffer.read(cx);
4711 let snapshot = buffer.snapshot(cx);
4712
4713 let mut edits = Vec::new();
4714 let mut rows = Vec::new();
4715
4716 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4717 let cursor = selection.head();
4718 let row = cursor.row;
4719
4720 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4721
4722 let newline = "\n".to_string();
4723 edits.push((start_of_line..start_of_line, newline));
4724
4725 rows.push(row + rows_inserted as u32);
4726 }
4727
4728 self.transact(window, cx, |editor, window, cx| {
4729 editor.edit(edits, cx);
4730
4731 editor.change_selections(Default::default(), window, cx, |s| {
4732 let mut index = 0;
4733 s.move_cursors_with(|map, _, _| {
4734 let row = rows[index];
4735 index += 1;
4736
4737 let point = Point::new(row, 0);
4738 let boundary = map.next_line_boundary(point).1;
4739 let clipped = map.clip_point(boundary, Bias::Left);
4740
4741 (clipped, SelectionGoal::None)
4742 });
4743 });
4744
4745 let mut indent_edits = Vec::new();
4746 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4747 for row in rows {
4748 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4749 for (row, indent) in indents {
4750 if indent.len == 0 {
4751 continue;
4752 }
4753
4754 let text = match indent.kind {
4755 IndentKind::Space => " ".repeat(indent.len as usize),
4756 IndentKind::Tab => "\t".repeat(indent.len as usize),
4757 };
4758 let point = Point::new(row.0, 0);
4759 indent_edits.push((point..point, text));
4760 }
4761 }
4762 editor.edit(indent_edits, cx);
4763 });
4764 }
4765
4766 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4768
4769 let buffer = self.buffer.read(cx);
4770 let snapshot = buffer.snapshot(cx);
4771
4772 let mut edits = Vec::new();
4773 let mut rows = Vec::new();
4774 let mut rows_inserted = 0;
4775
4776 for selection in self.selections.all_adjusted(cx) {
4777 let cursor = selection.head();
4778 let row = cursor.row;
4779
4780 let point = Point::new(row + 1, 0);
4781 let start_of_line = snapshot.clip_point(point, Bias::Left);
4782
4783 let newline = "\n".to_string();
4784 edits.push((start_of_line..start_of_line, newline));
4785
4786 rows_inserted += 1;
4787 rows.push(row + rows_inserted);
4788 }
4789
4790 self.transact(window, cx, |editor, window, cx| {
4791 editor.edit(edits, cx);
4792
4793 editor.change_selections(Default::default(), window, cx, |s| {
4794 let mut index = 0;
4795 s.move_cursors_with(|map, _, _| {
4796 let row = rows[index];
4797 index += 1;
4798
4799 let point = Point::new(row, 0);
4800 let boundary = map.next_line_boundary(point).1;
4801 let clipped = map.clip_point(boundary, Bias::Left);
4802
4803 (clipped, SelectionGoal::None)
4804 });
4805 });
4806
4807 let mut indent_edits = Vec::new();
4808 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4809 for row in rows {
4810 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4811 for (row, indent) in indents {
4812 if indent.len == 0 {
4813 continue;
4814 }
4815
4816 let text = match indent.kind {
4817 IndentKind::Space => " ".repeat(indent.len as usize),
4818 IndentKind::Tab => "\t".repeat(indent.len as usize),
4819 };
4820 let point = Point::new(row.0, 0);
4821 indent_edits.push((point..point, text));
4822 }
4823 }
4824 editor.edit(indent_edits, cx);
4825 });
4826 }
4827
4828 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4829 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4830 original_indent_columns: Vec::new(),
4831 });
4832 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4833 }
4834
4835 fn insert_with_autoindent_mode(
4836 &mut self,
4837 text: &str,
4838 autoindent_mode: Option<AutoindentMode>,
4839 window: &mut Window,
4840 cx: &mut Context<Self>,
4841 ) {
4842 if self.read_only(cx) {
4843 return;
4844 }
4845
4846 let text: Arc<str> = text.into();
4847 self.transact(window, cx, |this, window, cx| {
4848 let old_selections = this.selections.all_adjusted(cx);
4849 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4850 let anchors = {
4851 let snapshot = buffer.read(cx);
4852 old_selections
4853 .iter()
4854 .map(|s| {
4855 let anchor = snapshot.anchor_after(s.head());
4856 s.map(|_| anchor)
4857 })
4858 .collect::<Vec<_>>()
4859 };
4860 buffer.edit(
4861 old_selections
4862 .iter()
4863 .map(|s| (s.start..s.end, text.clone())),
4864 autoindent_mode,
4865 cx,
4866 );
4867 anchors
4868 });
4869
4870 this.change_selections(Default::default(), window, cx, |s| {
4871 s.select_anchors(selection_anchors);
4872 });
4873
4874 cx.notify();
4875 });
4876 }
4877
4878 fn trigger_completion_on_input(
4879 &mut self,
4880 text: &str,
4881 trigger_in_words: bool,
4882 window: &mut Window,
4883 cx: &mut Context<Self>,
4884 ) {
4885 let completions_source = self
4886 .context_menu
4887 .borrow()
4888 .as_ref()
4889 .and_then(|menu| match menu {
4890 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4891 CodeContextMenu::CodeActions(_) => None,
4892 });
4893
4894 match completions_source {
4895 Some(CompletionsMenuSource::Words) => {
4896 self.show_word_completions(&ShowWordCompletions, window, cx)
4897 }
4898 Some(CompletionsMenuSource::Normal)
4899 | Some(CompletionsMenuSource::SnippetChoices)
4900 | None
4901 if self.is_completion_trigger(
4902 text,
4903 trigger_in_words,
4904 completions_source.is_some(),
4905 cx,
4906 ) =>
4907 {
4908 self.show_completions(
4909 &ShowCompletions {
4910 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4911 },
4912 window,
4913 cx,
4914 )
4915 }
4916 _ => {
4917 self.hide_context_menu(window, cx);
4918 }
4919 }
4920 }
4921
4922 fn is_completion_trigger(
4923 &self,
4924 text: &str,
4925 trigger_in_words: bool,
4926 menu_is_open: bool,
4927 cx: &mut Context<Self>,
4928 ) -> bool {
4929 let position = self.selections.newest_anchor().head();
4930 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4931 return false;
4932 };
4933
4934 if let Some(completion_provider) = &self.completion_provider {
4935 completion_provider.is_completion_trigger(
4936 &buffer,
4937 position.text_anchor,
4938 text,
4939 trigger_in_words,
4940 menu_is_open,
4941 cx,
4942 )
4943 } else {
4944 false
4945 }
4946 }
4947
4948 /// If any empty selections is touching the start of its innermost containing autoclose
4949 /// region, expand it to select the brackets.
4950 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4951 let selections = self.selections.all::<usize>(cx);
4952 let buffer = self.buffer.read(cx).read(cx);
4953 let new_selections = self
4954 .selections_with_autoclose_regions(selections, &buffer)
4955 .map(|(mut selection, region)| {
4956 if !selection.is_empty() {
4957 return selection;
4958 }
4959
4960 if let Some(region) = region {
4961 let mut range = region.range.to_offset(&buffer);
4962 if selection.start == range.start && range.start >= region.pair.start.len() {
4963 range.start -= region.pair.start.len();
4964 if buffer.contains_str_at(range.start, ®ion.pair.start)
4965 && buffer.contains_str_at(range.end, ®ion.pair.end)
4966 {
4967 range.end += region.pair.end.len();
4968 selection.start = range.start;
4969 selection.end = range.end;
4970
4971 return selection;
4972 }
4973 }
4974 }
4975
4976 let always_treat_brackets_as_autoclosed = buffer
4977 .language_settings_at(selection.start, cx)
4978 .always_treat_brackets_as_autoclosed;
4979
4980 if !always_treat_brackets_as_autoclosed {
4981 return selection;
4982 }
4983
4984 if let Some(scope) = buffer.language_scope_at(selection.start) {
4985 for (pair, enabled) in scope.brackets() {
4986 if !enabled || !pair.close {
4987 continue;
4988 }
4989
4990 if buffer.contains_str_at(selection.start, &pair.end) {
4991 let pair_start_len = pair.start.len();
4992 if buffer.contains_str_at(
4993 selection.start.saturating_sub(pair_start_len),
4994 &pair.start,
4995 ) {
4996 selection.start -= pair_start_len;
4997 selection.end += pair.end.len();
4998
4999 return selection;
5000 }
5001 }
5002 }
5003 }
5004
5005 selection
5006 })
5007 .collect();
5008
5009 drop(buffer);
5010 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5011 selections.select(new_selections)
5012 });
5013 }
5014
5015 /// Iterate the given selections, and for each one, find the smallest surrounding
5016 /// autoclose region. This uses the ordering of the selections and the autoclose
5017 /// regions to avoid repeated comparisons.
5018 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5019 &'a self,
5020 selections: impl IntoIterator<Item = Selection<D>>,
5021 buffer: &'a MultiBufferSnapshot,
5022 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5023 let mut i = 0;
5024 let mut regions = self.autoclose_regions.as_slice();
5025 selections.into_iter().map(move |selection| {
5026 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5027
5028 let mut enclosing = None;
5029 while let Some(pair_state) = regions.get(i) {
5030 if pair_state.range.end.to_offset(buffer) < range.start {
5031 regions = ®ions[i + 1..];
5032 i = 0;
5033 } else if pair_state.range.start.to_offset(buffer) > range.end {
5034 break;
5035 } else {
5036 if pair_state.selection_id == selection.id {
5037 enclosing = Some(pair_state);
5038 }
5039 i += 1;
5040 }
5041 }
5042
5043 (selection, enclosing)
5044 })
5045 }
5046
5047 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5048 fn invalidate_autoclose_regions(
5049 &mut self,
5050 mut selections: &[Selection<Anchor>],
5051 buffer: &MultiBufferSnapshot,
5052 ) {
5053 self.autoclose_regions.retain(|state| {
5054 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5055 return false;
5056 }
5057
5058 let mut i = 0;
5059 while let Some(selection) = selections.get(i) {
5060 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5061 selections = &selections[1..];
5062 continue;
5063 }
5064 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5065 break;
5066 }
5067 if selection.id == state.selection_id {
5068 return true;
5069 } else {
5070 i += 1;
5071 }
5072 }
5073 false
5074 });
5075 }
5076
5077 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5078 let offset = position.to_offset(buffer);
5079 let (word_range, kind) = buffer.surrounding_word(offset, true);
5080 if offset > word_range.start && kind == Some(CharKind::Word) {
5081 Some(
5082 buffer
5083 .text_for_range(word_range.start..offset)
5084 .collect::<String>(),
5085 )
5086 } else {
5087 None
5088 }
5089 }
5090
5091 pub fn toggle_inline_values(
5092 &mut self,
5093 _: &ToggleInlineValues,
5094 _: &mut Window,
5095 cx: &mut Context<Self>,
5096 ) {
5097 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5098
5099 self.refresh_inline_values(cx);
5100 }
5101
5102 pub fn toggle_inlay_hints(
5103 &mut self,
5104 _: &ToggleInlayHints,
5105 _: &mut Window,
5106 cx: &mut Context<Self>,
5107 ) {
5108 self.refresh_inlay_hints(
5109 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5110 cx,
5111 );
5112 }
5113
5114 pub fn inlay_hints_enabled(&self) -> bool {
5115 self.inlay_hint_cache.enabled
5116 }
5117
5118 pub fn inline_values_enabled(&self) -> bool {
5119 self.inline_value_cache.enabled
5120 }
5121
5122 #[cfg(any(test, feature = "test-support"))]
5123 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5124 self.display_map
5125 .read(cx)
5126 .current_inlays()
5127 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5128 .cloned()
5129 .collect()
5130 }
5131
5132 #[cfg(any(test, feature = "test-support"))]
5133 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5134 self.display_map
5135 .read(cx)
5136 .current_inlays()
5137 .cloned()
5138 .collect()
5139 }
5140
5141 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5142 if self.semantics_provider.is_none() || !self.mode.is_full() {
5143 return;
5144 }
5145
5146 let reason_description = reason.description();
5147 let ignore_debounce = matches!(
5148 reason,
5149 InlayHintRefreshReason::SettingsChange(_)
5150 | InlayHintRefreshReason::Toggle(_)
5151 | InlayHintRefreshReason::ExcerptsRemoved(_)
5152 | InlayHintRefreshReason::ModifiersChanged(_)
5153 );
5154 let (invalidate_cache, required_languages) = match reason {
5155 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5156 match self.inlay_hint_cache.modifiers_override(enabled) {
5157 Some(enabled) => {
5158 if enabled {
5159 (InvalidationStrategy::RefreshRequested, None)
5160 } else {
5161 self.splice_inlays(
5162 &self
5163 .visible_inlay_hints(cx)
5164 .iter()
5165 .map(|inlay| inlay.id)
5166 .collect::<Vec<InlayId>>(),
5167 Vec::new(),
5168 cx,
5169 );
5170 return;
5171 }
5172 }
5173 None => return,
5174 }
5175 }
5176 InlayHintRefreshReason::Toggle(enabled) => {
5177 if self.inlay_hint_cache.toggle(enabled) {
5178 if enabled {
5179 (InvalidationStrategy::RefreshRequested, None)
5180 } else {
5181 self.splice_inlays(
5182 &self
5183 .visible_inlay_hints(cx)
5184 .iter()
5185 .map(|inlay| inlay.id)
5186 .collect::<Vec<InlayId>>(),
5187 Vec::new(),
5188 cx,
5189 );
5190 return;
5191 }
5192 } else {
5193 return;
5194 }
5195 }
5196 InlayHintRefreshReason::SettingsChange(new_settings) => {
5197 match self.inlay_hint_cache.update_settings(
5198 &self.buffer,
5199 new_settings,
5200 self.visible_inlay_hints(cx),
5201 cx,
5202 ) {
5203 ControlFlow::Break(Some(InlaySplice {
5204 to_remove,
5205 to_insert,
5206 })) => {
5207 self.splice_inlays(&to_remove, to_insert, cx);
5208 return;
5209 }
5210 ControlFlow::Break(None) => return,
5211 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5212 }
5213 }
5214 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5215 if let Some(InlaySplice {
5216 to_remove,
5217 to_insert,
5218 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5219 {
5220 self.splice_inlays(&to_remove, to_insert, cx);
5221 }
5222 self.display_map.update(cx, |display_map, _| {
5223 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5224 });
5225 return;
5226 }
5227 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5228 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5229 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5230 }
5231 InlayHintRefreshReason::RefreshRequested => {
5232 (InvalidationStrategy::RefreshRequested, None)
5233 }
5234 };
5235
5236 if let Some(InlaySplice {
5237 to_remove,
5238 to_insert,
5239 }) = self.inlay_hint_cache.spawn_hint_refresh(
5240 reason_description,
5241 self.visible_excerpts(required_languages.as_ref(), cx),
5242 invalidate_cache,
5243 ignore_debounce,
5244 cx,
5245 ) {
5246 self.splice_inlays(&to_remove, to_insert, cx);
5247 }
5248 }
5249
5250 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5251 self.display_map
5252 .read(cx)
5253 .current_inlays()
5254 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5255 .cloned()
5256 .collect()
5257 }
5258
5259 pub fn visible_excerpts(
5260 &self,
5261 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5262 cx: &mut Context<Editor>,
5263 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5264 let Some(project) = self.project() else {
5265 return HashMap::default();
5266 };
5267 let project = project.read(cx);
5268 let multi_buffer = self.buffer().read(cx);
5269 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5270 let multi_buffer_visible_start = self
5271 .scroll_manager
5272 .anchor()
5273 .anchor
5274 .to_point(&multi_buffer_snapshot);
5275 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5276 multi_buffer_visible_start
5277 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5278 Bias::Left,
5279 );
5280 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5281 multi_buffer_snapshot
5282 .range_to_buffer_ranges(multi_buffer_visible_range)
5283 .into_iter()
5284 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5285 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5286 let buffer_file = project::File::from_dyn(buffer.file())?;
5287 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5288 let worktree_entry = buffer_worktree
5289 .read(cx)
5290 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5291 if worktree_entry.is_ignored {
5292 return None;
5293 }
5294
5295 let language = buffer.language()?;
5296 if let Some(restrict_to_languages) = restrict_to_languages
5297 && !restrict_to_languages.contains(language)
5298 {
5299 return None;
5300 }
5301 Some((
5302 excerpt_id,
5303 (
5304 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5305 buffer.version().clone(),
5306 excerpt_visible_range,
5307 ),
5308 ))
5309 })
5310 .collect()
5311 }
5312
5313 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5314 TextLayoutDetails {
5315 text_system: window.text_system().clone(),
5316 editor_style: self.style.clone().unwrap(),
5317 rem_size: window.rem_size(),
5318 scroll_anchor: self.scroll_manager.anchor(),
5319 visible_rows: self.visible_line_count(),
5320 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5321 }
5322 }
5323
5324 pub fn splice_inlays(
5325 &self,
5326 to_remove: &[InlayId],
5327 to_insert: Vec<Inlay>,
5328 cx: &mut Context<Self>,
5329 ) {
5330 self.display_map.update(cx, |display_map, cx| {
5331 display_map.splice_inlays(to_remove, to_insert, cx)
5332 });
5333 cx.notify();
5334 }
5335
5336 fn trigger_on_type_formatting(
5337 &self,
5338 input: String,
5339 window: &mut Window,
5340 cx: &mut Context<Self>,
5341 ) -> Option<Task<Result<()>>> {
5342 if input.len() != 1 {
5343 return None;
5344 }
5345
5346 let project = self.project()?;
5347 let position = self.selections.newest_anchor().head();
5348 let (buffer, buffer_position) = self
5349 .buffer
5350 .read(cx)
5351 .text_anchor_for_position(position, cx)?;
5352
5353 let settings = language_settings::language_settings(
5354 buffer
5355 .read(cx)
5356 .language_at(buffer_position)
5357 .map(|l| l.name()),
5358 buffer.read(cx).file(),
5359 cx,
5360 );
5361 if !settings.use_on_type_format {
5362 return None;
5363 }
5364
5365 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5366 // hence we do LSP request & edit on host side only — add formats to host's history.
5367 let push_to_lsp_host_history = true;
5368 // If this is not the host, append its history with new edits.
5369 let push_to_client_history = project.read(cx).is_via_collab();
5370
5371 let on_type_formatting = project.update(cx, |project, cx| {
5372 project.on_type_format(
5373 buffer.clone(),
5374 buffer_position,
5375 input,
5376 push_to_lsp_host_history,
5377 cx,
5378 )
5379 });
5380 Some(cx.spawn_in(window, async move |editor, cx| {
5381 if let Some(transaction) = on_type_formatting.await? {
5382 if push_to_client_history {
5383 buffer
5384 .update(cx, |buffer, _| {
5385 buffer.push_transaction(transaction, Instant::now());
5386 buffer.finalize_last_transaction();
5387 })
5388 .ok();
5389 }
5390 editor.update(cx, |editor, cx| {
5391 editor.refresh_document_highlights(cx);
5392 })?;
5393 }
5394 Ok(())
5395 }))
5396 }
5397
5398 pub fn show_word_completions(
5399 &mut self,
5400 _: &ShowWordCompletions,
5401 window: &mut Window,
5402 cx: &mut Context<Self>,
5403 ) {
5404 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5405 }
5406
5407 pub fn show_completions(
5408 &mut self,
5409 options: &ShowCompletions,
5410 window: &mut Window,
5411 cx: &mut Context<Self>,
5412 ) {
5413 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5414 }
5415
5416 fn open_or_update_completions_menu(
5417 &mut self,
5418 requested_source: Option<CompletionsMenuSource>,
5419 trigger: Option<&str>,
5420 window: &mut Window,
5421 cx: &mut Context<Self>,
5422 ) {
5423 if self.pending_rename.is_some() {
5424 return;
5425 }
5426
5427 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5428
5429 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5430 // inserted and selected. To handle that case, the start of the selection is used so that
5431 // the menu starts with all choices.
5432 let position = self
5433 .selections
5434 .newest_anchor()
5435 .start
5436 .bias_right(&multibuffer_snapshot);
5437 if position.diff_base_anchor.is_some() {
5438 return;
5439 }
5440 let (buffer, buffer_position) =
5441 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5442 output
5443 } else {
5444 return;
5445 };
5446 let buffer_snapshot = buffer.read(cx).snapshot();
5447
5448 let query: Option<Arc<String>> =
5449 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5450
5451 drop(multibuffer_snapshot);
5452
5453 let provider = match requested_source {
5454 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5455 Some(CompletionsMenuSource::Words) => None,
5456 Some(CompletionsMenuSource::SnippetChoices) => {
5457 log::error!("bug: SnippetChoices requested_source is not handled");
5458 None
5459 }
5460 };
5461
5462 let sort_completions = provider
5463 .as_ref()
5464 .is_some_and(|provider| provider.sort_completions());
5465
5466 let filter_completions = provider
5467 .as_ref()
5468 .is_none_or(|provider| provider.filter_completions());
5469
5470 let trigger_kind = match trigger {
5471 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5472 CompletionTriggerKind::TRIGGER_CHARACTER
5473 }
5474 _ => CompletionTriggerKind::INVOKED,
5475 };
5476 let completion_context = CompletionContext {
5477 trigger_character: trigger.and_then(|trigger| {
5478 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5479 Some(String::from(trigger))
5480 } else {
5481 None
5482 }
5483 }),
5484 trigger_kind,
5485 };
5486
5487 // Hide the current completions menu when a trigger char is typed. Without this, cached
5488 // completions from before the trigger char may be reused (#32774). Snippet choices could
5489 // involve trigger chars, so this is skipped in that case.
5490 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5491 {
5492 let menu_is_open = matches!(
5493 self.context_menu.borrow().as_ref(),
5494 Some(CodeContextMenu::Completions(_))
5495 );
5496 if menu_is_open {
5497 self.hide_context_menu(window, cx);
5498 }
5499 }
5500
5501 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5502 if filter_completions {
5503 menu.filter(query.clone(), provider.clone(), window, cx);
5504 }
5505 // When `is_incomplete` is false, no need to re-query completions when the current query
5506 // is a suffix of the initial query.
5507 if !menu.is_incomplete {
5508 // If the new query is a suffix of the old query (typing more characters) and
5509 // the previous result was complete, the existing completions can be filtered.
5510 //
5511 // Note that this is always true for snippet completions.
5512 let query_matches = match (&menu.initial_query, &query) {
5513 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5514 (None, _) => true,
5515 _ => false,
5516 };
5517 if query_matches {
5518 let position_matches = if menu.initial_position == position {
5519 true
5520 } else {
5521 let snapshot = self.buffer.read(cx).read(cx);
5522 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5523 };
5524 if position_matches {
5525 return;
5526 }
5527 }
5528 }
5529 };
5530
5531 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5532 buffer_snapshot.surrounding_word(buffer_position, false)
5533 {
5534 let word_to_exclude = buffer_snapshot
5535 .text_for_range(word_range.clone())
5536 .collect::<String>();
5537 (
5538 buffer_snapshot.anchor_before(word_range.start)
5539 ..buffer_snapshot.anchor_after(buffer_position),
5540 Some(word_to_exclude),
5541 )
5542 } else {
5543 (buffer_position..buffer_position, None)
5544 };
5545
5546 let language = buffer_snapshot
5547 .language_at(buffer_position)
5548 .map(|language| language.name());
5549
5550 let completion_settings =
5551 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5552
5553 let show_completion_documentation = buffer_snapshot
5554 .settings_at(buffer_position, cx)
5555 .show_completion_documentation;
5556
5557 // The document can be large, so stay in reasonable bounds when searching for words,
5558 // otherwise completion pop-up might be slow to appear.
5559 const WORD_LOOKUP_ROWS: u32 = 5_000;
5560 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5561 let min_word_search = buffer_snapshot.clip_point(
5562 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5563 Bias::Left,
5564 );
5565 let max_word_search = buffer_snapshot.clip_point(
5566 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5567 Bias::Right,
5568 );
5569 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5570 ..buffer_snapshot.point_to_offset(max_word_search);
5571
5572 let skip_digits = query
5573 .as_ref()
5574 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5575
5576 let omit_word_completions = match &query {
5577 Some(query) => query.chars().count() < completion_settings.words_min_length,
5578 None => completion_settings.words_min_length != 0,
5579 };
5580
5581 let (mut words, provider_responses) = match &provider {
5582 Some(provider) => {
5583 let provider_responses = provider.completions(
5584 position.excerpt_id,
5585 &buffer,
5586 buffer_position,
5587 completion_context,
5588 window,
5589 cx,
5590 );
5591
5592 let words = match (omit_word_completions, completion_settings.words) {
5593 (true, _) | (_, WordsCompletionMode::Disabled) => {
5594 Task::ready(BTreeMap::default())
5595 }
5596 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5597 .background_spawn(async move {
5598 buffer_snapshot.words_in_range(WordsQuery {
5599 fuzzy_contents: None,
5600 range: word_search_range,
5601 skip_digits,
5602 })
5603 }),
5604 };
5605
5606 (words, provider_responses)
5607 }
5608 None => {
5609 let words = if omit_word_completions {
5610 Task::ready(BTreeMap::default())
5611 } else {
5612 cx.background_spawn(async move {
5613 buffer_snapshot.words_in_range(WordsQuery {
5614 fuzzy_contents: None,
5615 range: word_search_range,
5616 skip_digits,
5617 })
5618 })
5619 };
5620 (words, Task::ready(Ok(Vec::new())))
5621 }
5622 };
5623
5624 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5625
5626 let id = post_inc(&mut self.next_completion_id);
5627 let task = cx.spawn_in(window, async move |editor, cx| {
5628 let Ok(()) = editor.update(cx, |this, _| {
5629 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5630 }) else {
5631 return;
5632 };
5633
5634 // TODO: Ideally completions from different sources would be selectively re-queried, so
5635 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5636 let mut completions = Vec::new();
5637 let mut is_incomplete = false;
5638 if let Some(provider_responses) = provider_responses.await.log_err()
5639 && !provider_responses.is_empty()
5640 {
5641 for response in provider_responses {
5642 completions.extend(response.completions);
5643 is_incomplete = is_incomplete || response.is_incomplete;
5644 }
5645 if completion_settings.words == WordsCompletionMode::Fallback {
5646 words = Task::ready(BTreeMap::default());
5647 }
5648 }
5649
5650 let mut words = words.await;
5651 if let Some(word_to_exclude) = &word_to_exclude {
5652 words.remove(word_to_exclude);
5653 }
5654 for lsp_completion in &completions {
5655 words.remove(&lsp_completion.new_text);
5656 }
5657 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5658 replace_range: word_replace_range.clone(),
5659 new_text: word.clone(),
5660 label: CodeLabel::plain(word, None),
5661 icon_path: None,
5662 documentation: None,
5663 source: CompletionSource::BufferWord {
5664 word_range,
5665 resolved: false,
5666 },
5667 insert_text_mode: Some(InsertTextMode::AS_IS),
5668 confirm: None,
5669 }));
5670
5671 let menu = if completions.is_empty() {
5672 None
5673 } else {
5674 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5675 let languages = editor
5676 .workspace
5677 .as_ref()
5678 .and_then(|(workspace, _)| workspace.upgrade())
5679 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5680 let menu = CompletionsMenu::new(
5681 id,
5682 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5683 sort_completions,
5684 show_completion_documentation,
5685 position,
5686 query.clone(),
5687 is_incomplete,
5688 buffer.clone(),
5689 completions.into(),
5690 snippet_sort_order,
5691 languages,
5692 language,
5693 cx,
5694 );
5695
5696 let query = if filter_completions { query } else { None };
5697 let matches_task = if let Some(query) = query {
5698 menu.do_async_filtering(query, cx)
5699 } else {
5700 Task::ready(menu.unfiltered_matches())
5701 };
5702 (menu, matches_task)
5703 }) else {
5704 return;
5705 };
5706
5707 let matches = matches_task.await;
5708
5709 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5710 // Newer menu already set, so exit.
5711 if let Some(CodeContextMenu::Completions(prev_menu)) =
5712 editor.context_menu.borrow().as_ref()
5713 && prev_menu.id > id
5714 {
5715 return;
5716 };
5717
5718 // Only valid to take prev_menu because it the new menu is immediately set
5719 // below, or the menu is hidden.
5720 if let Some(CodeContextMenu::Completions(prev_menu)) =
5721 editor.context_menu.borrow_mut().take()
5722 {
5723 let position_matches =
5724 if prev_menu.initial_position == menu.initial_position {
5725 true
5726 } else {
5727 let snapshot = editor.buffer.read(cx).read(cx);
5728 prev_menu.initial_position.to_offset(&snapshot)
5729 == menu.initial_position.to_offset(&snapshot)
5730 };
5731 if position_matches {
5732 // Preserve markdown cache before `set_filter_results` because it will
5733 // try to populate the documentation cache.
5734 menu.preserve_markdown_cache(prev_menu);
5735 }
5736 };
5737
5738 menu.set_filter_results(matches, provider, window, cx);
5739 }) else {
5740 return;
5741 };
5742
5743 menu.visible().then_some(menu)
5744 };
5745
5746 editor
5747 .update_in(cx, |editor, window, cx| {
5748 if editor.focus_handle.is_focused(window)
5749 && let Some(menu) = menu
5750 {
5751 *editor.context_menu.borrow_mut() =
5752 Some(CodeContextMenu::Completions(menu));
5753
5754 crate::hover_popover::hide_hover(editor, cx);
5755 if editor.show_edit_predictions_in_menu() {
5756 editor.update_visible_edit_prediction(window, cx);
5757 } else {
5758 editor.discard_edit_prediction(false, cx);
5759 }
5760
5761 cx.notify();
5762 return;
5763 }
5764
5765 if editor.completion_tasks.len() <= 1 {
5766 // If there are no more completion tasks and the last menu was empty, we should hide it.
5767 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5768 // If it was already hidden and we don't show edit predictions in the menu,
5769 // we should also show the edit prediction when available.
5770 if was_hidden && editor.show_edit_predictions_in_menu() {
5771 editor.update_visible_edit_prediction(window, cx);
5772 }
5773 }
5774 })
5775 .ok();
5776 });
5777
5778 self.completion_tasks.push((id, task));
5779 }
5780
5781 #[cfg(feature = "test-support")]
5782 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5783 let menu = self.context_menu.borrow();
5784 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5785 let completions = menu.completions.borrow();
5786 Some(completions.to_vec())
5787 } else {
5788 None
5789 }
5790 }
5791
5792 pub fn with_completions_menu_matching_id<R>(
5793 &self,
5794 id: CompletionId,
5795 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5796 ) -> R {
5797 let mut context_menu = self.context_menu.borrow_mut();
5798 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5799 return f(None);
5800 };
5801 if completions_menu.id != id {
5802 return f(None);
5803 }
5804 f(Some(completions_menu))
5805 }
5806
5807 pub fn confirm_completion(
5808 &mut self,
5809 action: &ConfirmCompletion,
5810 window: &mut Window,
5811 cx: &mut Context<Self>,
5812 ) -> Option<Task<Result<()>>> {
5813 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5814 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5815 }
5816
5817 pub fn confirm_completion_insert(
5818 &mut self,
5819 _: &ConfirmCompletionInsert,
5820 window: &mut Window,
5821 cx: &mut Context<Self>,
5822 ) -> Option<Task<Result<()>>> {
5823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5824 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5825 }
5826
5827 pub fn confirm_completion_replace(
5828 &mut self,
5829 _: &ConfirmCompletionReplace,
5830 window: &mut Window,
5831 cx: &mut Context<Self>,
5832 ) -> Option<Task<Result<()>>> {
5833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5834 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5835 }
5836
5837 pub fn compose_completion(
5838 &mut self,
5839 action: &ComposeCompletion,
5840 window: &mut Window,
5841 cx: &mut Context<Self>,
5842 ) -> Option<Task<Result<()>>> {
5843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5844 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5845 }
5846
5847 fn do_completion(
5848 &mut self,
5849 item_ix: Option<usize>,
5850 intent: CompletionIntent,
5851 window: &mut Window,
5852 cx: &mut Context<Editor>,
5853 ) -> Option<Task<Result<()>>> {
5854 use language::ToOffset as _;
5855
5856 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5857 else {
5858 return None;
5859 };
5860
5861 let candidate_id = {
5862 let entries = completions_menu.entries.borrow();
5863 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5864 if self.show_edit_predictions_in_menu() {
5865 self.discard_edit_prediction(true, cx);
5866 }
5867 mat.candidate_id
5868 };
5869
5870 let completion = completions_menu
5871 .completions
5872 .borrow()
5873 .get(candidate_id)?
5874 .clone();
5875 cx.stop_propagation();
5876
5877 let buffer_handle = completions_menu.buffer.clone();
5878
5879 let CompletionEdit {
5880 new_text,
5881 snippet,
5882 replace_range,
5883 } = process_completion_for_edit(
5884 &completion,
5885 intent,
5886 &buffer_handle,
5887 &completions_menu.initial_position.text_anchor,
5888 cx,
5889 );
5890
5891 let buffer = buffer_handle.read(cx);
5892 let snapshot = self.buffer.read(cx).snapshot(cx);
5893 let newest_anchor = self.selections.newest_anchor();
5894 let replace_range_multibuffer = {
5895 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5896 let multibuffer_anchor = snapshot
5897 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5898 .unwrap()
5899 ..snapshot
5900 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5901 .unwrap();
5902 multibuffer_anchor.start.to_offset(&snapshot)
5903 ..multibuffer_anchor.end.to_offset(&snapshot)
5904 };
5905 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5906 return None;
5907 }
5908
5909 let old_text = buffer
5910 .text_for_range(replace_range.clone())
5911 .collect::<String>();
5912 let lookbehind = newest_anchor
5913 .start
5914 .text_anchor
5915 .to_offset(buffer)
5916 .saturating_sub(replace_range.start);
5917 let lookahead = replace_range
5918 .end
5919 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5920 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5921 let suffix = &old_text[lookbehind.min(old_text.len())..];
5922
5923 let selections = self.selections.all::<usize>(cx);
5924 let mut ranges = Vec::new();
5925 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5926
5927 for selection in &selections {
5928 let range = if selection.id == newest_anchor.id {
5929 replace_range_multibuffer.clone()
5930 } else {
5931 let mut range = selection.range();
5932
5933 // if prefix is present, don't duplicate it
5934 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5935 range.start = range.start.saturating_sub(lookbehind);
5936
5937 // if suffix is also present, mimic the newest cursor and replace it
5938 if selection.id != newest_anchor.id
5939 && snapshot.contains_str_at(range.end, suffix)
5940 {
5941 range.end += lookahead;
5942 }
5943 }
5944 range
5945 };
5946
5947 ranges.push(range.clone());
5948
5949 if !self.linked_edit_ranges.is_empty() {
5950 let start_anchor = snapshot.anchor_before(range.start);
5951 let end_anchor = snapshot.anchor_after(range.end);
5952 if let Some(ranges) = self
5953 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5954 {
5955 for (buffer, edits) in ranges {
5956 linked_edits
5957 .entry(buffer.clone())
5958 .or_default()
5959 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5960 }
5961 }
5962 }
5963 }
5964
5965 let common_prefix_len = old_text
5966 .chars()
5967 .zip(new_text.chars())
5968 .take_while(|(a, b)| a == b)
5969 .map(|(a, _)| a.len_utf8())
5970 .sum::<usize>();
5971
5972 cx.emit(EditorEvent::InputHandled {
5973 utf16_range_to_replace: None,
5974 text: new_text[common_prefix_len..].into(),
5975 });
5976
5977 self.transact(window, cx, |editor, window, cx| {
5978 if let Some(mut snippet) = snippet {
5979 snippet.text = new_text.to_string();
5980 editor
5981 .insert_snippet(&ranges, snippet, window, cx)
5982 .log_err();
5983 } else {
5984 editor.buffer.update(cx, |multi_buffer, cx| {
5985 let auto_indent = match completion.insert_text_mode {
5986 Some(InsertTextMode::AS_IS) => None,
5987 _ => editor.autoindent_mode.clone(),
5988 };
5989 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5990 multi_buffer.edit(edits, auto_indent, cx);
5991 });
5992 }
5993 for (buffer, edits) in linked_edits {
5994 buffer.update(cx, |buffer, cx| {
5995 let snapshot = buffer.snapshot();
5996 let edits = edits
5997 .into_iter()
5998 .map(|(range, text)| {
5999 use text::ToPoint as TP;
6000 let end_point = TP::to_point(&range.end, &snapshot);
6001 let start_point = TP::to_point(&range.start, &snapshot);
6002 (start_point..end_point, text)
6003 })
6004 .sorted_by_key(|(range, _)| range.start);
6005 buffer.edit(edits, None, cx);
6006 })
6007 }
6008
6009 editor.refresh_edit_prediction(true, false, window, cx);
6010 });
6011 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6012
6013 let show_new_completions_on_confirm = completion
6014 .confirm
6015 .as_ref()
6016 .is_some_and(|confirm| confirm(intent, window, cx));
6017 if show_new_completions_on_confirm {
6018 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6019 }
6020
6021 let provider = self.completion_provider.as_ref()?;
6022 drop(completion);
6023 let apply_edits = provider.apply_additional_edits_for_completion(
6024 buffer_handle,
6025 completions_menu.completions.clone(),
6026 candidate_id,
6027 true,
6028 cx,
6029 );
6030
6031 let editor_settings = EditorSettings::get_global(cx);
6032 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6033 // After the code completion is finished, users often want to know what signatures are needed.
6034 // so we should automatically call signature_help
6035 self.show_signature_help(&ShowSignatureHelp, window, cx);
6036 }
6037
6038 Some(cx.foreground_executor().spawn(async move {
6039 apply_edits.await?;
6040 Ok(())
6041 }))
6042 }
6043
6044 pub fn toggle_code_actions(
6045 &mut self,
6046 action: &ToggleCodeActions,
6047 window: &mut Window,
6048 cx: &mut Context<Self>,
6049 ) {
6050 let quick_launch = action.quick_launch;
6051 let mut context_menu = self.context_menu.borrow_mut();
6052 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6053 if code_actions.deployed_from == action.deployed_from {
6054 // Toggle if we're selecting the same one
6055 *context_menu = None;
6056 cx.notify();
6057 return;
6058 } else {
6059 // Otherwise, clear it and start a new one
6060 *context_menu = None;
6061 cx.notify();
6062 }
6063 }
6064 drop(context_menu);
6065 let snapshot = self.snapshot(window, cx);
6066 let deployed_from = action.deployed_from.clone();
6067 let action = action.clone();
6068 self.completion_tasks.clear();
6069 self.discard_edit_prediction(false, cx);
6070
6071 let multibuffer_point = match &action.deployed_from {
6072 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6073 DisplayPoint::new(*row, 0).to_point(&snapshot)
6074 }
6075 _ => self.selections.newest::<Point>(cx).head(),
6076 };
6077 let Some((buffer, buffer_row)) = snapshot
6078 .buffer_snapshot
6079 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6080 .and_then(|(buffer_snapshot, range)| {
6081 self.buffer()
6082 .read(cx)
6083 .buffer(buffer_snapshot.remote_id())
6084 .map(|buffer| (buffer, range.start.row))
6085 })
6086 else {
6087 return;
6088 };
6089 let buffer_id = buffer.read(cx).remote_id();
6090 let tasks = self
6091 .tasks
6092 .get(&(buffer_id, buffer_row))
6093 .map(|t| Arc::new(t.to_owned()));
6094
6095 if !self.focus_handle.is_focused(window) {
6096 return;
6097 }
6098 let project = self.project.clone();
6099
6100 let code_actions_task = match deployed_from {
6101 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6102 _ => self.code_actions(buffer_row, window, cx),
6103 };
6104
6105 let runnable_task = match deployed_from {
6106 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6107 _ => {
6108 let mut task_context_task = Task::ready(None);
6109 if let Some(tasks) = &tasks
6110 && let Some(project) = project
6111 {
6112 task_context_task =
6113 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6114 }
6115
6116 cx.spawn_in(window, {
6117 let buffer = buffer.clone();
6118 async move |editor, cx| {
6119 let task_context = task_context_task.await;
6120
6121 let resolved_tasks =
6122 tasks
6123 .zip(task_context.clone())
6124 .map(|(tasks, task_context)| ResolvedTasks {
6125 templates: tasks.resolve(&task_context).collect(),
6126 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6127 multibuffer_point.row,
6128 tasks.column,
6129 )),
6130 });
6131 let debug_scenarios = editor
6132 .update(cx, |editor, cx| {
6133 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6134 })?
6135 .await;
6136 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6137 }
6138 })
6139 }
6140 };
6141
6142 cx.spawn_in(window, async move |editor, cx| {
6143 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6144 let code_actions = code_actions_task.await;
6145 let spawn_straight_away = quick_launch
6146 && resolved_tasks
6147 .as_ref()
6148 .is_some_and(|tasks| tasks.templates.len() == 1)
6149 && code_actions
6150 .as_ref()
6151 .is_none_or(|actions| actions.is_empty())
6152 && debug_scenarios.is_empty();
6153
6154 editor.update_in(cx, |editor, window, cx| {
6155 crate::hover_popover::hide_hover(editor, cx);
6156 let actions = CodeActionContents::new(
6157 resolved_tasks,
6158 code_actions,
6159 debug_scenarios,
6160 task_context.unwrap_or_default(),
6161 );
6162
6163 // Don't show the menu if there are no actions available
6164 if actions.is_empty() {
6165 cx.notify();
6166 return Task::ready(Ok(()));
6167 }
6168
6169 *editor.context_menu.borrow_mut() =
6170 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6171 buffer,
6172 actions,
6173 selected_item: Default::default(),
6174 scroll_handle: UniformListScrollHandle::default(),
6175 deployed_from,
6176 }));
6177 cx.notify();
6178 if spawn_straight_away
6179 && let Some(task) = editor.confirm_code_action(
6180 &ConfirmCodeAction { item_ix: Some(0) },
6181 window,
6182 cx,
6183 )
6184 {
6185 return task;
6186 }
6187
6188 Task::ready(Ok(()))
6189 })
6190 })
6191 .detach_and_log_err(cx);
6192 }
6193
6194 fn debug_scenarios(
6195 &mut self,
6196 resolved_tasks: &Option<ResolvedTasks>,
6197 buffer: &Entity<Buffer>,
6198 cx: &mut App,
6199 ) -> Task<Vec<task::DebugScenario>> {
6200 maybe!({
6201 let project = self.project()?;
6202 let dap_store = project.read(cx).dap_store();
6203 let mut scenarios = vec![];
6204 let resolved_tasks = resolved_tasks.as_ref()?;
6205 let buffer = buffer.read(cx);
6206 let language = buffer.language()?;
6207 let file = buffer.file();
6208 let debug_adapter = language_settings(language.name().into(), file, cx)
6209 .debuggers
6210 .first()
6211 .map(SharedString::from)
6212 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6213
6214 dap_store.update(cx, |dap_store, cx| {
6215 for (_, task) in &resolved_tasks.templates {
6216 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6217 task.original_task().clone(),
6218 debug_adapter.clone().into(),
6219 task.display_label().to_owned().into(),
6220 cx,
6221 );
6222 scenarios.push(maybe_scenario);
6223 }
6224 });
6225 Some(cx.background_spawn(async move {
6226 futures::future::join_all(scenarios)
6227 .await
6228 .into_iter()
6229 .flatten()
6230 .collect::<Vec<_>>()
6231 }))
6232 })
6233 .unwrap_or_else(|| Task::ready(vec![]))
6234 }
6235
6236 fn code_actions(
6237 &mut self,
6238 buffer_row: u32,
6239 window: &mut Window,
6240 cx: &mut Context<Self>,
6241 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6242 let mut task = self.code_actions_task.take();
6243 cx.spawn_in(window, async move |editor, cx| {
6244 while let Some(prev_task) = task {
6245 prev_task.await.log_err();
6246 task = editor
6247 .update(cx, |this, _| this.code_actions_task.take())
6248 .ok()?;
6249 }
6250
6251 editor
6252 .update(cx, |editor, cx| {
6253 editor
6254 .available_code_actions
6255 .clone()
6256 .and_then(|(location, code_actions)| {
6257 let snapshot = location.buffer.read(cx).snapshot();
6258 let point_range = location.range.to_point(&snapshot);
6259 let point_range = point_range.start.row..=point_range.end.row;
6260 if point_range.contains(&buffer_row) {
6261 Some(code_actions)
6262 } else {
6263 None
6264 }
6265 })
6266 })
6267 .ok()
6268 .flatten()
6269 })
6270 }
6271
6272 pub fn confirm_code_action(
6273 &mut self,
6274 action: &ConfirmCodeAction,
6275 window: &mut Window,
6276 cx: &mut Context<Self>,
6277 ) -> Option<Task<Result<()>>> {
6278 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6279
6280 let actions_menu =
6281 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6282 menu
6283 } else {
6284 return None;
6285 };
6286
6287 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6288 let action = actions_menu.actions.get(action_ix)?;
6289 let title = action.label();
6290 let buffer = actions_menu.buffer;
6291 let workspace = self.workspace()?;
6292
6293 match action {
6294 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6295 workspace.update(cx, |workspace, cx| {
6296 workspace.schedule_resolved_task(
6297 task_source_kind,
6298 resolved_task,
6299 false,
6300 window,
6301 cx,
6302 );
6303
6304 Some(Task::ready(Ok(())))
6305 })
6306 }
6307 CodeActionsItem::CodeAction {
6308 excerpt_id,
6309 action,
6310 provider,
6311 } => {
6312 let apply_code_action =
6313 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6314 let workspace = workspace.downgrade();
6315 Some(cx.spawn_in(window, async move |editor, cx| {
6316 let project_transaction = apply_code_action.await?;
6317 Self::open_project_transaction(
6318 &editor,
6319 workspace,
6320 project_transaction,
6321 title,
6322 cx,
6323 )
6324 .await
6325 }))
6326 }
6327 CodeActionsItem::DebugScenario(scenario) => {
6328 let context = actions_menu.actions.context;
6329
6330 workspace.update(cx, |workspace, cx| {
6331 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6332 workspace.start_debug_session(
6333 scenario,
6334 context,
6335 Some(buffer),
6336 None,
6337 window,
6338 cx,
6339 );
6340 });
6341 Some(Task::ready(Ok(())))
6342 }
6343 }
6344 }
6345
6346 pub async fn open_project_transaction(
6347 editor: &WeakEntity<Editor>,
6348 workspace: WeakEntity<Workspace>,
6349 transaction: ProjectTransaction,
6350 title: String,
6351 cx: &mut AsyncWindowContext,
6352 ) -> Result<()> {
6353 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6354 cx.update(|_, cx| {
6355 entries.sort_unstable_by_key(|(buffer, _)| {
6356 buffer.read(cx).file().map(|f| f.path().clone())
6357 });
6358 })?;
6359
6360 // If the project transaction's edits are all contained within this editor, then
6361 // avoid opening a new editor to display them.
6362
6363 if let Some((buffer, transaction)) = entries.first() {
6364 if entries.len() == 1 {
6365 let excerpt = editor.update(cx, |editor, cx| {
6366 editor
6367 .buffer()
6368 .read(cx)
6369 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6370 })?;
6371 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6372 && excerpted_buffer == *buffer
6373 {
6374 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6375 let excerpt_range = excerpt_range.to_offset(buffer);
6376 buffer
6377 .edited_ranges_for_transaction::<usize>(transaction)
6378 .all(|range| {
6379 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6380 })
6381 })?;
6382
6383 if all_edits_within_excerpt {
6384 return Ok(());
6385 }
6386 }
6387 }
6388 } else {
6389 return Ok(());
6390 }
6391
6392 let mut ranges_to_highlight = Vec::new();
6393 let excerpt_buffer = cx.new(|cx| {
6394 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6395 for (buffer_handle, transaction) in &entries {
6396 let edited_ranges = buffer_handle
6397 .read(cx)
6398 .edited_ranges_for_transaction::<Point>(transaction)
6399 .collect::<Vec<_>>();
6400 let (ranges, _) = multibuffer.set_excerpts_for_path(
6401 PathKey::for_buffer(buffer_handle, cx),
6402 buffer_handle.clone(),
6403 edited_ranges,
6404 multibuffer_context_lines(cx),
6405 cx,
6406 );
6407
6408 ranges_to_highlight.extend(ranges);
6409 }
6410 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6411 multibuffer
6412 })?;
6413
6414 workspace.update_in(cx, |workspace, window, cx| {
6415 let project = workspace.project().clone();
6416 let editor =
6417 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6418 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6419 editor.update(cx, |editor, cx| {
6420 editor.highlight_background::<Self>(
6421 &ranges_to_highlight,
6422 |theme| theme.colors().editor_highlighted_line_background,
6423 cx,
6424 );
6425 });
6426 })?;
6427
6428 Ok(())
6429 }
6430
6431 pub fn clear_code_action_providers(&mut self) {
6432 self.code_action_providers.clear();
6433 self.available_code_actions.take();
6434 }
6435
6436 pub fn add_code_action_provider(
6437 &mut self,
6438 provider: Rc<dyn CodeActionProvider>,
6439 window: &mut Window,
6440 cx: &mut Context<Self>,
6441 ) {
6442 if self
6443 .code_action_providers
6444 .iter()
6445 .any(|existing_provider| existing_provider.id() == provider.id())
6446 {
6447 return;
6448 }
6449
6450 self.code_action_providers.push(provider);
6451 self.refresh_code_actions(window, cx);
6452 }
6453
6454 pub fn remove_code_action_provider(
6455 &mut self,
6456 id: Arc<str>,
6457 window: &mut Window,
6458 cx: &mut Context<Self>,
6459 ) {
6460 self.code_action_providers
6461 .retain(|provider| provider.id() != id);
6462 self.refresh_code_actions(window, cx);
6463 }
6464
6465 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6466 !self.code_action_providers.is_empty()
6467 && EditorSettings::get_global(cx).toolbar.code_actions
6468 }
6469
6470 pub fn has_available_code_actions(&self) -> bool {
6471 self.available_code_actions
6472 .as_ref()
6473 .is_some_and(|(_, actions)| !actions.is_empty())
6474 }
6475
6476 fn render_inline_code_actions(
6477 &self,
6478 icon_size: ui::IconSize,
6479 display_row: DisplayRow,
6480 is_active: bool,
6481 cx: &mut Context<Self>,
6482 ) -> AnyElement {
6483 let show_tooltip = !self.context_menu_visible();
6484 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6485 .icon_size(icon_size)
6486 .shape(ui::IconButtonShape::Square)
6487 .icon_color(ui::Color::Hidden)
6488 .toggle_state(is_active)
6489 .when(show_tooltip, |this| {
6490 this.tooltip({
6491 let focus_handle = self.focus_handle.clone();
6492 move |window, cx| {
6493 Tooltip::for_action_in(
6494 "Toggle Code Actions",
6495 &ToggleCodeActions {
6496 deployed_from: None,
6497 quick_launch: false,
6498 },
6499 &focus_handle,
6500 window,
6501 cx,
6502 )
6503 }
6504 })
6505 })
6506 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6507 window.focus(&editor.focus_handle(cx));
6508 editor.toggle_code_actions(
6509 &crate::actions::ToggleCodeActions {
6510 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6511 display_row,
6512 )),
6513 quick_launch: false,
6514 },
6515 window,
6516 cx,
6517 );
6518 }))
6519 .into_any_element()
6520 }
6521
6522 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6523 &self.context_menu
6524 }
6525
6526 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6527 let newest_selection = self.selections.newest_anchor().clone();
6528 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6529 let buffer = self.buffer.read(cx);
6530 if newest_selection.head().diff_base_anchor.is_some() {
6531 return None;
6532 }
6533 let (start_buffer, start) =
6534 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6535 let (end_buffer, end) =
6536 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6537 if start_buffer != end_buffer {
6538 return None;
6539 }
6540
6541 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6542 cx.background_executor()
6543 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6544 .await;
6545
6546 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6547 let providers = this.code_action_providers.clone();
6548 let tasks = this
6549 .code_action_providers
6550 .iter()
6551 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6552 .collect::<Vec<_>>();
6553 (providers, tasks)
6554 })?;
6555
6556 let mut actions = Vec::new();
6557 for (provider, provider_actions) in
6558 providers.into_iter().zip(future::join_all(tasks).await)
6559 {
6560 if let Some(provider_actions) = provider_actions.log_err() {
6561 actions.extend(provider_actions.into_iter().map(|action| {
6562 AvailableCodeAction {
6563 excerpt_id: newest_selection.start.excerpt_id,
6564 action,
6565 provider: provider.clone(),
6566 }
6567 }));
6568 }
6569 }
6570
6571 this.update(cx, |this, cx| {
6572 this.available_code_actions = if actions.is_empty() {
6573 None
6574 } else {
6575 Some((
6576 Location {
6577 buffer: start_buffer,
6578 range: start..end,
6579 },
6580 actions.into(),
6581 ))
6582 };
6583 cx.notify();
6584 })
6585 }));
6586 None
6587 }
6588
6589 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6590 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6591 self.show_git_blame_inline = false;
6592
6593 self.show_git_blame_inline_delay_task =
6594 Some(cx.spawn_in(window, async move |this, cx| {
6595 cx.background_executor().timer(delay).await;
6596
6597 this.update(cx, |this, cx| {
6598 this.show_git_blame_inline = true;
6599 cx.notify();
6600 })
6601 .log_err();
6602 }));
6603 }
6604 }
6605
6606 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6607 let snapshot = self.snapshot(window, cx);
6608 let cursor = self.selections.newest::<Point>(cx).head();
6609 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6610 else {
6611 return;
6612 };
6613
6614 let Some(blame) = self.blame.as_ref() else {
6615 return;
6616 };
6617
6618 let row_info = RowInfo {
6619 buffer_id: Some(buffer.remote_id()),
6620 buffer_row: Some(point.row),
6621 ..Default::default()
6622 };
6623 let Some(blame_entry) = blame
6624 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6625 .flatten()
6626 else {
6627 return;
6628 };
6629
6630 let anchor = self.selections.newest_anchor().head();
6631 let position = self.to_pixel_point(anchor, &snapshot, window);
6632 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6633 self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
6634 };
6635 }
6636
6637 fn show_blame_popover(
6638 &mut self,
6639 blame_entry: &BlameEntry,
6640 position: gpui::Point<Pixels>,
6641 ignore_timeout: bool,
6642 cx: &mut Context<Self>,
6643 ) {
6644 if let Some(state) = &mut self.inline_blame_popover {
6645 state.hide_task.take();
6646 } else {
6647 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6648 let blame_entry = blame_entry.clone();
6649 let show_task = cx.spawn(async move |editor, cx| {
6650 if !ignore_timeout {
6651 cx.background_executor()
6652 .timer(std::time::Duration::from_millis(blame_popover_delay))
6653 .await;
6654 }
6655 editor
6656 .update(cx, |editor, cx| {
6657 editor.inline_blame_popover_show_task.take();
6658 let Some(blame) = editor.blame.as_ref() else {
6659 return;
6660 };
6661 let blame = blame.read(cx);
6662 let details = blame.details_for_entry(&blame_entry);
6663 let markdown = cx.new(|cx| {
6664 Markdown::new(
6665 details
6666 .as_ref()
6667 .map(|message| message.message.clone())
6668 .unwrap_or_default(),
6669 None,
6670 None,
6671 cx,
6672 )
6673 });
6674 editor.inline_blame_popover = Some(InlineBlamePopover {
6675 position,
6676 hide_task: None,
6677 popover_bounds: None,
6678 popover_state: InlineBlamePopoverState {
6679 scroll_handle: ScrollHandle::new(),
6680 commit_message: details,
6681 markdown,
6682 },
6683 keyboard_grace: ignore_timeout,
6684 });
6685 cx.notify();
6686 })
6687 .ok();
6688 });
6689 self.inline_blame_popover_show_task = Some(show_task);
6690 }
6691 }
6692
6693 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6694 self.inline_blame_popover_show_task.take();
6695 if let Some(state) = &mut self.inline_blame_popover {
6696 let hide_task = cx.spawn(async move |editor, cx| {
6697 cx.background_executor()
6698 .timer(std::time::Duration::from_millis(100))
6699 .await;
6700 editor
6701 .update(cx, |editor, cx| {
6702 editor.inline_blame_popover.take();
6703 cx.notify();
6704 })
6705 .ok();
6706 });
6707 state.hide_task = Some(hide_task);
6708 }
6709 }
6710
6711 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6712 if self.pending_rename.is_some() {
6713 return None;
6714 }
6715
6716 let provider = self.semantics_provider.clone()?;
6717 let buffer = self.buffer.read(cx);
6718 let newest_selection = self.selections.newest_anchor().clone();
6719 let cursor_position = newest_selection.head();
6720 let (cursor_buffer, cursor_buffer_position) =
6721 buffer.text_anchor_for_position(cursor_position, cx)?;
6722 let (tail_buffer, tail_buffer_position) =
6723 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6724 if cursor_buffer != tail_buffer {
6725 return None;
6726 }
6727
6728 let snapshot = cursor_buffer.read(cx).snapshot();
6729 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6730 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6731 if start_word_range != end_word_range {
6732 self.document_highlights_task.take();
6733 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6734 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6735 return None;
6736 }
6737
6738 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6739 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6740 cx.background_executor()
6741 .timer(Duration::from_millis(debounce))
6742 .await;
6743
6744 let highlights = if let Some(highlights) = cx
6745 .update(|cx| {
6746 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6747 })
6748 .ok()
6749 .flatten()
6750 {
6751 highlights.await.log_err()
6752 } else {
6753 None
6754 };
6755
6756 if let Some(highlights) = highlights {
6757 this.update(cx, |this, cx| {
6758 if this.pending_rename.is_some() {
6759 return;
6760 }
6761
6762 let buffer = this.buffer.read(cx);
6763 if buffer
6764 .text_anchor_for_position(cursor_position, cx)
6765 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6766 {
6767 return;
6768 }
6769
6770 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6771 let mut write_ranges = Vec::new();
6772 let mut read_ranges = Vec::new();
6773 for highlight in highlights {
6774 let buffer_id = cursor_buffer.read(cx).remote_id();
6775 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6776 {
6777 let start = highlight
6778 .range
6779 .start
6780 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6781 let end = highlight
6782 .range
6783 .end
6784 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6785 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6786 continue;
6787 }
6788
6789 let range = Anchor {
6790 buffer_id: Some(buffer_id),
6791 excerpt_id,
6792 text_anchor: start,
6793 diff_base_anchor: None,
6794 }..Anchor {
6795 buffer_id: Some(buffer_id),
6796 excerpt_id,
6797 text_anchor: end,
6798 diff_base_anchor: None,
6799 };
6800 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6801 write_ranges.push(range);
6802 } else {
6803 read_ranges.push(range);
6804 }
6805 }
6806 }
6807
6808 this.highlight_background::<DocumentHighlightRead>(
6809 &read_ranges,
6810 |theme| theme.colors().editor_document_highlight_read_background,
6811 cx,
6812 );
6813 this.highlight_background::<DocumentHighlightWrite>(
6814 &write_ranges,
6815 |theme| theme.colors().editor_document_highlight_write_background,
6816 cx,
6817 );
6818 cx.notify();
6819 })
6820 .log_err();
6821 }
6822 }));
6823 None
6824 }
6825
6826 fn prepare_highlight_query_from_selection(
6827 &mut self,
6828 cx: &mut Context<Editor>,
6829 ) -> Option<(String, Range<Anchor>)> {
6830 if matches!(self.mode, EditorMode::SingleLine) {
6831 return None;
6832 }
6833 if !EditorSettings::get_global(cx).selection_highlight {
6834 return None;
6835 }
6836 if self.selections.count() != 1 || self.selections.line_mode {
6837 return None;
6838 }
6839 let selection = self.selections.newest::<Point>(cx);
6840 if selection.is_empty() || selection.start.row != selection.end.row {
6841 return None;
6842 }
6843 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6844 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6845 let query = multi_buffer_snapshot
6846 .text_for_range(selection_anchor_range.clone())
6847 .collect::<String>();
6848 if query.trim().is_empty() {
6849 return None;
6850 }
6851 Some((query, selection_anchor_range))
6852 }
6853
6854 fn update_selection_occurrence_highlights(
6855 &mut self,
6856 query_text: String,
6857 query_range: Range<Anchor>,
6858 multi_buffer_range_to_query: Range<Point>,
6859 use_debounce: bool,
6860 window: &mut Window,
6861 cx: &mut Context<Editor>,
6862 ) -> Task<()> {
6863 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6864 cx.spawn_in(window, async move |editor, cx| {
6865 if use_debounce {
6866 cx.background_executor()
6867 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6868 .await;
6869 }
6870 let match_task = cx.background_spawn(async move {
6871 let buffer_ranges = multi_buffer_snapshot
6872 .range_to_buffer_ranges(multi_buffer_range_to_query)
6873 .into_iter()
6874 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6875 let mut match_ranges = Vec::new();
6876 let Ok(regex) = project::search::SearchQuery::text(
6877 query_text.clone(),
6878 false,
6879 false,
6880 false,
6881 Default::default(),
6882 Default::default(),
6883 false,
6884 None,
6885 ) else {
6886 return Vec::default();
6887 };
6888 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6889 match_ranges.extend(
6890 regex
6891 .search(buffer_snapshot, Some(search_range.clone()))
6892 .await
6893 .into_iter()
6894 .filter_map(|match_range| {
6895 let match_start = buffer_snapshot
6896 .anchor_after(search_range.start + match_range.start);
6897 let match_end = buffer_snapshot
6898 .anchor_before(search_range.start + match_range.end);
6899 let match_anchor_range = Anchor::range_in_buffer(
6900 excerpt_id,
6901 buffer_snapshot.remote_id(),
6902 match_start..match_end,
6903 );
6904 (match_anchor_range != query_range).then_some(match_anchor_range)
6905 }),
6906 );
6907 }
6908 match_ranges
6909 });
6910 let match_ranges = match_task.await;
6911 editor
6912 .update_in(cx, |editor, _, cx| {
6913 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6914 if !match_ranges.is_empty() {
6915 editor.highlight_background::<SelectedTextHighlight>(
6916 &match_ranges,
6917 |theme| theme.colors().editor_document_highlight_bracket_background,
6918 cx,
6919 )
6920 }
6921 })
6922 .log_err();
6923 })
6924 }
6925
6926 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6927 struct NewlineFold;
6928 let type_id = std::any::TypeId::of::<NewlineFold>();
6929 if !self.mode.is_single_line() {
6930 return;
6931 }
6932 let snapshot = self.snapshot(window, cx);
6933 if snapshot.buffer_snapshot.max_point().row == 0 {
6934 return;
6935 }
6936 let task = cx.background_spawn(async move {
6937 let new_newlines = snapshot
6938 .buffer_chars_at(0)
6939 .filter_map(|(c, i)| {
6940 if c == '\n' {
6941 Some(
6942 snapshot.buffer_snapshot.anchor_after(i)
6943 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6944 )
6945 } else {
6946 None
6947 }
6948 })
6949 .collect::<Vec<_>>();
6950 let existing_newlines = snapshot
6951 .folds_in_range(0..snapshot.buffer_snapshot.len())
6952 .filter_map(|fold| {
6953 if fold.placeholder.type_tag == Some(type_id) {
6954 Some(fold.range.start..fold.range.end)
6955 } else {
6956 None
6957 }
6958 })
6959 .collect::<Vec<_>>();
6960
6961 (new_newlines, existing_newlines)
6962 });
6963 self.folding_newlines = cx.spawn(async move |this, cx| {
6964 let (new_newlines, existing_newlines) = task.await;
6965 if new_newlines == existing_newlines {
6966 return;
6967 }
6968 let placeholder = FoldPlaceholder {
6969 render: Arc::new(move |_, _, cx| {
6970 div()
6971 .bg(cx.theme().status().hint_background)
6972 .border_b_1()
6973 .size_full()
6974 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6975 .border_color(cx.theme().status().hint)
6976 .child("\\n")
6977 .into_any()
6978 }),
6979 constrain_width: false,
6980 merge_adjacent: false,
6981 type_tag: Some(type_id),
6982 };
6983 let creases = new_newlines
6984 .into_iter()
6985 .map(|range| Crease::simple(range, placeholder.clone()))
6986 .collect();
6987 this.update(cx, |this, cx| {
6988 this.display_map.update(cx, |display_map, cx| {
6989 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6990 display_map.fold(creases, cx);
6991 });
6992 })
6993 .ok();
6994 });
6995 }
6996
6997 fn refresh_selected_text_highlights(
6998 &mut self,
6999 on_buffer_edit: bool,
7000 window: &mut Window,
7001 cx: &mut Context<Editor>,
7002 ) {
7003 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7004 else {
7005 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7006 self.quick_selection_highlight_task.take();
7007 self.debounced_selection_highlight_task.take();
7008 return;
7009 };
7010 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7011 if on_buffer_edit
7012 || self
7013 .quick_selection_highlight_task
7014 .as_ref()
7015 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7016 {
7017 let multi_buffer_visible_start = self
7018 .scroll_manager
7019 .anchor()
7020 .anchor
7021 .to_point(&multi_buffer_snapshot);
7022 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7023 multi_buffer_visible_start
7024 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7025 Bias::Left,
7026 );
7027 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7028 self.quick_selection_highlight_task = Some((
7029 query_range.clone(),
7030 self.update_selection_occurrence_highlights(
7031 query_text.clone(),
7032 query_range.clone(),
7033 multi_buffer_visible_range,
7034 false,
7035 window,
7036 cx,
7037 ),
7038 ));
7039 }
7040 if on_buffer_edit
7041 || self
7042 .debounced_selection_highlight_task
7043 .as_ref()
7044 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7045 {
7046 let multi_buffer_start = multi_buffer_snapshot
7047 .anchor_before(0)
7048 .to_point(&multi_buffer_snapshot);
7049 let multi_buffer_end = multi_buffer_snapshot
7050 .anchor_after(multi_buffer_snapshot.len())
7051 .to_point(&multi_buffer_snapshot);
7052 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7053 self.debounced_selection_highlight_task = Some((
7054 query_range.clone(),
7055 self.update_selection_occurrence_highlights(
7056 query_text,
7057 query_range,
7058 multi_buffer_full_range,
7059 true,
7060 window,
7061 cx,
7062 ),
7063 ));
7064 }
7065 }
7066
7067 pub fn refresh_edit_prediction(
7068 &mut self,
7069 debounce: bool,
7070 user_requested: bool,
7071 window: &mut Window,
7072 cx: &mut Context<Self>,
7073 ) -> Option<()> {
7074 if DisableAiSettings::get_global(cx).disable_ai {
7075 return None;
7076 }
7077
7078 let provider = self.edit_prediction_provider()?;
7079 let cursor = self.selections.newest_anchor().head();
7080 let (buffer, cursor_buffer_position) =
7081 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7082
7083 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7084 self.discard_edit_prediction(false, cx);
7085 return None;
7086 }
7087
7088 if !user_requested
7089 && (!self.should_show_edit_predictions()
7090 || !self.is_focused(window)
7091 || buffer.read(cx).is_empty())
7092 {
7093 self.discard_edit_prediction(false, cx);
7094 return None;
7095 }
7096
7097 self.update_visible_edit_prediction(window, cx);
7098 provider.refresh(
7099 self.project.clone(),
7100 buffer,
7101 cursor_buffer_position,
7102 debounce,
7103 cx,
7104 );
7105 Some(())
7106 }
7107
7108 fn show_edit_predictions_in_menu(&self) -> bool {
7109 match self.edit_prediction_settings {
7110 EditPredictionSettings::Disabled => false,
7111 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7112 }
7113 }
7114
7115 pub fn edit_predictions_enabled(&self) -> bool {
7116 match self.edit_prediction_settings {
7117 EditPredictionSettings::Disabled => false,
7118 EditPredictionSettings::Enabled { .. } => true,
7119 }
7120 }
7121
7122 fn edit_prediction_requires_modifier(&self) -> bool {
7123 match self.edit_prediction_settings {
7124 EditPredictionSettings::Disabled => false,
7125 EditPredictionSettings::Enabled {
7126 preview_requires_modifier,
7127 ..
7128 } => preview_requires_modifier,
7129 }
7130 }
7131
7132 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7133 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7134 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7135 self.discard_edit_prediction(false, cx);
7136 } else {
7137 let selection = self.selections.newest_anchor();
7138 let cursor = selection.head();
7139
7140 if let Some((buffer, cursor_buffer_position)) =
7141 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7142 {
7143 self.edit_prediction_settings =
7144 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7145 }
7146 }
7147 }
7148
7149 fn edit_prediction_settings_at_position(
7150 &self,
7151 buffer: &Entity<Buffer>,
7152 buffer_position: language::Anchor,
7153 cx: &App,
7154 ) -> EditPredictionSettings {
7155 if !self.mode.is_full()
7156 || !self.show_edit_predictions_override.unwrap_or(true)
7157 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7158 {
7159 return EditPredictionSettings::Disabled;
7160 }
7161
7162 let buffer = buffer.read(cx);
7163
7164 let file = buffer.file();
7165
7166 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7167 return EditPredictionSettings::Disabled;
7168 };
7169
7170 let by_provider = matches!(
7171 self.menu_edit_predictions_policy,
7172 MenuEditPredictionsPolicy::ByProvider
7173 );
7174
7175 let show_in_menu = by_provider
7176 && self
7177 .edit_prediction_provider
7178 .as_ref()
7179 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7180
7181 let preview_requires_modifier =
7182 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7183
7184 EditPredictionSettings::Enabled {
7185 show_in_menu,
7186 preview_requires_modifier,
7187 }
7188 }
7189
7190 fn should_show_edit_predictions(&self) -> bool {
7191 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7192 }
7193
7194 pub fn edit_prediction_preview_is_active(&self) -> bool {
7195 matches!(
7196 self.edit_prediction_preview,
7197 EditPredictionPreview::Active { .. }
7198 )
7199 }
7200
7201 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7202 let cursor = self.selections.newest_anchor().head();
7203 if let Some((buffer, cursor_position)) =
7204 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7205 {
7206 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7207 } else {
7208 false
7209 }
7210 }
7211
7212 pub fn supports_minimap(&self, cx: &App) -> bool {
7213 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7214 }
7215
7216 fn edit_predictions_enabled_in_buffer(
7217 &self,
7218 buffer: &Entity<Buffer>,
7219 buffer_position: language::Anchor,
7220 cx: &App,
7221 ) -> bool {
7222 maybe!({
7223 if self.read_only(cx) {
7224 return Some(false);
7225 }
7226 let provider = self.edit_prediction_provider()?;
7227 if !provider.is_enabled(buffer, buffer_position, cx) {
7228 return Some(false);
7229 }
7230 let buffer = buffer.read(cx);
7231 let Some(file) = buffer.file() else {
7232 return Some(true);
7233 };
7234 let settings = all_language_settings(Some(file), cx);
7235 Some(settings.edit_predictions_enabled_for_file(file, cx))
7236 })
7237 .unwrap_or(false)
7238 }
7239
7240 fn cycle_edit_prediction(
7241 &mut self,
7242 direction: Direction,
7243 window: &mut Window,
7244 cx: &mut Context<Self>,
7245 ) -> Option<()> {
7246 let provider = self.edit_prediction_provider()?;
7247 let cursor = self.selections.newest_anchor().head();
7248 let (buffer, cursor_buffer_position) =
7249 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7250 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7251 return None;
7252 }
7253
7254 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7255 self.update_visible_edit_prediction(window, cx);
7256
7257 Some(())
7258 }
7259
7260 pub fn show_edit_prediction(
7261 &mut self,
7262 _: &ShowEditPrediction,
7263 window: &mut Window,
7264 cx: &mut Context<Self>,
7265 ) {
7266 if !self.has_active_edit_prediction() {
7267 self.refresh_edit_prediction(false, true, window, cx);
7268 return;
7269 }
7270
7271 self.update_visible_edit_prediction(window, cx);
7272 }
7273
7274 pub fn display_cursor_names(
7275 &mut self,
7276 _: &DisplayCursorNames,
7277 window: &mut Window,
7278 cx: &mut Context<Self>,
7279 ) {
7280 self.show_cursor_names(window, cx);
7281 }
7282
7283 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7284 self.show_cursor_names = true;
7285 cx.notify();
7286 cx.spawn_in(window, async move |this, cx| {
7287 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7288 this.update(cx, |this, cx| {
7289 this.show_cursor_names = false;
7290 cx.notify()
7291 })
7292 .ok()
7293 })
7294 .detach();
7295 }
7296
7297 pub fn next_edit_prediction(
7298 &mut self,
7299 _: &NextEditPrediction,
7300 window: &mut Window,
7301 cx: &mut Context<Self>,
7302 ) {
7303 if self.has_active_edit_prediction() {
7304 self.cycle_edit_prediction(Direction::Next, window, cx);
7305 } else {
7306 let is_copilot_disabled = self
7307 .refresh_edit_prediction(false, true, window, cx)
7308 .is_none();
7309 if is_copilot_disabled {
7310 cx.propagate();
7311 }
7312 }
7313 }
7314
7315 pub fn previous_edit_prediction(
7316 &mut self,
7317 _: &PreviousEditPrediction,
7318 window: &mut Window,
7319 cx: &mut Context<Self>,
7320 ) {
7321 if self.has_active_edit_prediction() {
7322 self.cycle_edit_prediction(Direction::Prev, window, cx);
7323 } else {
7324 let is_copilot_disabled = self
7325 .refresh_edit_prediction(false, true, window, cx)
7326 .is_none();
7327 if is_copilot_disabled {
7328 cx.propagate();
7329 }
7330 }
7331 }
7332
7333 pub fn accept_edit_prediction(
7334 &mut self,
7335 _: &AcceptEditPrediction,
7336 window: &mut Window,
7337 cx: &mut Context<Self>,
7338 ) {
7339 if self.show_edit_predictions_in_menu() {
7340 self.hide_context_menu(window, cx);
7341 }
7342
7343 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7344 return;
7345 };
7346
7347 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7348
7349 match &active_edit_prediction.completion {
7350 EditPrediction::Move { target, .. } => {
7351 let target = *target;
7352
7353 if let Some(position_map) = &self.last_position_map {
7354 if position_map
7355 .visible_row_range
7356 .contains(&target.to_display_point(&position_map.snapshot).row())
7357 || !self.edit_prediction_requires_modifier()
7358 {
7359 self.unfold_ranges(&[target..target], true, false, cx);
7360 // Note that this is also done in vim's handler of the Tab action.
7361 self.change_selections(
7362 SelectionEffects::scroll(Autoscroll::newest()),
7363 window,
7364 cx,
7365 |selections| {
7366 selections.select_anchor_ranges([target..target]);
7367 },
7368 );
7369 self.clear_row_highlights::<EditPredictionPreview>();
7370
7371 self.edit_prediction_preview
7372 .set_previous_scroll_position(None);
7373 } else {
7374 self.edit_prediction_preview
7375 .set_previous_scroll_position(Some(
7376 position_map.snapshot.scroll_anchor,
7377 ));
7378
7379 self.highlight_rows::<EditPredictionPreview>(
7380 target..target,
7381 cx.theme().colors().editor_highlighted_line_background,
7382 RowHighlightOptions {
7383 autoscroll: true,
7384 ..Default::default()
7385 },
7386 cx,
7387 );
7388 self.request_autoscroll(Autoscroll::fit(), cx);
7389 }
7390 }
7391 }
7392 EditPrediction::Edit { edits, .. } => {
7393 if let Some(provider) = self.edit_prediction_provider() {
7394 provider.accept(cx);
7395 }
7396
7397 // Store the transaction ID and selections before applying the edit
7398 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7399
7400 let snapshot = self.buffer.read(cx).snapshot(cx);
7401 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7402
7403 self.buffer.update(cx, |buffer, cx| {
7404 buffer.edit(edits.iter().cloned(), None, cx)
7405 });
7406
7407 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7408 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7409 });
7410
7411 let selections = self.selections.disjoint_anchors();
7412 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7413 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7414 if has_new_transaction {
7415 self.selection_history
7416 .insert_transaction(transaction_id_now, selections);
7417 }
7418 }
7419
7420 self.update_visible_edit_prediction(window, cx);
7421 if self.active_edit_prediction.is_none() {
7422 self.refresh_edit_prediction(true, true, window, cx);
7423 }
7424
7425 cx.notify();
7426 }
7427 }
7428
7429 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7430 }
7431
7432 pub fn accept_partial_edit_prediction(
7433 &mut self,
7434 _: &AcceptPartialEditPrediction,
7435 window: &mut Window,
7436 cx: &mut Context<Self>,
7437 ) {
7438 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7439 return;
7440 };
7441 if self.selections.count() != 1 {
7442 return;
7443 }
7444
7445 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7446
7447 match &active_edit_prediction.completion {
7448 EditPrediction::Move { target, .. } => {
7449 let target = *target;
7450 self.change_selections(
7451 SelectionEffects::scroll(Autoscroll::newest()),
7452 window,
7453 cx,
7454 |selections| {
7455 selections.select_anchor_ranges([target..target]);
7456 },
7457 );
7458 }
7459 EditPrediction::Edit { edits, .. } => {
7460 // Find an insertion that starts at the cursor position.
7461 let snapshot = self.buffer.read(cx).snapshot(cx);
7462 let cursor_offset = self.selections.newest::<usize>(cx).head();
7463 let insertion = edits.iter().find_map(|(range, text)| {
7464 let range = range.to_offset(&snapshot);
7465 if range.is_empty() && range.start == cursor_offset {
7466 Some(text)
7467 } else {
7468 None
7469 }
7470 });
7471
7472 if let Some(text) = insertion {
7473 let mut partial_completion = text
7474 .chars()
7475 .by_ref()
7476 .take_while(|c| c.is_alphabetic())
7477 .collect::<String>();
7478 if partial_completion.is_empty() {
7479 partial_completion = text
7480 .chars()
7481 .by_ref()
7482 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7483 .collect::<String>();
7484 }
7485
7486 cx.emit(EditorEvent::InputHandled {
7487 utf16_range_to_replace: None,
7488 text: partial_completion.clone().into(),
7489 });
7490
7491 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7492
7493 self.refresh_edit_prediction(true, true, window, cx);
7494 cx.notify();
7495 } else {
7496 self.accept_edit_prediction(&Default::default(), window, cx);
7497 }
7498 }
7499 }
7500 }
7501
7502 fn discard_edit_prediction(
7503 &mut self,
7504 should_report_edit_prediction_event: bool,
7505 cx: &mut Context<Self>,
7506 ) -> bool {
7507 if should_report_edit_prediction_event {
7508 let completion_id = self
7509 .active_edit_prediction
7510 .as_ref()
7511 .and_then(|active_completion| active_completion.completion_id.clone());
7512
7513 self.report_edit_prediction_event(completion_id, false, cx);
7514 }
7515
7516 if let Some(provider) = self.edit_prediction_provider() {
7517 provider.discard(cx);
7518 }
7519
7520 self.take_active_edit_prediction(cx)
7521 }
7522
7523 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7524 let Some(provider) = self.edit_prediction_provider() else {
7525 return;
7526 };
7527
7528 let Some((_, buffer, _)) = self
7529 .buffer
7530 .read(cx)
7531 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7532 else {
7533 return;
7534 };
7535
7536 let extension = buffer
7537 .read(cx)
7538 .file()
7539 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7540
7541 let event_type = match accepted {
7542 true => "Edit Prediction Accepted",
7543 false => "Edit Prediction Discarded",
7544 };
7545 telemetry::event!(
7546 event_type,
7547 provider = provider.name(),
7548 prediction_id = id,
7549 suggestion_accepted = accepted,
7550 file_extension = extension,
7551 );
7552 }
7553
7554 pub fn has_active_edit_prediction(&self) -> bool {
7555 self.active_edit_prediction.is_some()
7556 }
7557
7558 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7559 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7560 return false;
7561 };
7562
7563 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7564 self.clear_highlights::<EditPredictionHighlight>(cx);
7565 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7566 true
7567 }
7568
7569 /// Returns true when we're displaying the edit prediction popover below the cursor
7570 /// like we are not previewing and the LSP autocomplete menu is visible
7571 /// or we are in `when_holding_modifier` mode.
7572 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7573 if self.edit_prediction_preview_is_active()
7574 || !self.show_edit_predictions_in_menu()
7575 || !self.edit_predictions_enabled()
7576 {
7577 return false;
7578 }
7579
7580 if self.has_visible_completions_menu() {
7581 return true;
7582 }
7583
7584 has_completion && self.edit_prediction_requires_modifier()
7585 }
7586
7587 fn handle_modifiers_changed(
7588 &mut self,
7589 modifiers: Modifiers,
7590 position_map: &PositionMap,
7591 window: &mut Window,
7592 cx: &mut Context<Self>,
7593 ) {
7594 if self.show_edit_predictions_in_menu() {
7595 self.update_edit_prediction_preview(&modifiers, window, cx);
7596 }
7597
7598 self.update_selection_mode(&modifiers, position_map, window, cx);
7599
7600 let mouse_position = window.mouse_position();
7601 if !position_map.text_hitbox.is_hovered(window) {
7602 return;
7603 }
7604
7605 self.update_hovered_link(
7606 position_map.point_for_position(mouse_position),
7607 &position_map.snapshot,
7608 modifiers,
7609 window,
7610 cx,
7611 )
7612 }
7613
7614 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7615 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7616 if invert {
7617 match multi_cursor_setting {
7618 MultiCursorModifier::Alt => modifiers.alt,
7619 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7620 }
7621 } else {
7622 match multi_cursor_setting {
7623 MultiCursorModifier::Alt => modifiers.secondary(),
7624 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7625 }
7626 }
7627 }
7628
7629 fn columnar_selection_mode(
7630 modifiers: &Modifiers,
7631 cx: &mut Context<Self>,
7632 ) -> Option<ColumnarMode> {
7633 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7634 if Self::multi_cursor_modifier(false, modifiers, cx) {
7635 Some(ColumnarMode::FromMouse)
7636 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7637 Some(ColumnarMode::FromSelection)
7638 } else {
7639 None
7640 }
7641 } else {
7642 None
7643 }
7644 }
7645
7646 fn update_selection_mode(
7647 &mut self,
7648 modifiers: &Modifiers,
7649 position_map: &PositionMap,
7650 window: &mut Window,
7651 cx: &mut Context<Self>,
7652 ) {
7653 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7654 return;
7655 };
7656 if self.selections.pending.is_none() {
7657 return;
7658 }
7659
7660 let mouse_position = window.mouse_position();
7661 let point_for_position = position_map.point_for_position(mouse_position);
7662 let position = point_for_position.previous_valid;
7663
7664 self.select(
7665 SelectPhase::BeginColumnar {
7666 position,
7667 reset: false,
7668 mode,
7669 goal_column: point_for_position.exact_unclipped.column(),
7670 },
7671 window,
7672 cx,
7673 );
7674 }
7675
7676 fn update_edit_prediction_preview(
7677 &mut self,
7678 modifiers: &Modifiers,
7679 window: &mut Window,
7680 cx: &mut Context<Self>,
7681 ) {
7682 let mut modifiers_held = false;
7683 if let Some(accept_keystroke) = self
7684 .accept_edit_prediction_keybind(false, window, cx)
7685 .keystroke()
7686 {
7687 modifiers_held = modifiers_held
7688 || (accept_keystroke.modifiers() == modifiers
7689 && accept_keystroke.modifiers().modified());
7690 };
7691 if let Some(accept_partial_keystroke) = self
7692 .accept_edit_prediction_keybind(true, window, cx)
7693 .keystroke()
7694 {
7695 modifiers_held = modifiers_held
7696 || (accept_partial_keystroke.modifiers() == modifiers
7697 && accept_partial_keystroke.modifiers().modified());
7698 }
7699
7700 if modifiers_held {
7701 if matches!(
7702 self.edit_prediction_preview,
7703 EditPredictionPreview::Inactive { .. }
7704 ) {
7705 self.edit_prediction_preview = EditPredictionPreview::Active {
7706 previous_scroll_position: None,
7707 since: Instant::now(),
7708 };
7709
7710 self.update_visible_edit_prediction(window, cx);
7711 cx.notify();
7712 }
7713 } else if let EditPredictionPreview::Active {
7714 previous_scroll_position,
7715 since,
7716 } = self.edit_prediction_preview
7717 {
7718 if let (Some(previous_scroll_position), Some(position_map)) =
7719 (previous_scroll_position, self.last_position_map.as_ref())
7720 {
7721 self.set_scroll_position(
7722 previous_scroll_position
7723 .scroll_position(&position_map.snapshot.display_snapshot),
7724 window,
7725 cx,
7726 );
7727 }
7728
7729 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7730 released_too_fast: since.elapsed() < Duration::from_millis(200),
7731 };
7732 self.clear_row_highlights::<EditPredictionPreview>();
7733 self.update_visible_edit_prediction(window, cx);
7734 cx.notify();
7735 }
7736 }
7737
7738 fn update_visible_edit_prediction(
7739 &mut self,
7740 _window: &mut Window,
7741 cx: &mut Context<Self>,
7742 ) -> Option<()> {
7743 if DisableAiSettings::get_global(cx).disable_ai {
7744 return None;
7745 }
7746
7747 let selection = self.selections.newest_anchor();
7748 let cursor = selection.head();
7749 let multibuffer = self.buffer.read(cx).snapshot(cx);
7750 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7751 let excerpt_id = cursor.excerpt_id;
7752
7753 let show_in_menu = self.show_edit_predictions_in_menu();
7754 let completions_menu_has_precedence = !show_in_menu
7755 && (self.context_menu.borrow().is_some()
7756 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7757
7758 if completions_menu_has_precedence
7759 || !offset_selection.is_empty()
7760 || self
7761 .active_edit_prediction
7762 .as_ref()
7763 .is_some_and(|completion| {
7764 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7765 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7766 !invalidation_range.contains(&offset_selection.head())
7767 })
7768 {
7769 self.discard_edit_prediction(false, cx);
7770 return None;
7771 }
7772
7773 self.take_active_edit_prediction(cx);
7774 let Some(provider) = self.edit_prediction_provider() else {
7775 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7776 return None;
7777 };
7778
7779 let (buffer, cursor_buffer_position) =
7780 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7781
7782 self.edit_prediction_settings =
7783 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7784
7785 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7786 self.discard_edit_prediction(false, cx);
7787 return None;
7788 };
7789
7790 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7791
7792 if self.edit_prediction_indent_conflict {
7793 let cursor_point = cursor.to_point(&multibuffer);
7794
7795 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7796
7797 if let Some((_, indent)) = indents.iter().next()
7798 && indent.len == cursor_point.column
7799 {
7800 self.edit_prediction_indent_conflict = false;
7801 }
7802 }
7803
7804 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7805 let edits = edit_prediction
7806 .edits
7807 .into_iter()
7808 .flat_map(|(range, new_text)| {
7809 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7810 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7811 Some((start..end, new_text))
7812 })
7813 .collect::<Vec<_>>();
7814 if edits.is_empty() {
7815 return None;
7816 }
7817
7818 let first_edit_start = edits.first().unwrap().0.start;
7819 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7820 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7821
7822 let last_edit_end = edits.last().unwrap().0.end;
7823 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7824 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7825
7826 let cursor_row = cursor.to_point(&multibuffer).row;
7827
7828 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7829
7830 let mut inlay_ids = Vec::new();
7831 let invalidation_row_range;
7832 let move_invalidation_row_range = if cursor_row < edit_start_row {
7833 Some(cursor_row..edit_end_row)
7834 } else if cursor_row > edit_end_row {
7835 Some(edit_start_row..cursor_row)
7836 } else {
7837 None
7838 };
7839 let supports_jump = self
7840 .edit_prediction_provider
7841 .as_ref()
7842 .map(|provider| provider.provider.supports_jump_to_edit())
7843 .unwrap_or(true);
7844
7845 let is_move = supports_jump
7846 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7847 let completion = if is_move {
7848 invalidation_row_range =
7849 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7850 let target = first_edit_start;
7851 EditPrediction::Move { target, snapshot }
7852 } else {
7853 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7854 && !self.edit_predictions_hidden_for_vim_mode;
7855
7856 if show_completions_in_buffer {
7857 if edits
7858 .iter()
7859 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7860 {
7861 let mut inlays = Vec::new();
7862 for (range, new_text) in &edits {
7863 let inlay = Inlay::edit_prediction(
7864 post_inc(&mut self.next_inlay_id),
7865 range.start,
7866 new_text.as_str(),
7867 );
7868 inlay_ids.push(inlay.id);
7869 inlays.push(inlay);
7870 }
7871
7872 self.splice_inlays(&[], inlays, cx);
7873 } else {
7874 let background_color = cx.theme().status().deleted_background;
7875 self.highlight_text::<EditPredictionHighlight>(
7876 edits.iter().map(|(range, _)| range.clone()).collect(),
7877 HighlightStyle {
7878 background_color: Some(background_color),
7879 ..Default::default()
7880 },
7881 cx,
7882 );
7883 }
7884 }
7885
7886 invalidation_row_range = edit_start_row..edit_end_row;
7887
7888 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7889 if provider.show_tab_accept_marker() {
7890 EditDisplayMode::TabAccept
7891 } else {
7892 EditDisplayMode::Inline
7893 }
7894 } else {
7895 EditDisplayMode::DiffPopover
7896 };
7897
7898 EditPrediction::Edit {
7899 edits,
7900 edit_preview: edit_prediction.edit_preview,
7901 display_mode,
7902 snapshot,
7903 }
7904 };
7905
7906 let invalidation_range = multibuffer
7907 .anchor_before(Point::new(invalidation_row_range.start, 0))
7908 ..multibuffer.anchor_after(Point::new(
7909 invalidation_row_range.end,
7910 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7911 ));
7912
7913 self.stale_edit_prediction_in_menu = None;
7914 self.active_edit_prediction = Some(EditPredictionState {
7915 inlay_ids,
7916 completion,
7917 completion_id: edit_prediction.id,
7918 invalidation_range,
7919 });
7920
7921 cx.notify();
7922
7923 Some(())
7924 }
7925
7926 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7927 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7928 }
7929
7930 fn clear_tasks(&mut self) {
7931 self.tasks.clear()
7932 }
7933
7934 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7935 if self.tasks.insert(key, value).is_some() {
7936 // This case should hopefully be rare, but just in case...
7937 log::error!(
7938 "multiple different run targets found on a single line, only the last target will be rendered"
7939 )
7940 }
7941 }
7942
7943 /// Get all display points of breakpoints that will be rendered within editor
7944 ///
7945 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7946 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7947 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7948 fn active_breakpoints(
7949 &self,
7950 range: Range<DisplayRow>,
7951 window: &mut Window,
7952 cx: &mut Context<Self>,
7953 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7954 let mut breakpoint_display_points = HashMap::default();
7955
7956 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7957 return breakpoint_display_points;
7958 };
7959
7960 let snapshot = self.snapshot(window, cx);
7961
7962 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7963 let Some(project) = self.project() else {
7964 return breakpoint_display_points;
7965 };
7966
7967 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7968 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7969
7970 for (buffer_snapshot, range, excerpt_id) in
7971 multi_buffer_snapshot.range_to_buffer_ranges(range)
7972 {
7973 let Some(buffer) = project
7974 .read(cx)
7975 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7976 else {
7977 continue;
7978 };
7979 let breakpoints = breakpoint_store.read(cx).breakpoints(
7980 &buffer,
7981 Some(
7982 buffer_snapshot.anchor_before(range.start)
7983 ..buffer_snapshot.anchor_after(range.end),
7984 ),
7985 buffer_snapshot,
7986 cx,
7987 );
7988 for (breakpoint, state) in breakpoints {
7989 let multi_buffer_anchor =
7990 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7991 let position = multi_buffer_anchor
7992 .to_point(multi_buffer_snapshot)
7993 .to_display_point(&snapshot);
7994
7995 breakpoint_display_points.insert(
7996 position.row(),
7997 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7998 );
7999 }
8000 }
8001
8002 breakpoint_display_points
8003 }
8004
8005 fn breakpoint_context_menu(
8006 &self,
8007 anchor: Anchor,
8008 window: &mut Window,
8009 cx: &mut Context<Self>,
8010 ) -> Entity<ui::ContextMenu> {
8011 let weak_editor = cx.weak_entity();
8012 let focus_handle = self.focus_handle(cx);
8013
8014 let row = self
8015 .buffer
8016 .read(cx)
8017 .snapshot(cx)
8018 .summary_for_anchor::<Point>(&anchor)
8019 .row;
8020
8021 let breakpoint = self
8022 .breakpoint_at_row(row, window, cx)
8023 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8024
8025 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8026 "Edit Log Breakpoint"
8027 } else {
8028 "Set Log Breakpoint"
8029 };
8030
8031 let condition_breakpoint_msg = if breakpoint
8032 .as_ref()
8033 .is_some_and(|bp| bp.1.condition.is_some())
8034 {
8035 "Edit Condition Breakpoint"
8036 } else {
8037 "Set Condition Breakpoint"
8038 };
8039
8040 let hit_condition_breakpoint_msg = if breakpoint
8041 .as_ref()
8042 .is_some_and(|bp| bp.1.hit_condition.is_some())
8043 {
8044 "Edit Hit Condition Breakpoint"
8045 } else {
8046 "Set Hit Condition Breakpoint"
8047 };
8048
8049 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8050 "Unset Breakpoint"
8051 } else {
8052 "Set Breakpoint"
8053 };
8054
8055 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8056
8057 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8058 BreakpointState::Enabled => Some("Disable"),
8059 BreakpointState::Disabled => Some("Enable"),
8060 });
8061
8062 let (anchor, breakpoint) =
8063 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8064
8065 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8066 menu.on_blur_subscription(Subscription::new(|| {}))
8067 .context(focus_handle)
8068 .when(run_to_cursor, |this| {
8069 let weak_editor = weak_editor.clone();
8070 this.entry("Run to cursor", None, move |window, cx| {
8071 weak_editor
8072 .update(cx, |editor, cx| {
8073 editor.change_selections(
8074 SelectionEffects::no_scroll(),
8075 window,
8076 cx,
8077 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8078 );
8079 })
8080 .ok();
8081
8082 window.dispatch_action(Box::new(RunToCursor), cx);
8083 })
8084 .separator()
8085 })
8086 .when_some(toggle_state_msg, |this, msg| {
8087 this.entry(msg, None, {
8088 let weak_editor = weak_editor.clone();
8089 let breakpoint = breakpoint.clone();
8090 move |_window, cx| {
8091 weak_editor
8092 .update(cx, |this, cx| {
8093 this.edit_breakpoint_at_anchor(
8094 anchor,
8095 breakpoint.as_ref().clone(),
8096 BreakpointEditAction::InvertState,
8097 cx,
8098 );
8099 })
8100 .log_err();
8101 }
8102 })
8103 })
8104 .entry(set_breakpoint_msg, None, {
8105 let weak_editor = weak_editor.clone();
8106 let breakpoint = breakpoint.clone();
8107 move |_window, cx| {
8108 weak_editor
8109 .update(cx, |this, cx| {
8110 this.edit_breakpoint_at_anchor(
8111 anchor,
8112 breakpoint.as_ref().clone(),
8113 BreakpointEditAction::Toggle,
8114 cx,
8115 );
8116 })
8117 .log_err();
8118 }
8119 })
8120 .entry(log_breakpoint_msg, None, {
8121 let breakpoint = breakpoint.clone();
8122 let weak_editor = weak_editor.clone();
8123 move |window, cx| {
8124 weak_editor
8125 .update(cx, |this, cx| {
8126 this.add_edit_breakpoint_block(
8127 anchor,
8128 breakpoint.as_ref(),
8129 BreakpointPromptEditAction::Log,
8130 window,
8131 cx,
8132 );
8133 })
8134 .log_err();
8135 }
8136 })
8137 .entry(condition_breakpoint_msg, None, {
8138 let breakpoint = breakpoint.clone();
8139 let weak_editor = weak_editor.clone();
8140 move |window, cx| {
8141 weak_editor
8142 .update(cx, |this, cx| {
8143 this.add_edit_breakpoint_block(
8144 anchor,
8145 breakpoint.as_ref(),
8146 BreakpointPromptEditAction::Condition,
8147 window,
8148 cx,
8149 );
8150 })
8151 .log_err();
8152 }
8153 })
8154 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8155 weak_editor
8156 .update(cx, |this, cx| {
8157 this.add_edit_breakpoint_block(
8158 anchor,
8159 breakpoint.as_ref(),
8160 BreakpointPromptEditAction::HitCondition,
8161 window,
8162 cx,
8163 );
8164 })
8165 .log_err();
8166 })
8167 })
8168 }
8169
8170 fn render_breakpoint(
8171 &self,
8172 position: Anchor,
8173 row: DisplayRow,
8174 breakpoint: &Breakpoint,
8175 state: Option<BreakpointSessionState>,
8176 cx: &mut Context<Self>,
8177 ) -> IconButton {
8178 let is_rejected = state.is_some_and(|s| !s.verified);
8179 // Is it a breakpoint that shows up when hovering over gutter?
8180 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8181 (false, false),
8182 |PhantomBreakpointIndicator {
8183 is_active,
8184 display_row,
8185 collides_with_existing_breakpoint,
8186 }| {
8187 (
8188 is_active && display_row == row,
8189 collides_with_existing_breakpoint,
8190 )
8191 },
8192 );
8193
8194 let (color, icon) = {
8195 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8196 (false, false) => ui::IconName::DebugBreakpoint,
8197 (true, false) => ui::IconName::DebugLogBreakpoint,
8198 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8199 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8200 };
8201
8202 let color = if is_phantom {
8203 Color::Hint
8204 } else if is_rejected {
8205 Color::Disabled
8206 } else {
8207 Color::Debugger
8208 };
8209
8210 (color, icon)
8211 };
8212
8213 let breakpoint = Arc::from(breakpoint.clone());
8214
8215 let alt_as_text = gpui::Keystroke {
8216 modifiers: Modifiers::secondary_key(),
8217 ..Default::default()
8218 };
8219 let primary_action_text = if breakpoint.is_disabled() {
8220 "Enable breakpoint"
8221 } else if is_phantom && !collides_with_existing {
8222 "Set breakpoint"
8223 } else {
8224 "Unset breakpoint"
8225 };
8226 let focus_handle = self.focus_handle.clone();
8227
8228 let meta = if is_rejected {
8229 SharedString::from("No executable code is associated with this line.")
8230 } else if collides_with_existing && !breakpoint.is_disabled() {
8231 SharedString::from(format!(
8232 "{alt_as_text}-click to disable,\nright-click for more options."
8233 ))
8234 } else {
8235 SharedString::from("Right-click for more options.")
8236 };
8237 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8238 .icon_size(IconSize::XSmall)
8239 .size(ui::ButtonSize::None)
8240 .when(is_rejected, |this| {
8241 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8242 })
8243 .icon_color(color)
8244 .style(ButtonStyle::Transparent)
8245 .on_click(cx.listener({
8246 move |editor, event: &ClickEvent, window, cx| {
8247 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8248 BreakpointEditAction::InvertState
8249 } else {
8250 BreakpointEditAction::Toggle
8251 };
8252
8253 window.focus(&editor.focus_handle(cx));
8254 editor.edit_breakpoint_at_anchor(
8255 position,
8256 breakpoint.as_ref().clone(),
8257 edit_action,
8258 cx,
8259 );
8260 }
8261 }))
8262 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8263 editor.set_breakpoint_context_menu(
8264 row,
8265 Some(position),
8266 event.position(),
8267 window,
8268 cx,
8269 );
8270 }))
8271 .tooltip(move |window, cx| {
8272 Tooltip::with_meta_in(
8273 primary_action_text,
8274 Some(&ToggleBreakpoint),
8275 meta.clone(),
8276 &focus_handle,
8277 window,
8278 cx,
8279 )
8280 })
8281 }
8282
8283 fn build_tasks_context(
8284 project: &Entity<Project>,
8285 buffer: &Entity<Buffer>,
8286 buffer_row: u32,
8287 tasks: &Arc<RunnableTasks>,
8288 cx: &mut Context<Self>,
8289 ) -> Task<Option<task::TaskContext>> {
8290 let position = Point::new(buffer_row, tasks.column);
8291 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8292 let location = Location {
8293 buffer: buffer.clone(),
8294 range: range_start..range_start,
8295 };
8296 // Fill in the environmental variables from the tree-sitter captures
8297 let mut captured_task_variables = TaskVariables::default();
8298 for (capture_name, value) in tasks.extra_variables.clone() {
8299 captured_task_variables.insert(
8300 task::VariableName::Custom(capture_name.into()),
8301 value.clone(),
8302 );
8303 }
8304 project.update(cx, |project, cx| {
8305 project.task_store().update(cx, |task_store, cx| {
8306 task_store.task_context_for_location(captured_task_variables, location, cx)
8307 })
8308 })
8309 }
8310
8311 pub fn spawn_nearest_task(
8312 &mut self,
8313 action: &SpawnNearestTask,
8314 window: &mut Window,
8315 cx: &mut Context<Self>,
8316 ) {
8317 let Some((workspace, _)) = self.workspace.clone() else {
8318 return;
8319 };
8320 let Some(project) = self.project.clone() else {
8321 return;
8322 };
8323
8324 // Try to find a closest, enclosing node using tree-sitter that has a task
8325 let Some((buffer, buffer_row, tasks)) = self
8326 .find_enclosing_node_task(cx)
8327 // Or find the task that's closest in row-distance.
8328 .or_else(|| self.find_closest_task(cx))
8329 else {
8330 return;
8331 };
8332
8333 let reveal_strategy = action.reveal;
8334 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8335 cx.spawn_in(window, async move |_, cx| {
8336 let context = task_context.await?;
8337 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8338
8339 let resolved = &mut resolved_task.resolved;
8340 resolved.reveal = reveal_strategy;
8341
8342 workspace
8343 .update_in(cx, |workspace, window, cx| {
8344 workspace.schedule_resolved_task(
8345 task_source_kind,
8346 resolved_task,
8347 false,
8348 window,
8349 cx,
8350 );
8351 })
8352 .ok()
8353 })
8354 .detach();
8355 }
8356
8357 fn find_closest_task(
8358 &mut self,
8359 cx: &mut Context<Self>,
8360 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8361 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8362
8363 let ((buffer_id, row), tasks) = self
8364 .tasks
8365 .iter()
8366 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8367
8368 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8369 let tasks = Arc::new(tasks.to_owned());
8370 Some((buffer, *row, tasks))
8371 }
8372
8373 fn find_enclosing_node_task(
8374 &mut self,
8375 cx: &mut Context<Self>,
8376 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8377 let snapshot = self.buffer.read(cx).snapshot(cx);
8378 let offset = self.selections.newest::<usize>(cx).head();
8379 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8380 let buffer_id = excerpt.buffer().remote_id();
8381
8382 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8383 let mut cursor = layer.node().walk();
8384
8385 while cursor.goto_first_child_for_byte(offset).is_some() {
8386 if cursor.node().end_byte() == offset {
8387 cursor.goto_next_sibling();
8388 }
8389 }
8390
8391 // Ascend to the smallest ancestor that contains the range and has a task.
8392 loop {
8393 let node = cursor.node();
8394 let node_range = node.byte_range();
8395 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8396
8397 // Check if this node contains our offset
8398 if node_range.start <= offset && node_range.end >= offset {
8399 // If it contains offset, check for task
8400 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8401 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8402 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8403 }
8404 }
8405
8406 if !cursor.goto_parent() {
8407 break;
8408 }
8409 }
8410 None
8411 }
8412
8413 fn render_run_indicator(
8414 &self,
8415 _style: &EditorStyle,
8416 is_active: bool,
8417 row: DisplayRow,
8418 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8419 cx: &mut Context<Self>,
8420 ) -> IconButton {
8421 let color = Color::Muted;
8422 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8423
8424 IconButton::new(
8425 ("run_indicator", row.0 as usize),
8426 ui::IconName::PlayOutlined,
8427 )
8428 .shape(ui::IconButtonShape::Square)
8429 .icon_size(IconSize::XSmall)
8430 .icon_color(color)
8431 .toggle_state(is_active)
8432 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8433 let quick_launch = match e {
8434 ClickEvent::Keyboard(_) => true,
8435 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8436 };
8437
8438 window.focus(&editor.focus_handle(cx));
8439 editor.toggle_code_actions(
8440 &ToggleCodeActions {
8441 deployed_from: Some(CodeActionSource::RunMenu(row)),
8442 quick_launch,
8443 },
8444 window,
8445 cx,
8446 );
8447 }))
8448 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8449 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8450 }))
8451 }
8452
8453 pub fn context_menu_visible(&self) -> bool {
8454 !self.edit_prediction_preview_is_active()
8455 && self
8456 .context_menu
8457 .borrow()
8458 .as_ref()
8459 .is_some_and(|menu| menu.visible())
8460 }
8461
8462 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8463 self.context_menu
8464 .borrow()
8465 .as_ref()
8466 .map(|menu| menu.origin())
8467 }
8468
8469 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8470 self.context_menu_options = Some(options);
8471 }
8472
8473 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8474 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8475
8476 fn render_edit_prediction_popover(
8477 &mut self,
8478 text_bounds: &Bounds<Pixels>,
8479 content_origin: gpui::Point<Pixels>,
8480 right_margin: Pixels,
8481 editor_snapshot: &EditorSnapshot,
8482 visible_row_range: Range<DisplayRow>,
8483 scroll_top: f32,
8484 scroll_bottom: f32,
8485 line_layouts: &[LineWithInvisibles],
8486 line_height: Pixels,
8487 scroll_pixel_position: gpui::Point<Pixels>,
8488 newest_selection_head: Option<DisplayPoint>,
8489 editor_width: Pixels,
8490 style: &EditorStyle,
8491 window: &mut Window,
8492 cx: &mut App,
8493 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8494 if self.mode().is_minimap() {
8495 return None;
8496 }
8497 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8498
8499 if self.edit_prediction_visible_in_cursor_popover(true) {
8500 return None;
8501 }
8502
8503 match &active_edit_prediction.completion {
8504 EditPrediction::Move { target, .. } => {
8505 let target_display_point = target.to_display_point(editor_snapshot);
8506
8507 if self.edit_prediction_requires_modifier() {
8508 if !self.edit_prediction_preview_is_active() {
8509 return None;
8510 }
8511
8512 self.render_edit_prediction_modifier_jump_popover(
8513 text_bounds,
8514 content_origin,
8515 visible_row_range,
8516 line_layouts,
8517 line_height,
8518 scroll_pixel_position,
8519 newest_selection_head,
8520 target_display_point,
8521 window,
8522 cx,
8523 )
8524 } else {
8525 self.render_edit_prediction_eager_jump_popover(
8526 text_bounds,
8527 content_origin,
8528 editor_snapshot,
8529 visible_row_range,
8530 scroll_top,
8531 scroll_bottom,
8532 line_height,
8533 scroll_pixel_position,
8534 target_display_point,
8535 editor_width,
8536 window,
8537 cx,
8538 )
8539 }
8540 }
8541 EditPrediction::Edit {
8542 display_mode: EditDisplayMode::Inline,
8543 ..
8544 } => None,
8545 EditPrediction::Edit {
8546 display_mode: EditDisplayMode::TabAccept,
8547 edits,
8548 ..
8549 } => {
8550 let range = &edits.first()?.0;
8551 let target_display_point = range.end.to_display_point(editor_snapshot);
8552
8553 self.render_edit_prediction_end_of_line_popover(
8554 "Accept",
8555 editor_snapshot,
8556 visible_row_range,
8557 target_display_point,
8558 line_height,
8559 scroll_pixel_position,
8560 content_origin,
8561 editor_width,
8562 window,
8563 cx,
8564 )
8565 }
8566 EditPrediction::Edit {
8567 edits,
8568 edit_preview,
8569 display_mode: EditDisplayMode::DiffPopover,
8570 snapshot,
8571 } => self.render_edit_prediction_diff_popover(
8572 text_bounds,
8573 content_origin,
8574 right_margin,
8575 editor_snapshot,
8576 visible_row_range,
8577 line_layouts,
8578 line_height,
8579 scroll_pixel_position,
8580 newest_selection_head,
8581 editor_width,
8582 style,
8583 edits,
8584 edit_preview,
8585 snapshot,
8586 window,
8587 cx,
8588 ),
8589 }
8590 }
8591
8592 fn render_edit_prediction_modifier_jump_popover(
8593 &mut self,
8594 text_bounds: &Bounds<Pixels>,
8595 content_origin: gpui::Point<Pixels>,
8596 visible_row_range: Range<DisplayRow>,
8597 line_layouts: &[LineWithInvisibles],
8598 line_height: Pixels,
8599 scroll_pixel_position: gpui::Point<Pixels>,
8600 newest_selection_head: Option<DisplayPoint>,
8601 target_display_point: DisplayPoint,
8602 window: &mut Window,
8603 cx: &mut App,
8604 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8605 let scrolled_content_origin =
8606 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8607
8608 const SCROLL_PADDING_Y: Pixels = px(12.);
8609
8610 if target_display_point.row() < visible_row_range.start {
8611 return self.render_edit_prediction_scroll_popover(
8612 |_| SCROLL_PADDING_Y,
8613 IconName::ArrowUp,
8614 visible_row_range,
8615 line_layouts,
8616 newest_selection_head,
8617 scrolled_content_origin,
8618 window,
8619 cx,
8620 );
8621 } else if target_display_point.row() >= visible_row_range.end {
8622 return self.render_edit_prediction_scroll_popover(
8623 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8624 IconName::ArrowDown,
8625 visible_row_range,
8626 line_layouts,
8627 newest_selection_head,
8628 scrolled_content_origin,
8629 window,
8630 cx,
8631 );
8632 }
8633
8634 const POLE_WIDTH: Pixels = px(2.);
8635
8636 let line_layout =
8637 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8638 let target_column = target_display_point.column() as usize;
8639
8640 let target_x = line_layout.x_for_index(target_column);
8641 let target_y =
8642 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8643
8644 let flag_on_right = target_x < text_bounds.size.width / 2.;
8645
8646 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8647 border_color.l += 0.001;
8648
8649 let mut element = v_flex()
8650 .items_end()
8651 .when(flag_on_right, |el| el.items_start())
8652 .child(if flag_on_right {
8653 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8654 .rounded_bl(px(0.))
8655 .rounded_tl(px(0.))
8656 .border_l_2()
8657 .border_color(border_color)
8658 } else {
8659 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8660 .rounded_br(px(0.))
8661 .rounded_tr(px(0.))
8662 .border_r_2()
8663 .border_color(border_color)
8664 })
8665 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8666 .into_any();
8667
8668 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8669
8670 let mut origin = scrolled_content_origin + point(target_x, target_y)
8671 - point(
8672 if flag_on_right {
8673 POLE_WIDTH
8674 } else {
8675 size.width - POLE_WIDTH
8676 },
8677 size.height - line_height,
8678 );
8679
8680 origin.x = origin.x.max(content_origin.x);
8681
8682 element.prepaint_at(origin, window, cx);
8683
8684 Some((element, origin))
8685 }
8686
8687 fn render_edit_prediction_scroll_popover(
8688 &mut self,
8689 to_y: impl Fn(Size<Pixels>) -> Pixels,
8690 scroll_icon: IconName,
8691 visible_row_range: Range<DisplayRow>,
8692 line_layouts: &[LineWithInvisibles],
8693 newest_selection_head: Option<DisplayPoint>,
8694 scrolled_content_origin: gpui::Point<Pixels>,
8695 window: &mut Window,
8696 cx: &mut App,
8697 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8698 let mut element = self
8699 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8700 .into_any();
8701
8702 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8703
8704 let cursor = newest_selection_head?;
8705 let cursor_row_layout =
8706 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8707 let cursor_column = cursor.column() as usize;
8708
8709 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8710
8711 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8712
8713 element.prepaint_at(origin, window, cx);
8714 Some((element, origin))
8715 }
8716
8717 fn render_edit_prediction_eager_jump_popover(
8718 &mut self,
8719 text_bounds: &Bounds<Pixels>,
8720 content_origin: gpui::Point<Pixels>,
8721 editor_snapshot: &EditorSnapshot,
8722 visible_row_range: Range<DisplayRow>,
8723 scroll_top: f32,
8724 scroll_bottom: f32,
8725 line_height: Pixels,
8726 scroll_pixel_position: gpui::Point<Pixels>,
8727 target_display_point: DisplayPoint,
8728 editor_width: Pixels,
8729 window: &mut Window,
8730 cx: &mut App,
8731 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8732 if target_display_point.row().as_f32() < scroll_top {
8733 let mut element = self
8734 .render_edit_prediction_line_popover(
8735 "Jump to Edit",
8736 Some(IconName::ArrowUp),
8737 window,
8738 cx,
8739 )?
8740 .into_any();
8741
8742 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8743 let offset = point(
8744 (text_bounds.size.width - size.width) / 2.,
8745 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8746 );
8747
8748 let origin = text_bounds.origin + offset;
8749 element.prepaint_at(origin, window, cx);
8750 Some((element, origin))
8751 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8752 let mut element = self
8753 .render_edit_prediction_line_popover(
8754 "Jump to Edit",
8755 Some(IconName::ArrowDown),
8756 window,
8757 cx,
8758 )?
8759 .into_any();
8760
8761 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8762 let offset = point(
8763 (text_bounds.size.width - size.width) / 2.,
8764 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8765 );
8766
8767 let origin = text_bounds.origin + offset;
8768 element.prepaint_at(origin, window, cx);
8769 Some((element, origin))
8770 } else {
8771 self.render_edit_prediction_end_of_line_popover(
8772 "Jump to Edit",
8773 editor_snapshot,
8774 visible_row_range,
8775 target_display_point,
8776 line_height,
8777 scroll_pixel_position,
8778 content_origin,
8779 editor_width,
8780 window,
8781 cx,
8782 )
8783 }
8784 }
8785
8786 fn render_edit_prediction_end_of_line_popover(
8787 self: &mut Editor,
8788 label: &'static str,
8789 editor_snapshot: &EditorSnapshot,
8790 visible_row_range: Range<DisplayRow>,
8791 target_display_point: DisplayPoint,
8792 line_height: Pixels,
8793 scroll_pixel_position: gpui::Point<Pixels>,
8794 content_origin: gpui::Point<Pixels>,
8795 editor_width: Pixels,
8796 window: &mut Window,
8797 cx: &mut App,
8798 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8799 let target_line_end = DisplayPoint::new(
8800 target_display_point.row(),
8801 editor_snapshot.line_len(target_display_point.row()),
8802 );
8803
8804 let mut element = self
8805 .render_edit_prediction_line_popover(label, None, window, cx)?
8806 .into_any();
8807
8808 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8809
8810 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8811
8812 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8813 let mut origin = start_point
8814 + line_origin
8815 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8816 origin.x = origin.x.max(content_origin.x);
8817
8818 let max_x = content_origin.x + editor_width - size.width;
8819
8820 if origin.x > max_x {
8821 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8822
8823 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8824 origin.y += offset;
8825 IconName::ArrowUp
8826 } else {
8827 origin.y -= offset;
8828 IconName::ArrowDown
8829 };
8830
8831 element = self
8832 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8833 .into_any();
8834
8835 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8836
8837 origin.x = content_origin.x + editor_width - size.width - px(2.);
8838 }
8839
8840 element.prepaint_at(origin, window, cx);
8841 Some((element, origin))
8842 }
8843
8844 fn render_edit_prediction_diff_popover(
8845 self: &Editor,
8846 text_bounds: &Bounds<Pixels>,
8847 content_origin: gpui::Point<Pixels>,
8848 right_margin: Pixels,
8849 editor_snapshot: &EditorSnapshot,
8850 visible_row_range: Range<DisplayRow>,
8851 line_layouts: &[LineWithInvisibles],
8852 line_height: Pixels,
8853 scroll_pixel_position: gpui::Point<Pixels>,
8854 newest_selection_head: Option<DisplayPoint>,
8855 editor_width: Pixels,
8856 style: &EditorStyle,
8857 edits: &Vec<(Range<Anchor>, String)>,
8858 edit_preview: &Option<language::EditPreview>,
8859 snapshot: &language::BufferSnapshot,
8860 window: &mut Window,
8861 cx: &mut App,
8862 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8863 let edit_start = edits
8864 .first()
8865 .unwrap()
8866 .0
8867 .start
8868 .to_display_point(editor_snapshot);
8869 let edit_end = edits
8870 .last()
8871 .unwrap()
8872 .0
8873 .end
8874 .to_display_point(editor_snapshot);
8875
8876 let is_visible = visible_row_range.contains(&edit_start.row())
8877 || visible_row_range.contains(&edit_end.row());
8878 if !is_visible {
8879 return None;
8880 }
8881
8882 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8883 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8884 } else {
8885 // Fallback for providers without edit_preview
8886 crate::edit_prediction_fallback_text(edits, cx)
8887 };
8888
8889 let styled_text = highlighted_edits.to_styled_text(&style.text);
8890 let line_count = highlighted_edits.text.lines().count();
8891
8892 const BORDER_WIDTH: Pixels = px(1.);
8893
8894 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8895 let has_keybind = keybind.is_some();
8896
8897 let mut element = h_flex()
8898 .items_start()
8899 .child(
8900 h_flex()
8901 .bg(cx.theme().colors().editor_background)
8902 .border(BORDER_WIDTH)
8903 .shadow_xs()
8904 .border_color(cx.theme().colors().border)
8905 .rounded_l_lg()
8906 .when(line_count > 1, |el| el.rounded_br_lg())
8907 .pr_1()
8908 .child(styled_text),
8909 )
8910 .child(
8911 h_flex()
8912 .h(line_height + BORDER_WIDTH * 2.)
8913 .px_1p5()
8914 .gap_1()
8915 // Workaround: For some reason, there's a gap if we don't do this
8916 .ml(-BORDER_WIDTH)
8917 .shadow(vec![gpui::BoxShadow {
8918 color: gpui::black().opacity(0.05),
8919 offset: point(px(1.), px(1.)),
8920 blur_radius: px(2.),
8921 spread_radius: px(0.),
8922 }])
8923 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8924 .border(BORDER_WIDTH)
8925 .border_color(cx.theme().colors().border)
8926 .rounded_r_lg()
8927 .id("edit_prediction_diff_popover_keybind")
8928 .when(!has_keybind, |el| {
8929 let status_colors = cx.theme().status();
8930
8931 el.bg(status_colors.error_background)
8932 .border_color(status_colors.error.opacity(0.6))
8933 .child(Icon::new(IconName::Info).color(Color::Error))
8934 .cursor_default()
8935 .hoverable_tooltip(move |_window, cx| {
8936 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8937 })
8938 })
8939 .children(keybind),
8940 )
8941 .into_any();
8942
8943 let longest_row =
8944 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8945 let longest_line_width = if visible_row_range.contains(&longest_row) {
8946 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8947 } else {
8948 layout_line(
8949 longest_row,
8950 editor_snapshot,
8951 style,
8952 editor_width,
8953 |_| false,
8954 window,
8955 cx,
8956 )
8957 .width
8958 };
8959
8960 let viewport_bounds =
8961 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8962 right: -right_margin,
8963 ..Default::default()
8964 });
8965
8966 let x_after_longest =
8967 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8968 - scroll_pixel_position.x;
8969
8970 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8971
8972 // Fully visible if it can be displayed within the window (allow overlapping other
8973 // panes). However, this is only allowed if the popover starts within text_bounds.
8974 let can_position_to_the_right = x_after_longest < text_bounds.right()
8975 && x_after_longest + element_bounds.width < viewport_bounds.right();
8976
8977 let mut origin = if can_position_to_the_right {
8978 point(
8979 x_after_longest,
8980 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8981 - scroll_pixel_position.y,
8982 )
8983 } else {
8984 let cursor_row = newest_selection_head.map(|head| head.row());
8985 let above_edit = edit_start
8986 .row()
8987 .0
8988 .checked_sub(line_count as u32)
8989 .map(DisplayRow);
8990 let below_edit = Some(edit_end.row() + 1);
8991 let above_cursor =
8992 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8993 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8994
8995 // Place the edit popover adjacent to the edit if there is a location
8996 // available that is onscreen and does not obscure the cursor. Otherwise,
8997 // place it adjacent to the cursor.
8998 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8999 .into_iter()
9000 .flatten()
9001 .find(|&start_row| {
9002 let end_row = start_row + line_count as u32;
9003 visible_row_range.contains(&start_row)
9004 && visible_row_range.contains(&end_row)
9005 && cursor_row
9006 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9007 })?;
9008
9009 content_origin
9010 + point(
9011 -scroll_pixel_position.x,
9012 row_target.as_f32() * line_height - scroll_pixel_position.y,
9013 )
9014 };
9015
9016 origin.x -= BORDER_WIDTH;
9017
9018 window.defer_draw(element, origin, 1);
9019
9020 // Do not return an element, since it will already be drawn due to defer_draw.
9021 None
9022 }
9023
9024 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9025 px(30.)
9026 }
9027
9028 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9029 if self.read_only(cx) {
9030 cx.theme().players().read_only()
9031 } else {
9032 self.style.as_ref().unwrap().local_player
9033 }
9034 }
9035
9036 fn render_edit_prediction_accept_keybind(
9037 &self,
9038 window: &mut Window,
9039 cx: &App,
9040 ) -> Option<AnyElement> {
9041 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9042 let accept_keystroke = accept_binding.keystroke()?;
9043
9044 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9045
9046 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9047 Color::Accent
9048 } else {
9049 Color::Muted
9050 };
9051
9052 h_flex()
9053 .px_0p5()
9054 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9055 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9056 .text_size(TextSize::XSmall.rems(cx))
9057 .child(h_flex().children(ui::render_modifiers(
9058 accept_keystroke.modifiers(),
9059 PlatformStyle::platform(),
9060 Some(modifiers_color),
9061 Some(IconSize::XSmall.rems().into()),
9062 true,
9063 )))
9064 .when(is_platform_style_mac, |parent| {
9065 parent.child(accept_keystroke.key().to_string())
9066 })
9067 .when(!is_platform_style_mac, |parent| {
9068 parent.child(
9069 Key::new(
9070 util::capitalize(accept_keystroke.key()),
9071 Some(Color::Default),
9072 )
9073 .size(Some(IconSize::XSmall.rems().into())),
9074 )
9075 })
9076 .into_any()
9077 .into()
9078 }
9079
9080 fn render_edit_prediction_line_popover(
9081 &self,
9082 label: impl Into<SharedString>,
9083 icon: Option<IconName>,
9084 window: &mut Window,
9085 cx: &App,
9086 ) -> Option<Stateful<Div>> {
9087 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9088
9089 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9090 let has_keybind = keybind.is_some();
9091
9092 let result = h_flex()
9093 .id("ep-line-popover")
9094 .py_0p5()
9095 .pl_1()
9096 .pr(padding_right)
9097 .gap_1()
9098 .rounded_md()
9099 .border_1()
9100 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9101 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9102 .shadow_xs()
9103 .when(!has_keybind, |el| {
9104 let status_colors = cx.theme().status();
9105
9106 el.bg(status_colors.error_background)
9107 .border_color(status_colors.error.opacity(0.6))
9108 .pl_2()
9109 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9110 .cursor_default()
9111 .hoverable_tooltip(move |_window, cx| {
9112 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9113 })
9114 })
9115 .children(keybind)
9116 .child(
9117 Label::new(label)
9118 .size(LabelSize::Small)
9119 .when(!has_keybind, |el| {
9120 el.color(cx.theme().status().error.into()).strikethrough()
9121 }),
9122 )
9123 .when(!has_keybind, |el| {
9124 el.child(
9125 h_flex().ml_1().child(
9126 Icon::new(IconName::Info)
9127 .size(IconSize::Small)
9128 .color(cx.theme().status().error.into()),
9129 ),
9130 )
9131 })
9132 .when_some(icon, |element, icon| {
9133 element.child(
9134 div()
9135 .mt(px(1.5))
9136 .child(Icon::new(icon).size(IconSize::Small)),
9137 )
9138 });
9139
9140 Some(result)
9141 }
9142
9143 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9144 let accent_color = cx.theme().colors().text_accent;
9145 let editor_bg_color = cx.theme().colors().editor_background;
9146 editor_bg_color.blend(accent_color.opacity(0.1))
9147 }
9148
9149 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9150 let accent_color = cx.theme().colors().text_accent;
9151 let editor_bg_color = cx.theme().colors().editor_background;
9152 editor_bg_color.blend(accent_color.opacity(0.6))
9153 }
9154 fn get_prediction_provider_icon_name(
9155 provider: &Option<RegisteredEditPredictionProvider>,
9156 ) -> IconName {
9157 match provider {
9158 Some(provider) => match provider.provider.name() {
9159 "copilot" => IconName::Copilot,
9160 "supermaven" => IconName::Supermaven,
9161 _ => IconName::ZedPredict,
9162 },
9163 None => IconName::ZedPredict,
9164 }
9165 }
9166
9167 fn render_edit_prediction_cursor_popover(
9168 &self,
9169 min_width: Pixels,
9170 max_width: Pixels,
9171 cursor_point: Point,
9172 style: &EditorStyle,
9173 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9174 _window: &Window,
9175 cx: &mut Context<Editor>,
9176 ) -> Option<AnyElement> {
9177 let provider = self.edit_prediction_provider.as_ref()?;
9178 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9179
9180 let is_refreshing = provider.provider.is_refreshing(cx);
9181
9182 fn pending_completion_container(icon: IconName) -> Div {
9183 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9184 }
9185
9186 let completion = match &self.active_edit_prediction {
9187 Some(prediction) => {
9188 if !self.has_visible_completions_menu() {
9189 const RADIUS: Pixels = px(6.);
9190 const BORDER_WIDTH: Pixels = px(1.);
9191
9192 return Some(
9193 h_flex()
9194 .elevation_2(cx)
9195 .border(BORDER_WIDTH)
9196 .border_color(cx.theme().colors().border)
9197 .when(accept_keystroke.is_none(), |el| {
9198 el.border_color(cx.theme().status().error)
9199 })
9200 .rounded(RADIUS)
9201 .rounded_tl(px(0.))
9202 .overflow_hidden()
9203 .child(div().px_1p5().child(match &prediction.completion {
9204 EditPrediction::Move { target, snapshot } => {
9205 use text::ToPoint as _;
9206 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9207 {
9208 Icon::new(IconName::ZedPredictDown)
9209 } else {
9210 Icon::new(IconName::ZedPredictUp)
9211 }
9212 }
9213 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9214 }))
9215 .child(
9216 h_flex()
9217 .gap_1()
9218 .py_1()
9219 .px_2()
9220 .rounded_r(RADIUS - BORDER_WIDTH)
9221 .border_l_1()
9222 .border_color(cx.theme().colors().border)
9223 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9224 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9225 el.child(
9226 Label::new("Hold")
9227 .size(LabelSize::Small)
9228 .when(accept_keystroke.is_none(), |el| {
9229 el.strikethrough()
9230 })
9231 .line_height_style(LineHeightStyle::UiLabel),
9232 )
9233 })
9234 .id("edit_prediction_cursor_popover_keybind")
9235 .when(accept_keystroke.is_none(), |el| {
9236 let status_colors = cx.theme().status();
9237
9238 el.bg(status_colors.error_background)
9239 .border_color(status_colors.error.opacity(0.6))
9240 .child(Icon::new(IconName::Info).color(Color::Error))
9241 .cursor_default()
9242 .hoverable_tooltip(move |_window, cx| {
9243 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9244 .into()
9245 })
9246 })
9247 .when_some(
9248 accept_keystroke.as_ref(),
9249 |el, accept_keystroke| {
9250 el.child(h_flex().children(ui::render_modifiers(
9251 accept_keystroke.modifiers(),
9252 PlatformStyle::platform(),
9253 Some(Color::Default),
9254 Some(IconSize::XSmall.rems().into()),
9255 false,
9256 )))
9257 },
9258 ),
9259 )
9260 .into_any(),
9261 );
9262 }
9263
9264 self.render_edit_prediction_cursor_popover_preview(
9265 prediction,
9266 cursor_point,
9267 style,
9268 cx,
9269 )?
9270 }
9271
9272 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9273 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9274 stale_completion,
9275 cursor_point,
9276 style,
9277 cx,
9278 )?,
9279
9280 None => pending_completion_container(provider_icon)
9281 .child(Label::new("...").size(LabelSize::Small)),
9282 },
9283
9284 None => pending_completion_container(provider_icon)
9285 .child(Label::new("...").size(LabelSize::Small)),
9286 };
9287
9288 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9289 completion
9290 .with_animation(
9291 "loading-completion",
9292 Animation::new(Duration::from_secs(2))
9293 .repeat()
9294 .with_easing(pulsating_between(0.4, 0.8)),
9295 |label, delta| label.opacity(delta),
9296 )
9297 .into_any_element()
9298 } else {
9299 completion.into_any_element()
9300 };
9301
9302 let has_completion = self.active_edit_prediction.is_some();
9303
9304 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9305 Some(
9306 h_flex()
9307 .min_w(min_width)
9308 .max_w(max_width)
9309 .flex_1()
9310 .elevation_2(cx)
9311 .border_color(cx.theme().colors().border)
9312 .child(
9313 div()
9314 .flex_1()
9315 .py_1()
9316 .px_2()
9317 .overflow_hidden()
9318 .child(completion),
9319 )
9320 .when_some(accept_keystroke, |el, accept_keystroke| {
9321 if !accept_keystroke.modifiers().modified() {
9322 return el;
9323 }
9324
9325 el.child(
9326 h_flex()
9327 .h_full()
9328 .border_l_1()
9329 .rounded_r_lg()
9330 .border_color(cx.theme().colors().border)
9331 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9332 .gap_1()
9333 .py_1()
9334 .px_2()
9335 .child(
9336 h_flex()
9337 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9338 .when(is_platform_style_mac, |parent| parent.gap_1())
9339 .child(h_flex().children(ui::render_modifiers(
9340 accept_keystroke.modifiers(),
9341 PlatformStyle::platform(),
9342 Some(if !has_completion {
9343 Color::Muted
9344 } else {
9345 Color::Default
9346 }),
9347 None,
9348 false,
9349 ))),
9350 )
9351 .child(Label::new("Preview").into_any_element())
9352 .opacity(if has_completion { 1.0 } else { 0.4 }),
9353 )
9354 })
9355 .into_any(),
9356 )
9357 }
9358
9359 fn render_edit_prediction_cursor_popover_preview(
9360 &self,
9361 completion: &EditPredictionState,
9362 cursor_point: Point,
9363 style: &EditorStyle,
9364 cx: &mut Context<Editor>,
9365 ) -> Option<Div> {
9366 use text::ToPoint as _;
9367
9368 fn render_relative_row_jump(
9369 prefix: impl Into<String>,
9370 current_row: u32,
9371 target_row: u32,
9372 ) -> Div {
9373 let (row_diff, arrow) = if target_row < current_row {
9374 (current_row - target_row, IconName::ArrowUp)
9375 } else {
9376 (target_row - current_row, IconName::ArrowDown)
9377 };
9378
9379 h_flex()
9380 .child(
9381 Label::new(format!("{}{}", prefix.into(), row_diff))
9382 .color(Color::Muted)
9383 .size(LabelSize::Small),
9384 )
9385 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9386 }
9387
9388 let supports_jump = self
9389 .edit_prediction_provider
9390 .as_ref()
9391 .map(|provider| provider.provider.supports_jump_to_edit())
9392 .unwrap_or(true);
9393
9394 match &completion.completion {
9395 EditPrediction::Move {
9396 target, snapshot, ..
9397 } => {
9398 if !supports_jump {
9399 return None;
9400 }
9401
9402 Some(
9403 h_flex()
9404 .px_2()
9405 .gap_2()
9406 .flex_1()
9407 .child(
9408 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9409 Icon::new(IconName::ZedPredictDown)
9410 } else {
9411 Icon::new(IconName::ZedPredictUp)
9412 },
9413 )
9414 .child(Label::new("Jump to Edit")),
9415 )
9416 }
9417
9418 EditPrediction::Edit {
9419 edits,
9420 edit_preview,
9421 snapshot,
9422 display_mode: _,
9423 } => {
9424 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9425
9426 let (highlighted_edits, has_more_lines) =
9427 if let Some(edit_preview) = edit_preview.as_ref() {
9428 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9429 .first_line_preview()
9430 } else {
9431 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9432 };
9433
9434 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9435 .with_default_highlights(&style.text, highlighted_edits.highlights);
9436
9437 let preview = h_flex()
9438 .gap_1()
9439 .min_w_16()
9440 .child(styled_text)
9441 .when(has_more_lines, |parent| parent.child("…"));
9442
9443 let left = if supports_jump && first_edit_row != cursor_point.row {
9444 render_relative_row_jump("", cursor_point.row, first_edit_row)
9445 .into_any_element()
9446 } else {
9447 let icon_name =
9448 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9449 Icon::new(icon_name).into_any_element()
9450 };
9451
9452 Some(
9453 h_flex()
9454 .h_full()
9455 .flex_1()
9456 .gap_2()
9457 .pr_1()
9458 .overflow_x_hidden()
9459 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9460 .child(left)
9461 .child(preview),
9462 )
9463 }
9464 }
9465 }
9466
9467 pub fn render_context_menu(
9468 &self,
9469 style: &EditorStyle,
9470 max_height_in_lines: u32,
9471 window: &mut Window,
9472 cx: &mut Context<Editor>,
9473 ) -> Option<AnyElement> {
9474 let menu = self.context_menu.borrow();
9475 let menu = menu.as_ref()?;
9476 if !menu.visible() {
9477 return None;
9478 };
9479 Some(menu.render(style, max_height_in_lines, window, cx))
9480 }
9481
9482 fn render_context_menu_aside(
9483 &mut self,
9484 max_size: Size<Pixels>,
9485 window: &mut Window,
9486 cx: &mut Context<Editor>,
9487 ) -> Option<AnyElement> {
9488 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9489 if menu.visible() {
9490 menu.render_aside(max_size, window, cx)
9491 } else {
9492 None
9493 }
9494 })
9495 }
9496
9497 fn hide_context_menu(
9498 &mut self,
9499 window: &mut Window,
9500 cx: &mut Context<Self>,
9501 ) -> Option<CodeContextMenu> {
9502 cx.notify();
9503 self.completion_tasks.clear();
9504 let context_menu = self.context_menu.borrow_mut().take();
9505 self.stale_edit_prediction_in_menu.take();
9506 self.update_visible_edit_prediction(window, cx);
9507 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9508 && let Some(completion_provider) = &self.completion_provider
9509 {
9510 completion_provider.selection_changed(None, window, cx);
9511 }
9512 context_menu
9513 }
9514
9515 fn show_snippet_choices(
9516 &mut self,
9517 choices: &Vec<String>,
9518 selection: Range<Anchor>,
9519 cx: &mut Context<Self>,
9520 ) {
9521 let Some((_, buffer, _)) = self
9522 .buffer()
9523 .read(cx)
9524 .excerpt_containing(selection.start, cx)
9525 else {
9526 return;
9527 };
9528 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9529 else {
9530 return;
9531 };
9532 if buffer != end_buffer {
9533 log::error!("expected anchor range to have matching buffer IDs");
9534 return;
9535 }
9536
9537 let id = post_inc(&mut self.next_completion_id);
9538 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9539 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9540 CompletionsMenu::new_snippet_choices(
9541 id,
9542 true,
9543 choices,
9544 selection,
9545 buffer,
9546 snippet_sort_order,
9547 ),
9548 ));
9549 }
9550
9551 pub fn insert_snippet(
9552 &mut self,
9553 insertion_ranges: &[Range<usize>],
9554 snippet: Snippet,
9555 window: &mut Window,
9556 cx: &mut Context<Self>,
9557 ) -> Result<()> {
9558 struct Tabstop<T> {
9559 is_end_tabstop: bool,
9560 ranges: Vec<Range<T>>,
9561 choices: Option<Vec<String>>,
9562 }
9563
9564 let tabstops = self.buffer.update(cx, |buffer, cx| {
9565 let snippet_text: Arc<str> = snippet.text.clone().into();
9566 let edits = insertion_ranges
9567 .iter()
9568 .cloned()
9569 .map(|range| (range, snippet_text.clone()));
9570 let autoindent_mode = AutoindentMode::Block {
9571 original_indent_columns: Vec::new(),
9572 };
9573 buffer.edit(edits, Some(autoindent_mode), cx);
9574
9575 let snapshot = &*buffer.read(cx);
9576 let snippet = &snippet;
9577 snippet
9578 .tabstops
9579 .iter()
9580 .map(|tabstop| {
9581 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9582 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9583 });
9584 let mut tabstop_ranges = tabstop
9585 .ranges
9586 .iter()
9587 .flat_map(|tabstop_range| {
9588 let mut delta = 0_isize;
9589 insertion_ranges.iter().map(move |insertion_range| {
9590 let insertion_start = insertion_range.start as isize + delta;
9591 delta +=
9592 snippet.text.len() as isize - insertion_range.len() as isize;
9593
9594 let start = ((insertion_start + tabstop_range.start) as usize)
9595 .min(snapshot.len());
9596 let end = ((insertion_start + tabstop_range.end) as usize)
9597 .min(snapshot.len());
9598 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9599 })
9600 })
9601 .collect::<Vec<_>>();
9602 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9603
9604 Tabstop {
9605 is_end_tabstop,
9606 ranges: tabstop_ranges,
9607 choices: tabstop.choices.clone(),
9608 }
9609 })
9610 .collect::<Vec<_>>()
9611 });
9612 if let Some(tabstop) = tabstops.first() {
9613 self.change_selections(Default::default(), window, cx, |s| {
9614 // Reverse order so that the first range is the newest created selection.
9615 // Completions will use it and autoscroll will prioritize it.
9616 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9617 });
9618
9619 if let Some(choices) = &tabstop.choices
9620 && let Some(selection) = tabstop.ranges.first()
9621 {
9622 self.show_snippet_choices(choices, selection.clone(), cx)
9623 }
9624
9625 // If we're already at the last tabstop and it's at the end of the snippet,
9626 // we're done, we don't need to keep the state around.
9627 if !tabstop.is_end_tabstop {
9628 let choices = tabstops
9629 .iter()
9630 .map(|tabstop| tabstop.choices.clone())
9631 .collect();
9632
9633 let ranges = tabstops
9634 .into_iter()
9635 .map(|tabstop| tabstop.ranges)
9636 .collect::<Vec<_>>();
9637
9638 self.snippet_stack.push(SnippetState {
9639 active_index: 0,
9640 ranges,
9641 choices,
9642 });
9643 }
9644
9645 // Check whether the just-entered snippet ends with an auto-closable bracket.
9646 if self.autoclose_regions.is_empty() {
9647 let snapshot = self.buffer.read(cx).snapshot(cx);
9648 let mut all_selections = self.selections.all::<Point>(cx);
9649 for selection in &mut all_selections {
9650 let selection_head = selection.head();
9651 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9652 continue;
9653 };
9654
9655 let mut bracket_pair = None;
9656 let max_lookup_length = scope
9657 .brackets()
9658 .map(|(pair, _)| {
9659 pair.start
9660 .as_str()
9661 .chars()
9662 .count()
9663 .max(pair.end.as_str().chars().count())
9664 })
9665 .max();
9666 if let Some(max_lookup_length) = max_lookup_length {
9667 let next_text = snapshot
9668 .chars_at(selection_head)
9669 .take(max_lookup_length)
9670 .collect::<String>();
9671 let prev_text = snapshot
9672 .reversed_chars_at(selection_head)
9673 .take(max_lookup_length)
9674 .collect::<String>();
9675
9676 for (pair, enabled) in scope.brackets() {
9677 if enabled
9678 && pair.close
9679 && prev_text.starts_with(pair.start.as_str())
9680 && next_text.starts_with(pair.end.as_str())
9681 {
9682 bracket_pair = Some(pair.clone());
9683 break;
9684 }
9685 }
9686 }
9687
9688 if let Some(pair) = bracket_pair {
9689 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9690 let autoclose_enabled =
9691 self.use_autoclose && snapshot_settings.use_autoclose;
9692 if autoclose_enabled {
9693 let start = snapshot.anchor_after(selection_head);
9694 let end = snapshot.anchor_after(selection_head);
9695 self.autoclose_regions.push(AutocloseRegion {
9696 selection_id: selection.id,
9697 range: start..end,
9698 pair,
9699 });
9700 }
9701 }
9702 }
9703 }
9704 }
9705 Ok(())
9706 }
9707
9708 pub fn move_to_next_snippet_tabstop(
9709 &mut self,
9710 window: &mut Window,
9711 cx: &mut Context<Self>,
9712 ) -> bool {
9713 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9714 }
9715
9716 pub fn move_to_prev_snippet_tabstop(
9717 &mut self,
9718 window: &mut Window,
9719 cx: &mut Context<Self>,
9720 ) -> bool {
9721 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9722 }
9723
9724 pub fn move_to_snippet_tabstop(
9725 &mut self,
9726 bias: Bias,
9727 window: &mut Window,
9728 cx: &mut Context<Self>,
9729 ) -> bool {
9730 if let Some(mut snippet) = self.snippet_stack.pop() {
9731 match bias {
9732 Bias::Left => {
9733 if snippet.active_index > 0 {
9734 snippet.active_index -= 1;
9735 } else {
9736 self.snippet_stack.push(snippet);
9737 return false;
9738 }
9739 }
9740 Bias::Right => {
9741 if snippet.active_index + 1 < snippet.ranges.len() {
9742 snippet.active_index += 1;
9743 } else {
9744 self.snippet_stack.push(snippet);
9745 return false;
9746 }
9747 }
9748 }
9749 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9750 self.change_selections(Default::default(), window, cx, |s| {
9751 // Reverse order so that the first range is the newest created selection.
9752 // Completions will use it and autoscroll will prioritize it.
9753 s.select_ranges(current_ranges.iter().rev().cloned())
9754 });
9755
9756 if let Some(choices) = &snippet.choices[snippet.active_index]
9757 && let Some(selection) = current_ranges.first()
9758 {
9759 self.show_snippet_choices(choices, selection.clone(), cx);
9760 }
9761
9762 // If snippet state is not at the last tabstop, push it back on the stack
9763 if snippet.active_index + 1 < snippet.ranges.len() {
9764 self.snippet_stack.push(snippet);
9765 }
9766 return true;
9767 }
9768 }
9769
9770 false
9771 }
9772
9773 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9774 self.transact(window, cx, |this, window, cx| {
9775 this.select_all(&SelectAll, window, cx);
9776 this.insert("", window, cx);
9777 });
9778 }
9779
9780 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9781 if self.read_only(cx) {
9782 return;
9783 }
9784 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9785 self.transact(window, cx, |this, window, cx| {
9786 this.select_autoclose_pair(window, cx);
9787 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9788 if !this.linked_edit_ranges.is_empty() {
9789 let selections = this.selections.all::<MultiBufferPoint>(cx);
9790 let snapshot = this.buffer.read(cx).snapshot(cx);
9791
9792 for selection in selections.iter() {
9793 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9794 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9795 if selection_start.buffer_id != selection_end.buffer_id {
9796 continue;
9797 }
9798 if let Some(ranges) =
9799 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9800 {
9801 for (buffer, entries) in ranges {
9802 linked_ranges.entry(buffer).or_default().extend(entries);
9803 }
9804 }
9805 }
9806 }
9807
9808 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9809 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9810 for selection in &mut selections {
9811 if selection.is_empty() {
9812 let old_head = selection.head();
9813 let mut new_head =
9814 movement::left(&display_map, old_head.to_display_point(&display_map))
9815 .to_point(&display_map);
9816 if let Some((buffer, line_buffer_range)) = display_map
9817 .buffer_snapshot
9818 .buffer_line_for_row(MultiBufferRow(old_head.row))
9819 {
9820 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9821 let indent_len = match indent_size.kind {
9822 IndentKind::Space => {
9823 buffer.settings_at(line_buffer_range.start, cx).tab_size
9824 }
9825 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9826 };
9827 if old_head.column <= indent_size.len && old_head.column > 0 {
9828 let indent_len = indent_len.get();
9829 new_head = cmp::min(
9830 new_head,
9831 MultiBufferPoint::new(
9832 old_head.row,
9833 ((old_head.column - 1) / indent_len) * indent_len,
9834 ),
9835 );
9836 }
9837 }
9838
9839 selection.set_head(new_head, SelectionGoal::None);
9840 }
9841 }
9842
9843 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9844 this.insert("", window, cx);
9845 let empty_str: Arc<str> = Arc::from("");
9846 for (buffer, edits) in linked_ranges {
9847 let snapshot = buffer.read(cx).snapshot();
9848 use text::ToPoint as TP;
9849
9850 let edits = edits
9851 .into_iter()
9852 .map(|range| {
9853 let end_point = TP::to_point(&range.end, &snapshot);
9854 let mut start_point = TP::to_point(&range.start, &snapshot);
9855
9856 if end_point == start_point {
9857 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9858 .saturating_sub(1);
9859 start_point =
9860 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9861 };
9862
9863 (start_point..end_point, empty_str.clone())
9864 })
9865 .sorted_by_key(|(range, _)| range.start)
9866 .collect::<Vec<_>>();
9867 buffer.update(cx, |this, cx| {
9868 this.edit(edits, None, cx);
9869 })
9870 }
9871 this.refresh_edit_prediction(true, false, window, cx);
9872 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9873 });
9874 }
9875
9876 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9877 if self.read_only(cx) {
9878 return;
9879 }
9880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9881 self.transact(window, cx, |this, window, cx| {
9882 this.change_selections(Default::default(), window, cx, |s| {
9883 s.move_with(|map, selection| {
9884 if selection.is_empty() {
9885 let cursor = movement::right(map, selection.head());
9886 selection.end = cursor;
9887 selection.reversed = true;
9888 selection.goal = SelectionGoal::None;
9889 }
9890 })
9891 });
9892 this.insert("", window, cx);
9893 this.refresh_edit_prediction(true, false, window, cx);
9894 });
9895 }
9896
9897 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9898 if self.mode.is_single_line() {
9899 cx.propagate();
9900 return;
9901 }
9902
9903 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9904 if self.move_to_prev_snippet_tabstop(window, cx) {
9905 return;
9906 }
9907 self.outdent(&Outdent, window, cx);
9908 }
9909
9910 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9911 if self.mode.is_single_line() {
9912 cx.propagate();
9913 return;
9914 }
9915
9916 if self.move_to_next_snippet_tabstop(window, cx) {
9917 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9918 return;
9919 }
9920 if self.read_only(cx) {
9921 return;
9922 }
9923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9924 let mut selections = self.selections.all_adjusted(cx);
9925 let buffer = self.buffer.read(cx);
9926 let snapshot = buffer.snapshot(cx);
9927 let rows_iter = selections.iter().map(|s| s.head().row);
9928 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9929
9930 let has_some_cursor_in_whitespace = selections
9931 .iter()
9932 .filter(|selection| selection.is_empty())
9933 .any(|selection| {
9934 let cursor = selection.head();
9935 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9936 cursor.column < current_indent.len
9937 });
9938
9939 let mut edits = Vec::new();
9940 let mut prev_edited_row = 0;
9941 let mut row_delta = 0;
9942 for selection in &mut selections {
9943 if selection.start.row != prev_edited_row {
9944 row_delta = 0;
9945 }
9946 prev_edited_row = selection.end.row;
9947
9948 // If the selection is non-empty, then increase the indentation of the selected lines.
9949 if !selection.is_empty() {
9950 row_delta =
9951 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9952 continue;
9953 }
9954
9955 let cursor = selection.head();
9956 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9957 if let Some(suggested_indent) =
9958 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9959 {
9960 // Don't do anything if already at suggested indent
9961 // and there is any other cursor which is not
9962 if has_some_cursor_in_whitespace
9963 && cursor.column == current_indent.len
9964 && current_indent.len == suggested_indent.len
9965 {
9966 continue;
9967 }
9968
9969 // Adjust line and move cursor to suggested indent
9970 // if cursor is not at suggested indent
9971 if cursor.column < suggested_indent.len
9972 && cursor.column <= current_indent.len
9973 && current_indent.len <= suggested_indent.len
9974 {
9975 selection.start = Point::new(cursor.row, suggested_indent.len);
9976 selection.end = selection.start;
9977 if row_delta == 0 {
9978 edits.extend(Buffer::edit_for_indent_size_adjustment(
9979 cursor.row,
9980 current_indent,
9981 suggested_indent,
9982 ));
9983 row_delta = suggested_indent.len - current_indent.len;
9984 }
9985 continue;
9986 }
9987
9988 // If current indent is more than suggested indent
9989 // only move cursor to current indent and skip indent
9990 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9991 selection.start = Point::new(cursor.row, current_indent.len);
9992 selection.end = selection.start;
9993 continue;
9994 }
9995 }
9996
9997 // Otherwise, insert a hard or soft tab.
9998 let settings = buffer.language_settings_at(cursor, cx);
9999 let tab_size = if settings.hard_tabs {
10000 IndentSize::tab()
10001 } else {
10002 let tab_size = settings.tab_size.get();
10003 let indent_remainder = snapshot
10004 .text_for_range(Point::new(cursor.row, 0)..cursor)
10005 .flat_map(str::chars)
10006 .fold(row_delta % tab_size, |counter: u32, c| {
10007 if c == '\t' {
10008 0
10009 } else {
10010 (counter + 1) % tab_size
10011 }
10012 });
10013
10014 let chars_to_next_tab_stop = tab_size - indent_remainder;
10015 IndentSize::spaces(chars_to_next_tab_stop)
10016 };
10017 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10018 selection.end = selection.start;
10019 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10020 row_delta += tab_size.len;
10021 }
10022
10023 self.transact(window, cx, |this, window, cx| {
10024 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10025 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10026 this.refresh_edit_prediction(true, false, window, cx);
10027 });
10028 }
10029
10030 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10031 if self.read_only(cx) {
10032 return;
10033 }
10034 if self.mode.is_single_line() {
10035 cx.propagate();
10036 return;
10037 }
10038
10039 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10040 let mut selections = self.selections.all::<Point>(cx);
10041 let mut prev_edited_row = 0;
10042 let mut row_delta = 0;
10043 let mut edits = Vec::new();
10044 let buffer = self.buffer.read(cx);
10045 let snapshot = buffer.snapshot(cx);
10046 for selection in &mut selections {
10047 if selection.start.row != prev_edited_row {
10048 row_delta = 0;
10049 }
10050 prev_edited_row = selection.end.row;
10051
10052 row_delta =
10053 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10054 }
10055
10056 self.transact(window, cx, |this, window, cx| {
10057 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10058 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10059 });
10060 }
10061
10062 fn indent_selection(
10063 buffer: &MultiBuffer,
10064 snapshot: &MultiBufferSnapshot,
10065 selection: &mut Selection<Point>,
10066 edits: &mut Vec<(Range<Point>, String)>,
10067 delta_for_start_row: u32,
10068 cx: &App,
10069 ) -> u32 {
10070 let settings = buffer.language_settings_at(selection.start, cx);
10071 let tab_size = settings.tab_size.get();
10072 let indent_kind = if settings.hard_tabs {
10073 IndentKind::Tab
10074 } else {
10075 IndentKind::Space
10076 };
10077 let mut start_row = selection.start.row;
10078 let mut end_row = selection.end.row + 1;
10079
10080 // If a selection ends at the beginning of a line, don't indent
10081 // that last line.
10082 if selection.end.column == 0 && selection.end.row > selection.start.row {
10083 end_row -= 1;
10084 }
10085
10086 // Avoid re-indenting a row that has already been indented by a
10087 // previous selection, but still update this selection's column
10088 // to reflect that indentation.
10089 if delta_for_start_row > 0 {
10090 start_row += 1;
10091 selection.start.column += delta_for_start_row;
10092 if selection.end.row == selection.start.row {
10093 selection.end.column += delta_for_start_row;
10094 }
10095 }
10096
10097 let mut delta_for_end_row = 0;
10098 let has_multiple_rows = start_row + 1 != end_row;
10099 for row in start_row..end_row {
10100 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10101 let indent_delta = match (current_indent.kind, indent_kind) {
10102 (IndentKind::Space, IndentKind::Space) => {
10103 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10104 IndentSize::spaces(columns_to_next_tab_stop)
10105 }
10106 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10107 (_, IndentKind::Tab) => IndentSize::tab(),
10108 };
10109
10110 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10111 0
10112 } else {
10113 selection.start.column
10114 };
10115 let row_start = Point::new(row, start);
10116 edits.push((
10117 row_start..row_start,
10118 indent_delta.chars().collect::<String>(),
10119 ));
10120
10121 // Update this selection's endpoints to reflect the indentation.
10122 if row == selection.start.row {
10123 selection.start.column += indent_delta.len;
10124 }
10125 if row == selection.end.row {
10126 selection.end.column += indent_delta.len;
10127 delta_for_end_row = indent_delta.len;
10128 }
10129 }
10130
10131 if selection.start.row == selection.end.row {
10132 delta_for_start_row + delta_for_end_row
10133 } else {
10134 delta_for_end_row
10135 }
10136 }
10137
10138 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10139 if self.read_only(cx) {
10140 return;
10141 }
10142 if self.mode.is_single_line() {
10143 cx.propagate();
10144 return;
10145 }
10146
10147 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10148 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10149 let selections = self.selections.all::<Point>(cx);
10150 let mut deletion_ranges = Vec::new();
10151 let mut last_outdent = None;
10152 {
10153 let buffer = self.buffer.read(cx);
10154 let snapshot = buffer.snapshot(cx);
10155 for selection in &selections {
10156 let settings = buffer.language_settings_at(selection.start, cx);
10157 let tab_size = settings.tab_size.get();
10158 let mut rows = selection.spanned_rows(false, &display_map);
10159
10160 // Avoid re-outdenting a row that has already been outdented by a
10161 // previous selection.
10162 if let Some(last_row) = last_outdent
10163 && last_row == rows.start
10164 {
10165 rows.start = rows.start.next_row();
10166 }
10167 let has_multiple_rows = rows.len() > 1;
10168 for row in rows.iter_rows() {
10169 let indent_size = snapshot.indent_size_for_line(row);
10170 if indent_size.len > 0 {
10171 let deletion_len = match indent_size.kind {
10172 IndentKind::Space => {
10173 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10174 if columns_to_prev_tab_stop == 0 {
10175 tab_size
10176 } else {
10177 columns_to_prev_tab_stop
10178 }
10179 }
10180 IndentKind::Tab => 1,
10181 };
10182 let start = if has_multiple_rows
10183 || deletion_len > selection.start.column
10184 || indent_size.len < selection.start.column
10185 {
10186 0
10187 } else {
10188 selection.start.column - deletion_len
10189 };
10190 deletion_ranges.push(
10191 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10192 );
10193 last_outdent = Some(row);
10194 }
10195 }
10196 }
10197 }
10198
10199 self.transact(window, cx, |this, window, cx| {
10200 this.buffer.update(cx, |buffer, cx| {
10201 let empty_str: Arc<str> = Arc::default();
10202 buffer.edit(
10203 deletion_ranges
10204 .into_iter()
10205 .map(|range| (range, empty_str.clone())),
10206 None,
10207 cx,
10208 );
10209 });
10210 let selections = this.selections.all::<usize>(cx);
10211 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10212 });
10213 }
10214
10215 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10216 if self.read_only(cx) {
10217 return;
10218 }
10219 if self.mode.is_single_line() {
10220 cx.propagate();
10221 return;
10222 }
10223
10224 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10225 let selections = self
10226 .selections
10227 .all::<usize>(cx)
10228 .into_iter()
10229 .map(|s| s.range());
10230
10231 self.transact(window, cx, |this, window, cx| {
10232 this.buffer.update(cx, |buffer, cx| {
10233 buffer.autoindent_ranges(selections, cx);
10234 });
10235 let selections = this.selections.all::<usize>(cx);
10236 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10237 });
10238 }
10239
10240 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10242 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10243 let selections = self.selections.all::<Point>(cx);
10244
10245 let mut new_cursors = Vec::new();
10246 let mut edit_ranges = Vec::new();
10247 let mut selections = selections.iter().peekable();
10248 while let Some(selection) = selections.next() {
10249 let mut rows = selection.spanned_rows(false, &display_map);
10250 let goal_display_column = selection.head().to_display_point(&display_map).column();
10251
10252 // Accumulate contiguous regions of rows that we want to delete.
10253 while let Some(next_selection) = selections.peek() {
10254 let next_rows = next_selection.spanned_rows(false, &display_map);
10255 if next_rows.start <= rows.end {
10256 rows.end = next_rows.end;
10257 selections.next().unwrap();
10258 } else {
10259 break;
10260 }
10261 }
10262
10263 let buffer = &display_map.buffer_snapshot;
10264 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10265 let edit_end;
10266 let cursor_buffer_row;
10267 if buffer.max_point().row >= rows.end.0 {
10268 // If there's a line after the range, delete the \n from the end of the row range
10269 // and position the cursor on the next line.
10270 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10271 cursor_buffer_row = rows.end;
10272 } else {
10273 // If there isn't a line after the range, delete the \n from the line before the
10274 // start of the row range and position the cursor there.
10275 edit_start = edit_start.saturating_sub(1);
10276 edit_end = buffer.len();
10277 cursor_buffer_row = rows.start.previous_row();
10278 }
10279
10280 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10281 *cursor.column_mut() =
10282 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10283
10284 new_cursors.push((
10285 selection.id,
10286 buffer.anchor_after(cursor.to_point(&display_map)),
10287 ));
10288 edit_ranges.push(edit_start..edit_end);
10289 }
10290
10291 self.transact(window, cx, |this, window, cx| {
10292 let buffer = this.buffer.update(cx, |buffer, cx| {
10293 let empty_str: Arc<str> = Arc::default();
10294 buffer.edit(
10295 edit_ranges
10296 .into_iter()
10297 .map(|range| (range, empty_str.clone())),
10298 None,
10299 cx,
10300 );
10301 buffer.snapshot(cx)
10302 });
10303 let new_selections = new_cursors
10304 .into_iter()
10305 .map(|(id, cursor)| {
10306 let cursor = cursor.to_point(&buffer);
10307 Selection {
10308 id,
10309 start: cursor,
10310 end: cursor,
10311 reversed: false,
10312 goal: SelectionGoal::None,
10313 }
10314 })
10315 .collect();
10316
10317 this.change_selections(Default::default(), window, cx, |s| {
10318 s.select(new_selections);
10319 });
10320 });
10321 }
10322
10323 pub fn join_lines_impl(
10324 &mut self,
10325 insert_whitespace: bool,
10326 window: &mut Window,
10327 cx: &mut Context<Self>,
10328 ) {
10329 if self.read_only(cx) {
10330 return;
10331 }
10332 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10333 for selection in self.selections.all::<Point>(cx) {
10334 let start = MultiBufferRow(selection.start.row);
10335 // Treat single line selections as if they include the next line. Otherwise this action
10336 // would do nothing for single line selections individual cursors.
10337 let end = if selection.start.row == selection.end.row {
10338 MultiBufferRow(selection.start.row + 1)
10339 } else {
10340 MultiBufferRow(selection.end.row)
10341 };
10342
10343 if let Some(last_row_range) = row_ranges.last_mut()
10344 && start <= last_row_range.end
10345 {
10346 last_row_range.end = end;
10347 continue;
10348 }
10349 row_ranges.push(start..end);
10350 }
10351
10352 let snapshot = self.buffer.read(cx).snapshot(cx);
10353 let mut cursor_positions = Vec::new();
10354 for row_range in &row_ranges {
10355 let anchor = snapshot.anchor_before(Point::new(
10356 row_range.end.previous_row().0,
10357 snapshot.line_len(row_range.end.previous_row()),
10358 ));
10359 cursor_positions.push(anchor..anchor);
10360 }
10361
10362 self.transact(window, cx, |this, window, cx| {
10363 for row_range in row_ranges.into_iter().rev() {
10364 for row in row_range.iter_rows().rev() {
10365 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10366 let next_line_row = row.next_row();
10367 let indent = snapshot.indent_size_for_line(next_line_row);
10368 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10369
10370 let replace =
10371 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10372 " "
10373 } else {
10374 ""
10375 };
10376
10377 this.buffer.update(cx, |buffer, cx| {
10378 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10379 });
10380 }
10381 }
10382
10383 this.change_selections(Default::default(), window, cx, |s| {
10384 s.select_anchor_ranges(cursor_positions)
10385 });
10386 });
10387 }
10388
10389 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10390 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10391 self.join_lines_impl(true, window, cx);
10392 }
10393
10394 pub fn sort_lines_case_sensitive(
10395 &mut self,
10396 _: &SortLinesCaseSensitive,
10397 window: &mut Window,
10398 cx: &mut Context<Self>,
10399 ) {
10400 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10401 }
10402
10403 pub fn sort_lines_by_length(
10404 &mut self,
10405 _: &SortLinesByLength,
10406 window: &mut Window,
10407 cx: &mut Context<Self>,
10408 ) {
10409 self.manipulate_immutable_lines(window, cx, |lines| {
10410 lines.sort_by_key(|&line| line.chars().count())
10411 })
10412 }
10413
10414 pub fn sort_lines_case_insensitive(
10415 &mut self,
10416 _: &SortLinesCaseInsensitive,
10417 window: &mut Window,
10418 cx: &mut Context<Self>,
10419 ) {
10420 self.manipulate_immutable_lines(window, cx, |lines| {
10421 lines.sort_by_key(|line| line.to_lowercase())
10422 })
10423 }
10424
10425 pub fn unique_lines_case_insensitive(
10426 &mut self,
10427 _: &UniqueLinesCaseInsensitive,
10428 window: &mut Window,
10429 cx: &mut Context<Self>,
10430 ) {
10431 self.manipulate_immutable_lines(window, cx, |lines| {
10432 let mut seen = HashSet::default();
10433 lines.retain(|line| seen.insert(line.to_lowercase()));
10434 })
10435 }
10436
10437 pub fn unique_lines_case_sensitive(
10438 &mut self,
10439 _: &UniqueLinesCaseSensitive,
10440 window: &mut Window,
10441 cx: &mut Context<Self>,
10442 ) {
10443 self.manipulate_immutable_lines(window, cx, |lines| {
10444 let mut seen = HashSet::default();
10445 lines.retain(|line| seen.insert(*line));
10446 })
10447 }
10448
10449 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10450 let snapshot = self.buffer.read(cx).snapshot(cx);
10451 for selection in self.selections.disjoint_anchors().iter() {
10452 if snapshot
10453 .language_at(selection.start)
10454 .and_then(|lang| lang.config().wrap_characters.as_ref())
10455 .is_some()
10456 {
10457 return true;
10458 }
10459 }
10460 false
10461 }
10462
10463 fn wrap_selections_in_tag(
10464 &mut self,
10465 _: &WrapSelectionsInTag,
10466 window: &mut Window,
10467 cx: &mut Context<Self>,
10468 ) {
10469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10470
10471 let snapshot = self.buffer.read(cx).snapshot(cx);
10472
10473 let mut edits = Vec::new();
10474 let mut boundaries = Vec::new();
10475
10476 for selection in self.selections.all::<Point>(cx).iter() {
10477 let Some(wrap_config) = snapshot
10478 .language_at(selection.start)
10479 .and_then(|lang| lang.config().wrap_characters.clone())
10480 else {
10481 continue;
10482 };
10483
10484 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10485 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10486
10487 let start_before = snapshot.anchor_before(selection.start);
10488 let end_after = snapshot.anchor_after(selection.end);
10489
10490 edits.push((start_before..start_before, open_tag));
10491 edits.push((end_after..end_after, close_tag));
10492
10493 boundaries.push((
10494 start_before,
10495 end_after,
10496 wrap_config.start_prefix.len(),
10497 wrap_config.end_suffix.len(),
10498 ));
10499 }
10500
10501 if edits.is_empty() {
10502 return;
10503 }
10504
10505 self.transact(window, cx, |this, window, cx| {
10506 let buffer = this.buffer.update(cx, |buffer, cx| {
10507 buffer.edit(edits, None, cx);
10508 buffer.snapshot(cx)
10509 });
10510
10511 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10512 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10513 boundaries.into_iter()
10514 {
10515 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10516 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10517 new_selections.push(open_offset..open_offset);
10518 new_selections.push(close_offset..close_offset);
10519 }
10520
10521 this.change_selections(Default::default(), window, cx, |s| {
10522 s.select_ranges(new_selections);
10523 });
10524
10525 this.request_autoscroll(Autoscroll::fit(), cx);
10526 });
10527 }
10528
10529 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10530 let Some(project) = self.project.clone() else {
10531 return;
10532 };
10533 self.reload(project, window, cx)
10534 .detach_and_notify_err(window, cx);
10535 }
10536
10537 pub fn restore_file(
10538 &mut self,
10539 _: &::git::RestoreFile,
10540 window: &mut Window,
10541 cx: &mut Context<Self>,
10542 ) {
10543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10544 let mut buffer_ids = HashSet::default();
10545 let snapshot = self.buffer().read(cx).snapshot(cx);
10546 for selection in self.selections.all::<usize>(cx) {
10547 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10548 }
10549
10550 let buffer = self.buffer().read(cx);
10551 let ranges = buffer_ids
10552 .into_iter()
10553 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10554 .collect::<Vec<_>>();
10555
10556 self.restore_hunks_in_ranges(ranges, window, cx);
10557 }
10558
10559 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10560 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10561 let selections = self
10562 .selections
10563 .all(cx)
10564 .into_iter()
10565 .map(|s| s.range())
10566 .collect();
10567 self.restore_hunks_in_ranges(selections, window, cx);
10568 }
10569
10570 pub fn restore_hunks_in_ranges(
10571 &mut self,
10572 ranges: Vec<Range<Point>>,
10573 window: &mut Window,
10574 cx: &mut Context<Editor>,
10575 ) {
10576 let mut revert_changes = HashMap::default();
10577 let chunk_by = self
10578 .snapshot(window, cx)
10579 .hunks_for_ranges(ranges)
10580 .into_iter()
10581 .chunk_by(|hunk| hunk.buffer_id);
10582 for (buffer_id, hunks) in &chunk_by {
10583 let hunks = hunks.collect::<Vec<_>>();
10584 for hunk in &hunks {
10585 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10586 }
10587 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10588 }
10589 drop(chunk_by);
10590 if !revert_changes.is_empty() {
10591 self.transact(window, cx, |editor, window, cx| {
10592 editor.restore(revert_changes, window, cx);
10593 });
10594 }
10595 }
10596
10597 pub fn open_active_item_in_terminal(
10598 &mut self,
10599 _: &OpenInTerminal,
10600 window: &mut Window,
10601 cx: &mut Context<Self>,
10602 ) {
10603 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10604 let project_path = buffer.read(cx).project_path(cx)?;
10605 let project = self.project()?.read(cx);
10606 let entry = project.entry_for_path(&project_path, cx)?;
10607 let parent = match &entry.canonical_path {
10608 Some(canonical_path) => canonical_path.to_path_buf(),
10609 None => project.absolute_path(&project_path, cx)?,
10610 }
10611 .parent()?
10612 .to_path_buf();
10613 Some(parent)
10614 }) {
10615 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10616 }
10617 }
10618
10619 fn set_breakpoint_context_menu(
10620 &mut self,
10621 display_row: DisplayRow,
10622 position: Option<Anchor>,
10623 clicked_point: gpui::Point<Pixels>,
10624 window: &mut Window,
10625 cx: &mut Context<Self>,
10626 ) {
10627 let source = self
10628 .buffer
10629 .read(cx)
10630 .snapshot(cx)
10631 .anchor_before(Point::new(display_row.0, 0u32));
10632
10633 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10634
10635 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10636 self,
10637 source,
10638 clicked_point,
10639 context_menu,
10640 window,
10641 cx,
10642 );
10643 }
10644
10645 fn add_edit_breakpoint_block(
10646 &mut self,
10647 anchor: Anchor,
10648 breakpoint: &Breakpoint,
10649 edit_action: BreakpointPromptEditAction,
10650 window: &mut Window,
10651 cx: &mut Context<Self>,
10652 ) {
10653 let weak_editor = cx.weak_entity();
10654 let bp_prompt = cx.new(|cx| {
10655 BreakpointPromptEditor::new(
10656 weak_editor,
10657 anchor,
10658 breakpoint.clone(),
10659 edit_action,
10660 window,
10661 cx,
10662 )
10663 });
10664
10665 let height = bp_prompt.update(cx, |this, cx| {
10666 this.prompt
10667 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10668 });
10669 let cloned_prompt = bp_prompt.clone();
10670 let blocks = vec![BlockProperties {
10671 style: BlockStyle::Sticky,
10672 placement: BlockPlacement::Above(anchor),
10673 height: Some(height),
10674 render: Arc::new(move |cx| {
10675 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10676 cloned_prompt.clone().into_any_element()
10677 }),
10678 priority: 0,
10679 }];
10680
10681 let focus_handle = bp_prompt.focus_handle(cx);
10682 window.focus(&focus_handle);
10683
10684 let block_ids = self.insert_blocks(blocks, None, cx);
10685 bp_prompt.update(cx, |prompt, _| {
10686 prompt.add_block_ids(block_ids);
10687 });
10688 }
10689
10690 pub(crate) fn breakpoint_at_row(
10691 &self,
10692 row: u32,
10693 window: &mut Window,
10694 cx: &mut Context<Self>,
10695 ) -> Option<(Anchor, Breakpoint)> {
10696 let snapshot = self.snapshot(window, cx);
10697 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10698
10699 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10700 }
10701
10702 pub(crate) fn breakpoint_at_anchor(
10703 &self,
10704 breakpoint_position: Anchor,
10705 snapshot: &EditorSnapshot,
10706 cx: &mut Context<Self>,
10707 ) -> Option<(Anchor, Breakpoint)> {
10708 let buffer = self
10709 .buffer
10710 .read(cx)
10711 .buffer_for_anchor(breakpoint_position, cx)?;
10712
10713 let enclosing_excerpt = breakpoint_position.excerpt_id;
10714 let buffer_snapshot = buffer.read(cx).snapshot();
10715
10716 let row = buffer_snapshot
10717 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10718 .row;
10719
10720 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10721 let anchor_end = snapshot
10722 .buffer_snapshot
10723 .anchor_after(Point::new(row, line_len));
10724
10725 self.breakpoint_store
10726 .as_ref()?
10727 .read_with(cx, |breakpoint_store, cx| {
10728 breakpoint_store
10729 .breakpoints(
10730 &buffer,
10731 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10732 &buffer_snapshot,
10733 cx,
10734 )
10735 .next()
10736 .and_then(|(bp, _)| {
10737 let breakpoint_row = buffer_snapshot
10738 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10739 .row;
10740
10741 if breakpoint_row == row {
10742 snapshot
10743 .buffer_snapshot
10744 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10745 .map(|position| (position, bp.bp.clone()))
10746 } else {
10747 None
10748 }
10749 })
10750 })
10751 }
10752
10753 pub fn edit_log_breakpoint(
10754 &mut self,
10755 _: &EditLogBreakpoint,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10760 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10761 message: None,
10762 state: BreakpointState::Enabled,
10763 condition: None,
10764 hit_condition: None,
10765 });
10766
10767 self.add_edit_breakpoint_block(
10768 anchor,
10769 &breakpoint,
10770 BreakpointPromptEditAction::Log,
10771 window,
10772 cx,
10773 );
10774 }
10775 }
10776
10777 fn breakpoints_at_cursors(
10778 &self,
10779 window: &mut Window,
10780 cx: &mut Context<Self>,
10781 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10782 let snapshot = self.snapshot(window, cx);
10783 let cursors = self
10784 .selections
10785 .disjoint_anchors()
10786 .iter()
10787 .map(|selection| {
10788 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10789
10790 let breakpoint_position = self
10791 .breakpoint_at_row(cursor_position.row, window, cx)
10792 .map(|bp| bp.0)
10793 .unwrap_or_else(|| {
10794 snapshot
10795 .display_snapshot
10796 .buffer_snapshot
10797 .anchor_after(Point::new(cursor_position.row, 0))
10798 });
10799
10800 let breakpoint = self
10801 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10802 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10803
10804 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10805 })
10806 // 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.
10807 .collect::<HashMap<Anchor, _>>();
10808
10809 cursors.into_iter().collect()
10810 }
10811
10812 pub fn enable_breakpoint(
10813 &mut self,
10814 _: &crate::actions::EnableBreakpoint,
10815 window: &mut Window,
10816 cx: &mut Context<Self>,
10817 ) {
10818 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10819 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10820 continue;
10821 };
10822 self.edit_breakpoint_at_anchor(
10823 anchor,
10824 breakpoint,
10825 BreakpointEditAction::InvertState,
10826 cx,
10827 );
10828 }
10829 }
10830
10831 pub fn disable_breakpoint(
10832 &mut self,
10833 _: &crate::actions::DisableBreakpoint,
10834 window: &mut Window,
10835 cx: &mut Context<Self>,
10836 ) {
10837 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10838 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10839 continue;
10840 };
10841 self.edit_breakpoint_at_anchor(
10842 anchor,
10843 breakpoint,
10844 BreakpointEditAction::InvertState,
10845 cx,
10846 );
10847 }
10848 }
10849
10850 pub fn toggle_breakpoint(
10851 &mut self,
10852 _: &crate::actions::ToggleBreakpoint,
10853 window: &mut Window,
10854 cx: &mut Context<Self>,
10855 ) {
10856 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10857 if let Some(breakpoint) = breakpoint {
10858 self.edit_breakpoint_at_anchor(
10859 anchor,
10860 breakpoint,
10861 BreakpointEditAction::Toggle,
10862 cx,
10863 );
10864 } else {
10865 self.edit_breakpoint_at_anchor(
10866 anchor,
10867 Breakpoint::new_standard(),
10868 BreakpointEditAction::Toggle,
10869 cx,
10870 );
10871 }
10872 }
10873 }
10874
10875 pub fn edit_breakpoint_at_anchor(
10876 &mut self,
10877 breakpoint_position: Anchor,
10878 breakpoint: Breakpoint,
10879 edit_action: BreakpointEditAction,
10880 cx: &mut Context<Self>,
10881 ) {
10882 let Some(breakpoint_store) = &self.breakpoint_store else {
10883 return;
10884 };
10885
10886 let Some(buffer) = self
10887 .buffer
10888 .read(cx)
10889 .buffer_for_anchor(breakpoint_position, cx)
10890 else {
10891 return;
10892 };
10893
10894 breakpoint_store.update(cx, |breakpoint_store, cx| {
10895 breakpoint_store.toggle_breakpoint(
10896 buffer,
10897 BreakpointWithPosition {
10898 position: breakpoint_position.text_anchor,
10899 bp: breakpoint,
10900 },
10901 edit_action,
10902 cx,
10903 );
10904 });
10905
10906 cx.notify();
10907 }
10908
10909 #[cfg(any(test, feature = "test-support"))]
10910 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10911 self.breakpoint_store.clone()
10912 }
10913
10914 pub fn prepare_restore_change(
10915 &self,
10916 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10917 hunk: &MultiBufferDiffHunk,
10918 cx: &mut App,
10919 ) -> Option<()> {
10920 if hunk.is_created_file() {
10921 return None;
10922 }
10923 let buffer = self.buffer.read(cx);
10924 let diff = buffer.diff_for(hunk.buffer_id)?;
10925 let buffer = buffer.buffer(hunk.buffer_id)?;
10926 let buffer = buffer.read(cx);
10927 let original_text = diff
10928 .read(cx)
10929 .base_text()
10930 .as_rope()
10931 .slice(hunk.diff_base_byte_range.clone());
10932 let buffer_snapshot = buffer.snapshot();
10933 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10934 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10935 probe
10936 .0
10937 .start
10938 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10939 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10940 }) {
10941 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10942 Some(())
10943 } else {
10944 None
10945 }
10946 }
10947
10948 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10949 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10950 }
10951
10952 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10953 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10954 }
10955
10956 fn manipulate_lines<M>(
10957 &mut self,
10958 window: &mut Window,
10959 cx: &mut Context<Self>,
10960 mut manipulate: M,
10961 ) where
10962 M: FnMut(&str) -> LineManipulationResult,
10963 {
10964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10965
10966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10967 let buffer = self.buffer.read(cx).snapshot(cx);
10968
10969 let mut edits = Vec::new();
10970
10971 let selections = self.selections.all::<Point>(cx);
10972 let mut selections = selections.iter().peekable();
10973 let mut contiguous_row_selections = Vec::new();
10974 let mut new_selections = Vec::new();
10975 let mut added_lines = 0;
10976 let mut removed_lines = 0;
10977
10978 while let Some(selection) = selections.next() {
10979 let (start_row, end_row) = consume_contiguous_rows(
10980 &mut contiguous_row_selections,
10981 selection,
10982 &display_map,
10983 &mut selections,
10984 );
10985
10986 let start_point = Point::new(start_row.0, 0);
10987 let end_point = Point::new(
10988 end_row.previous_row().0,
10989 buffer.line_len(end_row.previous_row()),
10990 );
10991 let text = buffer
10992 .text_for_range(start_point..end_point)
10993 .collect::<String>();
10994
10995 let LineManipulationResult {
10996 new_text,
10997 line_count_before,
10998 line_count_after,
10999 } = manipulate(&text);
11000
11001 edits.push((start_point..end_point, new_text));
11002
11003 // Selections must change based on added and removed line count
11004 let start_row =
11005 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11006 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11007 new_selections.push(Selection {
11008 id: selection.id,
11009 start: start_row,
11010 end: end_row,
11011 goal: SelectionGoal::None,
11012 reversed: selection.reversed,
11013 });
11014
11015 if line_count_after > line_count_before {
11016 added_lines += line_count_after - line_count_before;
11017 } else if line_count_before > line_count_after {
11018 removed_lines += line_count_before - line_count_after;
11019 }
11020 }
11021
11022 self.transact(window, cx, |this, window, cx| {
11023 let buffer = this.buffer.update(cx, |buffer, cx| {
11024 buffer.edit(edits, None, cx);
11025 buffer.snapshot(cx)
11026 });
11027
11028 // Recalculate offsets on newly edited buffer
11029 let new_selections = new_selections
11030 .iter()
11031 .map(|s| {
11032 let start_point = Point::new(s.start.0, 0);
11033 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11034 Selection {
11035 id: s.id,
11036 start: buffer.point_to_offset(start_point),
11037 end: buffer.point_to_offset(end_point),
11038 goal: s.goal,
11039 reversed: s.reversed,
11040 }
11041 })
11042 .collect();
11043
11044 this.change_selections(Default::default(), window, cx, |s| {
11045 s.select(new_selections);
11046 });
11047
11048 this.request_autoscroll(Autoscroll::fit(), cx);
11049 });
11050 }
11051
11052 fn manipulate_immutable_lines<Fn>(
11053 &mut self,
11054 window: &mut Window,
11055 cx: &mut Context<Self>,
11056 mut callback: Fn,
11057 ) where
11058 Fn: FnMut(&mut Vec<&str>),
11059 {
11060 self.manipulate_lines(window, cx, |text| {
11061 let mut lines: Vec<&str> = text.split('\n').collect();
11062 let line_count_before = lines.len();
11063
11064 callback(&mut lines);
11065
11066 LineManipulationResult {
11067 new_text: lines.join("\n"),
11068 line_count_before,
11069 line_count_after: lines.len(),
11070 }
11071 });
11072 }
11073
11074 fn manipulate_mutable_lines<Fn>(
11075 &mut self,
11076 window: &mut Window,
11077 cx: &mut Context<Self>,
11078 mut callback: Fn,
11079 ) where
11080 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11081 {
11082 self.manipulate_lines(window, cx, |text| {
11083 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11084 let line_count_before = lines.len();
11085
11086 callback(&mut lines);
11087
11088 LineManipulationResult {
11089 new_text: lines.join("\n"),
11090 line_count_before,
11091 line_count_after: lines.len(),
11092 }
11093 });
11094 }
11095
11096 pub fn convert_indentation_to_spaces(
11097 &mut self,
11098 _: &ConvertIndentationToSpaces,
11099 window: &mut Window,
11100 cx: &mut Context<Self>,
11101 ) {
11102 let settings = self.buffer.read(cx).language_settings(cx);
11103 let tab_size = settings.tab_size.get() as usize;
11104
11105 self.manipulate_mutable_lines(window, cx, |lines| {
11106 // Allocates a reasonably sized scratch buffer once for the whole loop
11107 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11108 // Avoids recomputing spaces that could be inserted many times
11109 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11110 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11111 .collect();
11112
11113 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11114 let mut chars = line.as_ref().chars();
11115 let mut col = 0;
11116 let mut changed = false;
11117
11118 for ch in chars.by_ref() {
11119 match ch {
11120 ' ' => {
11121 reindented_line.push(' ');
11122 col += 1;
11123 }
11124 '\t' => {
11125 // \t are converted to spaces depending on the current column
11126 let spaces_len = tab_size - (col % tab_size);
11127 reindented_line.extend(&space_cache[spaces_len - 1]);
11128 col += spaces_len;
11129 changed = true;
11130 }
11131 _ => {
11132 // If we dont append before break, the character is consumed
11133 reindented_line.push(ch);
11134 break;
11135 }
11136 }
11137 }
11138
11139 if !changed {
11140 reindented_line.clear();
11141 continue;
11142 }
11143 // Append the rest of the line and replace old reference with new one
11144 reindented_line.extend(chars);
11145 *line = Cow::Owned(reindented_line.clone());
11146 reindented_line.clear();
11147 }
11148 });
11149 }
11150
11151 pub fn convert_indentation_to_tabs(
11152 &mut self,
11153 _: &ConvertIndentationToTabs,
11154 window: &mut Window,
11155 cx: &mut Context<Self>,
11156 ) {
11157 let settings = self.buffer.read(cx).language_settings(cx);
11158 let tab_size = settings.tab_size.get() as usize;
11159
11160 self.manipulate_mutable_lines(window, cx, |lines| {
11161 // Allocates a reasonably sized buffer once for the whole loop
11162 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11163 // Avoids recomputing spaces that could be inserted many times
11164 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11165 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11166 .collect();
11167
11168 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11169 let mut chars = line.chars();
11170 let mut spaces_count = 0;
11171 let mut first_non_indent_char = None;
11172 let mut changed = false;
11173
11174 for ch in chars.by_ref() {
11175 match ch {
11176 ' ' => {
11177 // Keep track of spaces. Append \t when we reach tab_size
11178 spaces_count += 1;
11179 changed = true;
11180 if spaces_count == tab_size {
11181 reindented_line.push('\t');
11182 spaces_count = 0;
11183 }
11184 }
11185 '\t' => {
11186 reindented_line.push('\t');
11187 spaces_count = 0;
11188 }
11189 _ => {
11190 // Dont append it yet, we might have remaining spaces
11191 first_non_indent_char = Some(ch);
11192 break;
11193 }
11194 }
11195 }
11196
11197 if !changed {
11198 reindented_line.clear();
11199 continue;
11200 }
11201 // Remaining spaces that didn't make a full tab stop
11202 if spaces_count > 0 {
11203 reindented_line.extend(&space_cache[spaces_count - 1]);
11204 }
11205 // If we consume an extra character that was not indentation, add it back
11206 if let Some(extra_char) = first_non_indent_char {
11207 reindented_line.push(extra_char);
11208 }
11209 // Append the rest of the line and replace old reference with new one
11210 reindented_line.extend(chars);
11211 *line = Cow::Owned(reindented_line.clone());
11212 reindented_line.clear();
11213 }
11214 });
11215 }
11216
11217 pub fn convert_to_upper_case(
11218 &mut self,
11219 _: &ConvertToUpperCase,
11220 window: &mut Window,
11221 cx: &mut Context<Self>,
11222 ) {
11223 self.manipulate_text(window, cx, |text| text.to_uppercase())
11224 }
11225
11226 pub fn convert_to_lower_case(
11227 &mut self,
11228 _: &ConvertToLowerCase,
11229 window: &mut Window,
11230 cx: &mut Context<Self>,
11231 ) {
11232 self.manipulate_text(window, cx, |text| text.to_lowercase())
11233 }
11234
11235 pub fn convert_to_title_case(
11236 &mut self,
11237 _: &ConvertToTitleCase,
11238 window: &mut Window,
11239 cx: &mut Context<Self>,
11240 ) {
11241 self.manipulate_text(window, cx, |text| {
11242 text.split('\n')
11243 .map(|line| line.to_case(Case::Title))
11244 .join("\n")
11245 })
11246 }
11247
11248 pub fn convert_to_snake_case(
11249 &mut self,
11250 _: &ConvertToSnakeCase,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11255 }
11256
11257 pub fn convert_to_kebab_case(
11258 &mut self,
11259 _: &ConvertToKebabCase,
11260 window: &mut Window,
11261 cx: &mut Context<Self>,
11262 ) {
11263 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11264 }
11265
11266 pub fn convert_to_upper_camel_case(
11267 &mut self,
11268 _: &ConvertToUpperCamelCase,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 ) {
11272 self.manipulate_text(window, cx, |text| {
11273 text.split('\n')
11274 .map(|line| line.to_case(Case::UpperCamel))
11275 .join("\n")
11276 })
11277 }
11278
11279 pub fn convert_to_lower_camel_case(
11280 &mut self,
11281 _: &ConvertToLowerCamelCase,
11282 window: &mut Window,
11283 cx: &mut Context<Self>,
11284 ) {
11285 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11286 }
11287
11288 pub fn convert_to_opposite_case(
11289 &mut self,
11290 _: &ConvertToOppositeCase,
11291 window: &mut Window,
11292 cx: &mut Context<Self>,
11293 ) {
11294 self.manipulate_text(window, cx, |text| {
11295 text.chars()
11296 .fold(String::with_capacity(text.len()), |mut t, c| {
11297 if c.is_uppercase() {
11298 t.extend(c.to_lowercase());
11299 } else {
11300 t.extend(c.to_uppercase());
11301 }
11302 t
11303 })
11304 })
11305 }
11306
11307 pub fn convert_to_sentence_case(
11308 &mut self,
11309 _: &ConvertToSentenceCase,
11310 window: &mut Window,
11311 cx: &mut Context<Self>,
11312 ) {
11313 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11314 }
11315
11316 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11317 self.manipulate_text(window, cx, |text| {
11318 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11319 if has_upper_case_characters {
11320 text.to_lowercase()
11321 } else {
11322 text.to_uppercase()
11323 }
11324 })
11325 }
11326
11327 pub fn convert_to_rot13(
11328 &mut self,
11329 _: &ConvertToRot13,
11330 window: &mut Window,
11331 cx: &mut Context<Self>,
11332 ) {
11333 self.manipulate_text(window, cx, |text| {
11334 text.chars()
11335 .map(|c| match c {
11336 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11337 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11338 _ => c,
11339 })
11340 .collect()
11341 })
11342 }
11343
11344 pub fn convert_to_rot47(
11345 &mut self,
11346 _: &ConvertToRot47,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 self.manipulate_text(window, cx, |text| {
11351 text.chars()
11352 .map(|c| {
11353 let code_point = c as u32;
11354 if code_point >= 33 && code_point <= 126 {
11355 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11356 }
11357 c
11358 })
11359 .collect()
11360 })
11361 }
11362
11363 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11364 where
11365 Fn: FnMut(&str) -> String,
11366 {
11367 let buffer = self.buffer.read(cx).snapshot(cx);
11368
11369 let mut new_selections = Vec::new();
11370 let mut edits = Vec::new();
11371 let mut selection_adjustment = 0i32;
11372
11373 for selection in self.selections.all::<usize>(cx) {
11374 let selection_is_empty = selection.is_empty();
11375
11376 let (start, end) = if selection_is_empty {
11377 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11378 (word_range.start, word_range.end)
11379 } else {
11380 (selection.start, selection.end)
11381 };
11382
11383 let text = buffer.text_for_range(start..end).collect::<String>();
11384 let old_length = text.len() as i32;
11385 let text = callback(&text);
11386
11387 new_selections.push(Selection {
11388 start: (start as i32 - selection_adjustment) as usize,
11389 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11390 goal: SelectionGoal::None,
11391 ..selection
11392 });
11393
11394 selection_adjustment += old_length - text.len() as i32;
11395
11396 edits.push((start..end, text));
11397 }
11398
11399 self.transact(window, cx, |this, window, cx| {
11400 this.buffer.update(cx, |buffer, cx| {
11401 buffer.edit(edits, None, cx);
11402 });
11403
11404 this.change_selections(Default::default(), window, cx, |s| {
11405 s.select(new_selections);
11406 });
11407
11408 this.request_autoscroll(Autoscroll::fit(), cx);
11409 });
11410 }
11411
11412 pub fn move_selection_on_drop(
11413 &mut self,
11414 selection: &Selection<Anchor>,
11415 target: DisplayPoint,
11416 is_cut: bool,
11417 window: &mut Window,
11418 cx: &mut Context<Self>,
11419 ) {
11420 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11421 let buffer = &display_map.buffer_snapshot;
11422 let mut edits = Vec::new();
11423 let insert_point = display_map
11424 .clip_point(target, Bias::Left)
11425 .to_point(&display_map);
11426 let text = buffer
11427 .text_for_range(selection.start..selection.end)
11428 .collect::<String>();
11429 if is_cut {
11430 edits.push(((selection.start..selection.end), String::new()));
11431 }
11432 let insert_anchor = buffer.anchor_before(insert_point);
11433 edits.push(((insert_anchor..insert_anchor), text));
11434 let last_edit_start = insert_anchor.bias_left(buffer);
11435 let last_edit_end = insert_anchor.bias_right(buffer);
11436 self.transact(window, cx, |this, window, cx| {
11437 this.buffer.update(cx, |buffer, cx| {
11438 buffer.edit(edits, None, cx);
11439 });
11440 this.change_selections(Default::default(), window, cx, |s| {
11441 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11442 });
11443 });
11444 }
11445
11446 pub fn clear_selection_drag_state(&mut self) {
11447 self.selection_drag_state = SelectionDragState::None;
11448 }
11449
11450 pub fn duplicate(
11451 &mut self,
11452 upwards: bool,
11453 whole_lines: bool,
11454 window: &mut Window,
11455 cx: &mut Context<Self>,
11456 ) {
11457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11458
11459 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11460 let buffer = &display_map.buffer_snapshot;
11461 let selections = self.selections.all::<Point>(cx);
11462
11463 let mut edits = Vec::new();
11464 let mut selections_iter = selections.iter().peekable();
11465 while let Some(selection) = selections_iter.next() {
11466 let mut rows = selection.spanned_rows(false, &display_map);
11467 // duplicate line-wise
11468 if whole_lines || selection.start == selection.end {
11469 // Avoid duplicating the same lines twice.
11470 while let Some(next_selection) = selections_iter.peek() {
11471 let next_rows = next_selection.spanned_rows(false, &display_map);
11472 if next_rows.start < rows.end {
11473 rows.end = next_rows.end;
11474 selections_iter.next().unwrap();
11475 } else {
11476 break;
11477 }
11478 }
11479
11480 // Copy the text from the selected row region and splice it either at the start
11481 // or end of the region.
11482 let start = Point::new(rows.start.0, 0);
11483 let end = Point::new(
11484 rows.end.previous_row().0,
11485 buffer.line_len(rows.end.previous_row()),
11486 );
11487 let text = buffer
11488 .text_for_range(start..end)
11489 .chain(Some("\n"))
11490 .collect::<String>();
11491 let insert_location = if upwards {
11492 Point::new(rows.end.0, 0)
11493 } else {
11494 start
11495 };
11496 edits.push((insert_location..insert_location, text));
11497 } else {
11498 // duplicate character-wise
11499 let start = selection.start;
11500 let end = selection.end;
11501 let text = buffer.text_for_range(start..end).collect::<String>();
11502 edits.push((selection.end..selection.end, text));
11503 }
11504 }
11505
11506 self.transact(window, cx, |this, _, cx| {
11507 this.buffer.update(cx, |buffer, cx| {
11508 buffer.edit(edits, None, cx);
11509 });
11510
11511 this.request_autoscroll(Autoscroll::fit(), cx);
11512 });
11513 }
11514
11515 pub fn duplicate_line_up(
11516 &mut self,
11517 _: &DuplicateLineUp,
11518 window: &mut Window,
11519 cx: &mut Context<Self>,
11520 ) {
11521 self.duplicate(true, true, window, cx);
11522 }
11523
11524 pub fn duplicate_line_down(
11525 &mut self,
11526 _: &DuplicateLineDown,
11527 window: &mut Window,
11528 cx: &mut Context<Self>,
11529 ) {
11530 self.duplicate(false, true, window, cx);
11531 }
11532
11533 pub fn duplicate_selection(
11534 &mut self,
11535 _: &DuplicateSelection,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 self.duplicate(false, false, window, cx);
11540 }
11541
11542 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11544 if self.mode.is_single_line() {
11545 cx.propagate();
11546 return;
11547 }
11548
11549 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11550 let buffer = self.buffer.read(cx).snapshot(cx);
11551
11552 let mut edits = Vec::new();
11553 let mut unfold_ranges = Vec::new();
11554 let mut refold_creases = Vec::new();
11555
11556 let selections = self.selections.all::<Point>(cx);
11557 let mut selections = selections.iter().peekable();
11558 let mut contiguous_row_selections = Vec::new();
11559 let mut new_selections = Vec::new();
11560
11561 while let Some(selection) = selections.next() {
11562 // Find all the selections that span a contiguous row range
11563 let (start_row, end_row) = consume_contiguous_rows(
11564 &mut contiguous_row_selections,
11565 selection,
11566 &display_map,
11567 &mut selections,
11568 );
11569
11570 // Move the text spanned by the row range to be before the line preceding the row range
11571 if start_row.0 > 0 {
11572 let range_to_move = Point::new(
11573 start_row.previous_row().0,
11574 buffer.line_len(start_row.previous_row()),
11575 )
11576 ..Point::new(
11577 end_row.previous_row().0,
11578 buffer.line_len(end_row.previous_row()),
11579 );
11580 let insertion_point = display_map
11581 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11582 .0;
11583
11584 // Don't move lines across excerpts
11585 if buffer
11586 .excerpt_containing(insertion_point..range_to_move.end)
11587 .is_some()
11588 {
11589 let text = buffer
11590 .text_for_range(range_to_move.clone())
11591 .flat_map(|s| s.chars())
11592 .skip(1)
11593 .chain(['\n'])
11594 .collect::<String>();
11595
11596 edits.push((
11597 buffer.anchor_after(range_to_move.start)
11598 ..buffer.anchor_before(range_to_move.end),
11599 String::new(),
11600 ));
11601 let insertion_anchor = buffer.anchor_after(insertion_point);
11602 edits.push((insertion_anchor..insertion_anchor, text));
11603
11604 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11605
11606 // Move selections up
11607 new_selections.extend(contiguous_row_selections.drain(..).map(
11608 |mut selection| {
11609 selection.start.row -= row_delta;
11610 selection.end.row -= row_delta;
11611 selection
11612 },
11613 ));
11614
11615 // Move folds up
11616 unfold_ranges.push(range_to_move.clone());
11617 for fold in display_map.folds_in_range(
11618 buffer.anchor_before(range_to_move.start)
11619 ..buffer.anchor_after(range_to_move.end),
11620 ) {
11621 let mut start = fold.range.start.to_point(&buffer);
11622 let mut end = fold.range.end.to_point(&buffer);
11623 start.row -= row_delta;
11624 end.row -= row_delta;
11625 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11626 }
11627 }
11628 }
11629
11630 // If we didn't move line(s), preserve the existing selections
11631 new_selections.append(&mut contiguous_row_selections);
11632 }
11633
11634 self.transact(window, cx, |this, window, cx| {
11635 this.unfold_ranges(&unfold_ranges, true, true, cx);
11636 this.buffer.update(cx, |buffer, cx| {
11637 for (range, text) in edits {
11638 buffer.edit([(range, text)], None, cx);
11639 }
11640 });
11641 this.fold_creases(refold_creases, true, window, cx);
11642 this.change_selections(Default::default(), window, cx, |s| {
11643 s.select(new_selections);
11644 })
11645 });
11646 }
11647
11648 pub fn move_line_down(
11649 &mut self,
11650 _: &MoveLineDown,
11651 window: &mut Window,
11652 cx: &mut Context<Self>,
11653 ) {
11654 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11655 if self.mode.is_single_line() {
11656 cx.propagate();
11657 return;
11658 }
11659
11660 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11661 let buffer = self.buffer.read(cx).snapshot(cx);
11662
11663 let mut edits = Vec::new();
11664 let mut unfold_ranges = Vec::new();
11665 let mut refold_creases = Vec::new();
11666
11667 let selections = self.selections.all::<Point>(cx);
11668 let mut selections = selections.iter().peekable();
11669 let mut contiguous_row_selections = Vec::new();
11670 let mut new_selections = Vec::new();
11671
11672 while let Some(selection) = selections.next() {
11673 // Find all the selections that span a contiguous row range
11674 let (start_row, end_row) = consume_contiguous_rows(
11675 &mut contiguous_row_selections,
11676 selection,
11677 &display_map,
11678 &mut selections,
11679 );
11680
11681 // Move the text spanned by the row range to be after the last line of the row range
11682 if end_row.0 <= buffer.max_point().row {
11683 let range_to_move =
11684 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11685 let insertion_point = display_map
11686 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11687 .0;
11688
11689 // Don't move lines across excerpt boundaries
11690 if buffer
11691 .excerpt_containing(range_to_move.start..insertion_point)
11692 .is_some()
11693 {
11694 let mut text = String::from("\n");
11695 text.extend(buffer.text_for_range(range_to_move.clone()));
11696 text.pop(); // Drop trailing newline
11697 edits.push((
11698 buffer.anchor_after(range_to_move.start)
11699 ..buffer.anchor_before(range_to_move.end),
11700 String::new(),
11701 ));
11702 let insertion_anchor = buffer.anchor_after(insertion_point);
11703 edits.push((insertion_anchor..insertion_anchor, text));
11704
11705 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11706
11707 // Move selections down
11708 new_selections.extend(contiguous_row_selections.drain(..).map(
11709 |mut selection| {
11710 selection.start.row += row_delta;
11711 selection.end.row += row_delta;
11712 selection
11713 },
11714 ));
11715
11716 // Move folds down
11717 unfold_ranges.push(range_to_move.clone());
11718 for fold in display_map.folds_in_range(
11719 buffer.anchor_before(range_to_move.start)
11720 ..buffer.anchor_after(range_to_move.end),
11721 ) {
11722 let mut start = fold.range.start.to_point(&buffer);
11723 let mut end = fold.range.end.to_point(&buffer);
11724 start.row += row_delta;
11725 end.row += row_delta;
11726 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11727 }
11728 }
11729 }
11730
11731 // If we didn't move line(s), preserve the existing selections
11732 new_selections.append(&mut contiguous_row_selections);
11733 }
11734
11735 self.transact(window, cx, |this, window, cx| {
11736 this.unfold_ranges(&unfold_ranges, true, true, cx);
11737 this.buffer.update(cx, |buffer, cx| {
11738 for (range, text) in edits {
11739 buffer.edit([(range, text)], None, cx);
11740 }
11741 });
11742 this.fold_creases(refold_creases, true, window, cx);
11743 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11744 });
11745 }
11746
11747 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11748 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11749 let text_layout_details = &self.text_layout_details(window);
11750 self.transact(window, cx, |this, window, cx| {
11751 let edits = this.change_selections(Default::default(), window, cx, |s| {
11752 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11753 s.move_with(|display_map, selection| {
11754 if !selection.is_empty() {
11755 return;
11756 }
11757
11758 let mut head = selection.head();
11759 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11760 if head.column() == display_map.line_len(head.row()) {
11761 transpose_offset = display_map
11762 .buffer_snapshot
11763 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11764 }
11765
11766 if transpose_offset == 0 {
11767 return;
11768 }
11769
11770 *head.column_mut() += 1;
11771 head = display_map.clip_point(head, Bias::Right);
11772 let goal = SelectionGoal::HorizontalPosition(
11773 display_map
11774 .x_for_display_point(head, text_layout_details)
11775 .into(),
11776 );
11777 selection.collapse_to(head, goal);
11778
11779 let transpose_start = display_map
11780 .buffer_snapshot
11781 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11782 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11783 let transpose_end = display_map
11784 .buffer_snapshot
11785 .clip_offset(transpose_offset + 1, Bias::Right);
11786 if let Some(ch) =
11787 display_map.buffer_snapshot.chars_at(transpose_start).next()
11788 {
11789 edits.push((transpose_start..transpose_offset, String::new()));
11790 edits.push((transpose_end..transpose_end, ch.to_string()));
11791 }
11792 }
11793 });
11794 edits
11795 });
11796 this.buffer
11797 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11798 let selections = this.selections.all::<usize>(cx);
11799 this.change_selections(Default::default(), window, cx, |s| {
11800 s.select(selections);
11801 });
11802 });
11803 }
11804
11805 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11806 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11807 if self.mode.is_single_line() {
11808 cx.propagate();
11809 return;
11810 }
11811
11812 self.rewrap_impl(RewrapOptions::default(), cx)
11813 }
11814
11815 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11816 let buffer = self.buffer.read(cx).snapshot(cx);
11817 let selections = self.selections.all::<Point>(cx);
11818
11819 #[derive(Clone, Debug, PartialEq)]
11820 enum CommentFormat {
11821 /// single line comment, with prefix for line
11822 Line(String),
11823 /// single line within a block comment, with prefix for line
11824 BlockLine(String),
11825 /// a single line of a block comment that includes the initial delimiter
11826 BlockCommentWithStart(BlockCommentConfig),
11827 /// a single line of a block comment that includes the ending delimiter
11828 BlockCommentWithEnd(BlockCommentConfig),
11829 }
11830
11831 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11832 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11833 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11834 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11835 .peekable();
11836
11837 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11838 row
11839 } else {
11840 return Vec::new();
11841 };
11842
11843 let language_settings = buffer.language_settings_at(selection.head(), cx);
11844 let language_scope = buffer.language_scope_at(selection.head());
11845
11846 let indent_and_prefix_for_row =
11847 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11848 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11849 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11850 &language_scope
11851 {
11852 let indent_end = Point::new(row, indent.len);
11853 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11854 let line_text_after_indent = buffer
11855 .text_for_range(indent_end..line_end)
11856 .collect::<String>();
11857
11858 let is_within_comment_override = buffer
11859 .language_scope_at(indent_end)
11860 .is_some_and(|scope| scope.override_name() == Some("comment"));
11861 let comment_delimiters = if is_within_comment_override {
11862 // we are within a comment syntax node, but we don't
11863 // yet know what kind of comment: block, doc or line
11864 match (
11865 language_scope.documentation_comment(),
11866 language_scope.block_comment(),
11867 ) {
11868 (Some(config), _) | (_, Some(config))
11869 if buffer.contains_str_at(indent_end, &config.start) =>
11870 {
11871 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11872 }
11873 (Some(config), _) | (_, Some(config))
11874 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11875 {
11876 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11877 }
11878 (Some(config), _) | (_, Some(config))
11879 if buffer.contains_str_at(indent_end, &config.prefix) =>
11880 {
11881 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11882 }
11883 (_, _) => language_scope
11884 .line_comment_prefixes()
11885 .iter()
11886 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11887 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11888 }
11889 } else {
11890 // we not in an overridden comment node, but we may
11891 // be within a non-overridden line comment node
11892 language_scope
11893 .line_comment_prefixes()
11894 .iter()
11895 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11896 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11897 };
11898
11899 let rewrap_prefix = language_scope
11900 .rewrap_prefixes()
11901 .iter()
11902 .find_map(|prefix_regex| {
11903 prefix_regex.find(&line_text_after_indent).map(|mat| {
11904 if mat.start() == 0 {
11905 Some(mat.as_str().to_string())
11906 } else {
11907 None
11908 }
11909 })
11910 })
11911 .flatten();
11912 (comment_delimiters, rewrap_prefix)
11913 } else {
11914 (None, None)
11915 };
11916 (indent, comment_prefix, rewrap_prefix)
11917 };
11918
11919 let mut ranges = Vec::new();
11920 let from_empty_selection = selection.is_empty();
11921
11922 let mut current_range_start = first_row;
11923 let mut prev_row = first_row;
11924 let (
11925 mut current_range_indent,
11926 mut current_range_comment_delimiters,
11927 mut current_range_rewrap_prefix,
11928 ) = indent_and_prefix_for_row(first_row);
11929
11930 for row in non_blank_rows_iter.skip(1) {
11931 let has_paragraph_break = row > prev_row + 1;
11932
11933 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
11934 indent_and_prefix_for_row(row);
11935
11936 let has_indent_change = row_indent != current_range_indent;
11937 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
11938
11939 let has_boundary_change = has_comment_change
11940 || row_rewrap_prefix.is_some()
11941 || (has_indent_change && current_range_comment_delimiters.is_some());
11942
11943 if has_paragraph_break || has_boundary_change {
11944 ranges.push((
11945 language_settings.clone(),
11946 Point::new(current_range_start, 0)
11947 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11948 current_range_indent,
11949 current_range_comment_delimiters.clone(),
11950 current_range_rewrap_prefix.clone(),
11951 from_empty_selection,
11952 ));
11953 current_range_start = row;
11954 current_range_indent = row_indent;
11955 current_range_comment_delimiters = row_comment_delimiters;
11956 current_range_rewrap_prefix = row_rewrap_prefix;
11957 }
11958 prev_row = row;
11959 }
11960
11961 ranges.push((
11962 language_settings.clone(),
11963 Point::new(current_range_start, 0)
11964 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11965 current_range_indent,
11966 current_range_comment_delimiters,
11967 current_range_rewrap_prefix,
11968 from_empty_selection,
11969 ));
11970
11971 ranges
11972 });
11973
11974 let mut edits = Vec::new();
11975 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11976
11977 for (
11978 language_settings,
11979 wrap_range,
11980 mut indent_size,
11981 comment_prefix,
11982 rewrap_prefix,
11983 from_empty_selection,
11984 ) in wrap_ranges
11985 {
11986 let mut start_row = wrap_range.start.row;
11987 let mut end_row = wrap_range.end.row;
11988
11989 // Skip selections that overlap with a range that has already been rewrapped.
11990 let selection_range = start_row..end_row;
11991 if rewrapped_row_ranges
11992 .iter()
11993 .any(|range| range.overlaps(&selection_range))
11994 {
11995 continue;
11996 }
11997
11998 let tab_size = language_settings.tab_size;
11999
12000 let (line_prefix, inside_comment) = match &comment_prefix {
12001 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12002 (Some(prefix.as_str()), true)
12003 }
12004 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12005 (Some(prefix.as_ref()), true)
12006 }
12007 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12008 start: _,
12009 end: _,
12010 prefix,
12011 tab_size,
12012 })) => {
12013 indent_size.len += tab_size;
12014 (Some(prefix.as_ref()), true)
12015 }
12016 None => (None, false),
12017 };
12018 let indent_prefix = indent_size.chars().collect::<String>();
12019 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12020
12021 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12022 RewrapBehavior::InComments => inside_comment,
12023 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12024 RewrapBehavior::Anywhere => true,
12025 };
12026
12027 let should_rewrap = options.override_language_settings
12028 || allow_rewrap_based_on_language
12029 || self.hard_wrap.is_some();
12030 if !should_rewrap {
12031 continue;
12032 }
12033
12034 if from_empty_selection {
12035 'expand_upwards: while start_row > 0 {
12036 let prev_row = start_row - 1;
12037 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12038 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12039 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12040 {
12041 start_row = prev_row;
12042 } else {
12043 break 'expand_upwards;
12044 }
12045 }
12046
12047 'expand_downwards: while end_row < buffer.max_point().row {
12048 let next_row = end_row + 1;
12049 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12050 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12051 && !buffer.is_line_blank(MultiBufferRow(next_row))
12052 {
12053 end_row = next_row;
12054 } else {
12055 break 'expand_downwards;
12056 }
12057 }
12058 }
12059
12060 let start = Point::new(start_row, 0);
12061 let start_offset = start.to_offset(&buffer);
12062 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12063 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12064 let mut first_line_delimiter = None;
12065 let mut last_line_delimiter = None;
12066 let Some(lines_without_prefixes) = selection_text
12067 .lines()
12068 .enumerate()
12069 .map(|(ix, line)| {
12070 let line_trimmed = line.trim_start();
12071 if rewrap_prefix.is_some() && ix > 0 {
12072 Ok(line_trimmed)
12073 } else if let Some(
12074 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12075 start,
12076 prefix,
12077 end,
12078 tab_size,
12079 })
12080 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12081 start,
12082 prefix,
12083 end,
12084 tab_size,
12085 }),
12086 ) = &comment_prefix
12087 {
12088 let line_trimmed = line_trimmed
12089 .strip_prefix(start.as_ref())
12090 .map(|s| {
12091 let mut indent_size = indent_size;
12092 indent_size.len -= tab_size;
12093 let indent_prefix: String = indent_size.chars().collect();
12094 first_line_delimiter = Some((indent_prefix, start));
12095 s.trim_start()
12096 })
12097 .unwrap_or(line_trimmed);
12098 let line_trimmed = line_trimmed
12099 .strip_suffix(end.as_ref())
12100 .map(|s| {
12101 last_line_delimiter = Some(end);
12102 s.trim_end()
12103 })
12104 .unwrap_or(line_trimmed);
12105 let line_trimmed = line_trimmed
12106 .strip_prefix(prefix.as_ref())
12107 .unwrap_or(line_trimmed);
12108 Ok(line_trimmed)
12109 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12110 line_trimmed.strip_prefix(prefix).with_context(|| {
12111 format!("line did not start with prefix {prefix:?}: {line:?}")
12112 })
12113 } else {
12114 line_trimmed
12115 .strip_prefix(&line_prefix.trim_start())
12116 .with_context(|| {
12117 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12118 })
12119 }
12120 })
12121 .collect::<Result<Vec<_>, _>>()
12122 .log_err()
12123 else {
12124 continue;
12125 };
12126
12127 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12128 buffer
12129 .language_settings_at(Point::new(start_row, 0), cx)
12130 .preferred_line_length as usize
12131 });
12132
12133 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12134 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12135 } else {
12136 line_prefix.clone()
12137 };
12138
12139 let wrapped_text = {
12140 let mut wrapped_text = wrap_with_prefix(
12141 line_prefix,
12142 subsequent_lines_prefix,
12143 lines_without_prefixes.join("\n"),
12144 wrap_column,
12145 tab_size,
12146 options.preserve_existing_whitespace,
12147 );
12148
12149 if let Some((indent, delimiter)) = first_line_delimiter {
12150 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12151 }
12152 if let Some(last_line) = last_line_delimiter {
12153 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12154 }
12155
12156 wrapped_text
12157 };
12158
12159 // TODO: should always use char-based diff while still supporting cursor behavior that
12160 // matches vim.
12161 let mut diff_options = DiffOptions::default();
12162 if options.override_language_settings {
12163 diff_options.max_word_diff_len = 0;
12164 diff_options.max_word_diff_line_count = 0;
12165 } else {
12166 diff_options.max_word_diff_len = usize::MAX;
12167 diff_options.max_word_diff_line_count = usize::MAX;
12168 }
12169
12170 for (old_range, new_text) in
12171 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12172 {
12173 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12174 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12175 edits.push((edit_start..edit_end, new_text));
12176 }
12177
12178 rewrapped_row_ranges.push(start_row..=end_row);
12179 }
12180
12181 self.buffer
12182 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12183 }
12184
12185 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12186 let mut text = String::new();
12187 let buffer = self.buffer.read(cx).snapshot(cx);
12188 let mut selections = self.selections.all::<Point>(cx);
12189 let mut clipboard_selections = Vec::with_capacity(selections.len());
12190 {
12191 let max_point = buffer.max_point();
12192 let mut is_first = true;
12193 for selection in &mut selections {
12194 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12195 if is_entire_line {
12196 selection.start = Point::new(selection.start.row, 0);
12197 if !selection.is_empty() && selection.end.column == 0 {
12198 selection.end = cmp::min(max_point, selection.end);
12199 } else {
12200 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12201 }
12202 selection.goal = SelectionGoal::None;
12203 }
12204 if is_first {
12205 is_first = false;
12206 } else {
12207 text += "\n";
12208 }
12209 let mut len = 0;
12210 for chunk in buffer.text_for_range(selection.start..selection.end) {
12211 text.push_str(chunk);
12212 len += chunk.len();
12213 }
12214 clipboard_selections.push(ClipboardSelection {
12215 len,
12216 is_entire_line,
12217 first_line_indent: buffer
12218 .indent_size_for_line(MultiBufferRow(selection.start.row))
12219 .len,
12220 });
12221 }
12222 }
12223
12224 self.transact(window, cx, |this, window, cx| {
12225 this.change_selections(Default::default(), window, cx, |s| {
12226 s.select(selections);
12227 });
12228 this.insert("", window, cx);
12229 });
12230 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12231 }
12232
12233 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12234 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12235 let item = self.cut_common(window, cx);
12236 cx.write_to_clipboard(item);
12237 }
12238
12239 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12240 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12241 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12242 s.move_with(|snapshot, sel| {
12243 if sel.is_empty() {
12244 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12245 }
12246 });
12247 });
12248 let item = self.cut_common(window, cx);
12249 cx.set_global(KillRing(item))
12250 }
12251
12252 pub fn kill_ring_yank(
12253 &mut self,
12254 _: &KillRingYank,
12255 window: &mut Window,
12256 cx: &mut Context<Self>,
12257 ) {
12258 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12259 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12260 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12261 (kill_ring.text().to_string(), kill_ring.metadata_json())
12262 } else {
12263 return;
12264 }
12265 } else {
12266 return;
12267 };
12268 self.do_paste(&text, metadata, false, window, cx);
12269 }
12270
12271 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12272 self.do_copy(true, cx);
12273 }
12274
12275 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12276 self.do_copy(false, cx);
12277 }
12278
12279 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12280 let selections = self.selections.all::<Point>(cx);
12281 let buffer = self.buffer.read(cx).read(cx);
12282 let mut text = String::new();
12283
12284 let mut clipboard_selections = Vec::with_capacity(selections.len());
12285 {
12286 let max_point = buffer.max_point();
12287 let mut is_first = true;
12288 for selection in &selections {
12289 let mut start = selection.start;
12290 let mut end = selection.end;
12291 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12292 if is_entire_line {
12293 start = Point::new(start.row, 0);
12294 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12295 }
12296
12297 let mut trimmed_selections = Vec::new();
12298 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12299 let row = MultiBufferRow(start.row);
12300 let first_indent = buffer.indent_size_for_line(row);
12301 if first_indent.len == 0 || start.column > first_indent.len {
12302 trimmed_selections.push(start..end);
12303 } else {
12304 trimmed_selections.push(
12305 Point::new(row.0, first_indent.len)
12306 ..Point::new(row.0, buffer.line_len(row)),
12307 );
12308 for row in start.row + 1..=end.row {
12309 let mut line_len = buffer.line_len(MultiBufferRow(row));
12310 if row == end.row {
12311 line_len = end.column;
12312 }
12313 if line_len == 0 {
12314 trimmed_selections
12315 .push(Point::new(row, 0)..Point::new(row, line_len));
12316 continue;
12317 }
12318 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12319 if row_indent_size.len >= first_indent.len {
12320 trimmed_selections.push(
12321 Point::new(row, first_indent.len)..Point::new(row, line_len),
12322 );
12323 } else {
12324 trimmed_selections.clear();
12325 trimmed_selections.push(start..end);
12326 break;
12327 }
12328 }
12329 }
12330 } else {
12331 trimmed_selections.push(start..end);
12332 }
12333
12334 for trimmed_range in trimmed_selections {
12335 if is_first {
12336 is_first = false;
12337 } else {
12338 text += "\n";
12339 }
12340 let mut len = 0;
12341 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12342 text.push_str(chunk);
12343 len += chunk.len();
12344 }
12345 clipboard_selections.push(ClipboardSelection {
12346 len,
12347 is_entire_line,
12348 first_line_indent: buffer
12349 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12350 .len,
12351 });
12352 }
12353 }
12354 }
12355
12356 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12357 text,
12358 clipboard_selections,
12359 ));
12360 }
12361
12362 pub fn do_paste(
12363 &mut self,
12364 text: &String,
12365 clipboard_selections: Option<Vec<ClipboardSelection>>,
12366 handle_entire_lines: bool,
12367 window: &mut Window,
12368 cx: &mut Context<Self>,
12369 ) {
12370 if self.read_only(cx) {
12371 return;
12372 }
12373
12374 let clipboard_text = Cow::Borrowed(text);
12375
12376 self.transact(window, cx, |this, window, cx| {
12377 let had_active_edit_prediction = this.has_active_edit_prediction();
12378
12379 if let Some(mut clipboard_selections) = clipboard_selections {
12380 let old_selections = this.selections.all::<usize>(cx);
12381 let all_selections_were_entire_line =
12382 clipboard_selections.iter().all(|s| s.is_entire_line);
12383 let first_selection_indent_column =
12384 clipboard_selections.first().map(|s| s.first_line_indent);
12385 if clipboard_selections.len() != old_selections.len() {
12386 clipboard_selections.drain(..);
12387 }
12388 let cursor_offset = this.selections.last::<usize>(cx).head();
12389 let mut auto_indent_on_paste = true;
12390
12391 this.buffer.update(cx, |buffer, cx| {
12392 let snapshot = buffer.read(cx);
12393 auto_indent_on_paste = snapshot
12394 .language_settings_at(cursor_offset, cx)
12395 .auto_indent_on_paste;
12396
12397 let mut start_offset = 0;
12398 let mut edits = Vec::new();
12399 let mut original_indent_columns = Vec::new();
12400 for (ix, selection) in old_selections.iter().enumerate() {
12401 let to_insert;
12402 let entire_line;
12403 let original_indent_column;
12404 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12405 let end_offset = start_offset + clipboard_selection.len;
12406 to_insert = &clipboard_text[start_offset..end_offset];
12407 entire_line = clipboard_selection.is_entire_line;
12408 start_offset = end_offset + 1;
12409 original_indent_column = Some(clipboard_selection.first_line_indent);
12410 } else {
12411 to_insert = clipboard_text.as_str();
12412 entire_line = all_selections_were_entire_line;
12413 original_indent_column = first_selection_indent_column
12414 }
12415
12416 // If the corresponding selection was empty when this slice of the
12417 // clipboard text was written, then the entire line containing the
12418 // selection was copied. If this selection is also currently empty,
12419 // then paste the line before the current line of the buffer.
12420 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12421 let column = selection.start.to_point(&snapshot).column as usize;
12422 let line_start = selection.start - column;
12423 line_start..line_start
12424 } else {
12425 selection.range()
12426 };
12427
12428 edits.push((range, to_insert));
12429 original_indent_columns.push(original_indent_column);
12430 }
12431 drop(snapshot);
12432
12433 buffer.edit(
12434 edits,
12435 if auto_indent_on_paste {
12436 Some(AutoindentMode::Block {
12437 original_indent_columns,
12438 })
12439 } else {
12440 None
12441 },
12442 cx,
12443 );
12444 });
12445
12446 let selections = this.selections.all::<usize>(cx);
12447 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12448 } else {
12449 this.insert(&clipboard_text, window, cx);
12450 }
12451
12452 let trigger_in_words =
12453 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12454
12455 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12456 });
12457 }
12458
12459 pub fn diff_clipboard_with_selection(
12460 &mut self,
12461 _: &DiffClipboardWithSelection,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) {
12465 let selections = self.selections.all::<usize>(cx);
12466
12467 if selections.is_empty() {
12468 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12469 return;
12470 };
12471
12472 let clipboard_text = match cx.read_from_clipboard() {
12473 Some(item) => match item.entries().first() {
12474 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12475 _ => None,
12476 },
12477 None => None,
12478 };
12479
12480 let Some(clipboard_text) = clipboard_text else {
12481 log::warn!("Clipboard doesn't contain text.");
12482 return;
12483 };
12484
12485 window.dispatch_action(
12486 Box::new(DiffClipboardWithSelectionData {
12487 clipboard_text,
12488 editor: cx.entity(),
12489 }),
12490 cx,
12491 );
12492 }
12493
12494 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12495 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12496 if let Some(item) = cx.read_from_clipboard() {
12497 let entries = item.entries();
12498
12499 match entries.first() {
12500 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12501 // of all the pasted entries.
12502 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12503 .do_paste(
12504 clipboard_string.text(),
12505 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12506 true,
12507 window,
12508 cx,
12509 ),
12510 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12511 }
12512 }
12513 }
12514
12515 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12516 if self.read_only(cx) {
12517 return;
12518 }
12519
12520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12521
12522 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12523 if let Some((selections, _)) =
12524 self.selection_history.transaction(transaction_id).cloned()
12525 {
12526 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12527 s.select_anchors(selections.to_vec());
12528 });
12529 } else {
12530 log::error!(
12531 "No entry in selection_history found for undo. \
12532 This may correspond to a bug where undo does not update the selection. \
12533 If this is occurring, please add details to \
12534 https://github.com/zed-industries/zed/issues/22692"
12535 );
12536 }
12537 self.request_autoscroll(Autoscroll::fit(), cx);
12538 self.unmark_text(window, cx);
12539 self.refresh_edit_prediction(true, false, window, cx);
12540 cx.emit(EditorEvent::Edited { transaction_id });
12541 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12542 }
12543 }
12544
12545 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12546 if self.read_only(cx) {
12547 return;
12548 }
12549
12550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12551
12552 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12553 if let Some((_, Some(selections))) =
12554 self.selection_history.transaction(transaction_id).cloned()
12555 {
12556 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12557 s.select_anchors(selections.to_vec());
12558 });
12559 } else {
12560 log::error!(
12561 "No entry in selection_history found for redo. \
12562 This may correspond to a bug where undo does not update the selection. \
12563 If this is occurring, please add details to \
12564 https://github.com/zed-industries/zed/issues/22692"
12565 );
12566 }
12567 self.request_autoscroll(Autoscroll::fit(), cx);
12568 self.unmark_text(window, cx);
12569 self.refresh_edit_prediction(true, false, window, cx);
12570 cx.emit(EditorEvent::Edited { transaction_id });
12571 }
12572 }
12573
12574 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12575 self.buffer
12576 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12577 }
12578
12579 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12580 self.buffer
12581 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12582 }
12583
12584 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12586 self.change_selections(Default::default(), window, cx, |s| {
12587 s.move_with(|map, selection| {
12588 let cursor = if selection.is_empty() {
12589 movement::left(map, selection.start)
12590 } else {
12591 selection.start
12592 };
12593 selection.collapse_to(cursor, SelectionGoal::None);
12594 });
12595 })
12596 }
12597
12598 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12599 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12600 self.change_selections(Default::default(), window, cx, |s| {
12601 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12602 })
12603 }
12604
12605 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12607 self.change_selections(Default::default(), window, cx, |s| {
12608 s.move_with(|map, selection| {
12609 let cursor = if selection.is_empty() {
12610 movement::right(map, selection.end)
12611 } else {
12612 selection.end
12613 };
12614 selection.collapse_to(cursor, SelectionGoal::None)
12615 });
12616 })
12617 }
12618
12619 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12621 self.change_selections(Default::default(), window, cx, |s| {
12622 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12623 })
12624 }
12625
12626 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12627 if self.take_rename(true, window, cx).is_some() {
12628 return;
12629 }
12630
12631 if self.mode.is_single_line() {
12632 cx.propagate();
12633 return;
12634 }
12635
12636 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12637
12638 let text_layout_details = &self.text_layout_details(window);
12639 let selection_count = self.selections.count();
12640 let first_selection = self.selections.first_anchor();
12641
12642 self.change_selections(Default::default(), window, cx, |s| {
12643 s.move_with(|map, selection| {
12644 if !selection.is_empty() {
12645 selection.goal = SelectionGoal::None;
12646 }
12647 let (cursor, goal) = movement::up(
12648 map,
12649 selection.start,
12650 selection.goal,
12651 false,
12652 text_layout_details,
12653 );
12654 selection.collapse_to(cursor, goal);
12655 });
12656 });
12657
12658 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12659 {
12660 cx.propagate();
12661 }
12662 }
12663
12664 pub fn move_up_by_lines(
12665 &mut self,
12666 action: &MoveUpByLines,
12667 window: &mut Window,
12668 cx: &mut Context<Self>,
12669 ) {
12670 if self.take_rename(true, window, cx).is_some() {
12671 return;
12672 }
12673
12674 if self.mode.is_single_line() {
12675 cx.propagate();
12676 return;
12677 }
12678
12679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12680
12681 let text_layout_details = &self.text_layout_details(window);
12682
12683 self.change_selections(Default::default(), window, cx, |s| {
12684 s.move_with(|map, selection| {
12685 if !selection.is_empty() {
12686 selection.goal = SelectionGoal::None;
12687 }
12688 let (cursor, goal) = movement::up_by_rows(
12689 map,
12690 selection.start,
12691 action.lines,
12692 selection.goal,
12693 false,
12694 text_layout_details,
12695 );
12696 selection.collapse_to(cursor, goal);
12697 });
12698 })
12699 }
12700
12701 pub fn move_down_by_lines(
12702 &mut self,
12703 action: &MoveDownByLines,
12704 window: &mut Window,
12705 cx: &mut Context<Self>,
12706 ) {
12707 if self.take_rename(true, window, cx).is_some() {
12708 return;
12709 }
12710
12711 if self.mode.is_single_line() {
12712 cx.propagate();
12713 return;
12714 }
12715
12716 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12717
12718 let text_layout_details = &self.text_layout_details(window);
12719
12720 self.change_selections(Default::default(), window, cx, |s| {
12721 s.move_with(|map, selection| {
12722 if !selection.is_empty() {
12723 selection.goal = SelectionGoal::None;
12724 }
12725 let (cursor, goal) = movement::down_by_rows(
12726 map,
12727 selection.start,
12728 action.lines,
12729 selection.goal,
12730 false,
12731 text_layout_details,
12732 );
12733 selection.collapse_to(cursor, goal);
12734 });
12735 })
12736 }
12737
12738 pub fn select_down_by_lines(
12739 &mut self,
12740 action: &SelectDownByLines,
12741 window: &mut Window,
12742 cx: &mut Context<Self>,
12743 ) {
12744 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12745 let text_layout_details = &self.text_layout_details(window);
12746 self.change_selections(Default::default(), window, cx, |s| {
12747 s.move_heads_with(|map, head, goal| {
12748 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12749 })
12750 })
12751 }
12752
12753 pub fn select_up_by_lines(
12754 &mut self,
12755 action: &SelectUpByLines,
12756 window: &mut Window,
12757 cx: &mut Context<Self>,
12758 ) {
12759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12760 let text_layout_details = &self.text_layout_details(window);
12761 self.change_selections(Default::default(), window, cx, |s| {
12762 s.move_heads_with(|map, head, goal| {
12763 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12764 })
12765 })
12766 }
12767
12768 pub fn select_page_up(
12769 &mut self,
12770 _: &SelectPageUp,
12771 window: &mut Window,
12772 cx: &mut Context<Self>,
12773 ) {
12774 let Some(row_count) = self.visible_row_count() else {
12775 return;
12776 };
12777
12778 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12779
12780 let text_layout_details = &self.text_layout_details(window);
12781
12782 self.change_selections(Default::default(), window, cx, |s| {
12783 s.move_heads_with(|map, head, goal| {
12784 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12785 })
12786 })
12787 }
12788
12789 pub fn move_page_up(
12790 &mut self,
12791 action: &MovePageUp,
12792 window: &mut Window,
12793 cx: &mut Context<Self>,
12794 ) {
12795 if self.take_rename(true, window, cx).is_some() {
12796 return;
12797 }
12798
12799 if self
12800 .context_menu
12801 .borrow_mut()
12802 .as_mut()
12803 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12804 .unwrap_or(false)
12805 {
12806 return;
12807 }
12808
12809 if matches!(self.mode, EditorMode::SingleLine) {
12810 cx.propagate();
12811 return;
12812 }
12813
12814 let Some(row_count) = self.visible_row_count() else {
12815 return;
12816 };
12817
12818 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12819
12820 let effects = if action.center_cursor {
12821 SelectionEffects::scroll(Autoscroll::center())
12822 } else {
12823 SelectionEffects::default()
12824 };
12825
12826 let text_layout_details = &self.text_layout_details(window);
12827
12828 self.change_selections(effects, window, cx, |s| {
12829 s.move_with(|map, selection| {
12830 if !selection.is_empty() {
12831 selection.goal = SelectionGoal::None;
12832 }
12833 let (cursor, goal) = movement::up_by_rows(
12834 map,
12835 selection.end,
12836 row_count,
12837 selection.goal,
12838 false,
12839 text_layout_details,
12840 );
12841 selection.collapse_to(cursor, goal);
12842 });
12843 });
12844 }
12845
12846 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12847 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12848 let text_layout_details = &self.text_layout_details(window);
12849 self.change_selections(Default::default(), window, cx, |s| {
12850 s.move_heads_with(|map, head, goal| {
12851 movement::up(map, head, goal, false, text_layout_details)
12852 })
12853 })
12854 }
12855
12856 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12857 self.take_rename(true, window, cx);
12858
12859 if self.mode.is_single_line() {
12860 cx.propagate();
12861 return;
12862 }
12863
12864 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12865
12866 let text_layout_details = &self.text_layout_details(window);
12867 let selection_count = self.selections.count();
12868 let first_selection = self.selections.first_anchor();
12869
12870 self.change_selections(Default::default(), window, cx, |s| {
12871 s.move_with(|map, selection| {
12872 if !selection.is_empty() {
12873 selection.goal = SelectionGoal::None;
12874 }
12875 let (cursor, goal) = movement::down(
12876 map,
12877 selection.end,
12878 selection.goal,
12879 false,
12880 text_layout_details,
12881 );
12882 selection.collapse_to(cursor, goal);
12883 });
12884 });
12885
12886 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12887 {
12888 cx.propagate();
12889 }
12890 }
12891
12892 pub fn select_page_down(
12893 &mut self,
12894 _: &SelectPageDown,
12895 window: &mut Window,
12896 cx: &mut Context<Self>,
12897 ) {
12898 let Some(row_count) = self.visible_row_count() else {
12899 return;
12900 };
12901
12902 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12903
12904 let text_layout_details = &self.text_layout_details(window);
12905
12906 self.change_selections(Default::default(), window, cx, |s| {
12907 s.move_heads_with(|map, head, goal| {
12908 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12909 })
12910 })
12911 }
12912
12913 pub fn move_page_down(
12914 &mut self,
12915 action: &MovePageDown,
12916 window: &mut Window,
12917 cx: &mut Context<Self>,
12918 ) {
12919 if self.take_rename(true, window, cx).is_some() {
12920 return;
12921 }
12922
12923 if self
12924 .context_menu
12925 .borrow_mut()
12926 .as_mut()
12927 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12928 .unwrap_or(false)
12929 {
12930 return;
12931 }
12932
12933 if matches!(self.mode, EditorMode::SingleLine) {
12934 cx.propagate();
12935 return;
12936 }
12937
12938 let Some(row_count) = self.visible_row_count() else {
12939 return;
12940 };
12941
12942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12943
12944 let effects = if action.center_cursor {
12945 SelectionEffects::scroll(Autoscroll::center())
12946 } else {
12947 SelectionEffects::default()
12948 };
12949
12950 let text_layout_details = &self.text_layout_details(window);
12951 self.change_selections(effects, window, cx, |s| {
12952 s.move_with(|map, selection| {
12953 if !selection.is_empty() {
12954 selection.goal = SelectionGoal::None;
12955 }
12956 let (cursor, goal) = movement::down_by_rows(
12957 map,
12958 selection.end,
12959 row_count,
12960 selection.goal,
12961 false,
12962 text_layout_details,
12963 );
12964 selection.collapse_to(cursor, goal);
12965 });
12966 });
12967 }
12968
12969 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12971 let text_layout_details = &self.text_layout_details(window);
12972 self.change_selections(Default::default(), window, cx, |s| {
12973 s.move_heads_with(|map, head, goal| {
12974 movement::down(map, head, goal, false, text_layout_details)
12975 })
12976 });
12977 }
12978
12979 pub fn context_menu_first(
12980 &mut self,
12981 _: &ContextMenuFirst,
12982 window: &mut Window,
12983 cx: &mut Context<Self>,
12984 ) {
12985 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12986 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12987 }
12988 }
12989
12990 pub fn context_menu_prev(
12991 &mut self,
12992 _: &ContextMenuPrevious,
12993 window: &mut Window,
12994 cx: &mut Context<Self>,
12995 ) {
12996 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12997 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12998 }
12999 }
13000
13001 pub fn context_menu_next(
13002 &mut self,
13003 _: &ContextMenuNext,
13004 window: &mut Window,
13005 cx: &mut Context<Self>,
13006 ) {
13007 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13008 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13009 }
13010 }
13011
13012 pub fn context_menu_last(
13013 &mut self,
13014 _: &ContextMenuLast,
13015 window: &mut Window,
13016 cx: &mut Context<Self>,
13017 ) {
13018 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13019 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13020 }
13021 }
13022
13023 pub fn signature_help_prev(
13024 &mut self,
13025 _: &SignatureHelpPrevious,
13026 _: &mut Window,
13027 cx: &mut Context<Self>,
13028 ) {
13029 if let Some(popover) = self.signature_help_state.popover_mut() {
13030 if popover.current_signature == 0 {
13031 popover.current_signature = popover.signatures.len() - 1;
13032 } else {
13033 popover.current_signature -= 1;
13034 }
13035 cx.notify();
13036 }
13037 }
13038
13039 pub fn signature_help_next(
13040 &mut self,
13041 _: &SignatureHelpNext,
13042 _: &mut Window,
13043 cx: &mut Context<Self>,
13044 ) {
13045 if let Some(popover) = self.signature_help_state.popover_mut() {
13046 if popover.current_signature + 1 == popover.signatures.len() {
13047 popover.current_signature = 0;
13048 } else {
13049 popover.current_signature += 1;
13050 }
13051 cx.notify();
13052 }
13053 }
13054
13055 pub fn move_to_previous_word_start(
13056 &mut self,
13057 _: &MoveToPreviousWordStart,
13058 window: &mut Window,
13059 cx: &mut Context<Self>,
13060 ) {
13061 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13062 self.change_selections(Default::default(), window, cx, |s| {
13063 s.move_cursors_with(|map, head, _| {
13064 (
13065 movement::previous_word_start(map, head),
13066 SelectionGoal::None,
13067 )
13068 });
13069 })
13070 }
13071
13072 pub fn move_to_previous_subword_start(
13073 &mut self,
13074 _: &MoveToPreviousSubwordStart,
13075 window: &mut Window,
13076 cx: &mut Context<Self>,
13077 ) {
13078 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13079 self.change_selections(Default::default(), window, cx, |s| {
13080 s.move_cursors_with(|map, head, _| {
13081 (
13082 movement::previous_subword_start(map, head),
13083 SelectionGoal::None,
13084 )
13085 });
13086 })
13087 }
13088
13089 pub fn select_to_previous_word_start(
13090 &mut self,
13091 _: &SelectToPreviousWordStart,
13092 window: &mut Window,
13093 cx: &mut Context<Self>,
13094 ) {
13095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13096 self.change_selections(Default::default(), window, cx, |s| {
13097 s.move_heads_with(|map, head, _| {
13098 (
13099 movement::previous_word_start(map, head),
13100 SelectionGoal::None,
13101 )
13102 });
13103 })
13104 }
13105
13106 pub fn select_to_previous_subword_start(
13107 &mut self,
13108 _: &SelectToPreviousSubwordStart,
13109 window: &mut Window,
13110 cx: &mut Context<Self>,
13111 ) {
13112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13113 self.change_selections(Default::default(), window, cx, |s| {
13114 s.move_heads_with(|map, head, _| {
13115 (
13116 movement::previous_subword_start(map, head),
13117 SelectionGoal::None,
13118 )
13119 });
13120 })
13121 }
13122
13123 pub fn delete_to_previous_word_start(
13124 &mut self,
13125 action: &DeleteToPreviousWordStart,
13126 window: &mut Window,
13127 cx: &mut Context<Self>,
13128 ) {
13129 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13130 self.transact(window, cx, |this, window, cx| {
13131 this.select_autoclose_pair(window, cx);
13132 this.change_selections(Default::default(), window, cx, |s| {
13133 s.move_with(|map, selection| {
13134 if selection.is_empty() {
13135 let cursor = if action.ignore_newlines {
13136 movement::previous_word_start(map, selection.head())
13137 } else {
13138 movement::previous_word_start_or_newline(map, selection.head())
13139 };
13140 selection.set_head(cursor, SelectionGoal::None);
13141 }
13142 });
13143 });
13144 this.insert("", window, cx);
13145 });
13146 }
13147
13148 pub fn delete_to_previous_subword_start(
13149 &mut self,
13150 _: &DeleteToPreviousSubwordStart,
13151 window: &mut Window,
13152 cx: &mut Context<Self>,
13153 ) {
13154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13155 self.transact(window, cx, |this, window, cx| {
13156 this.select_autoclose_pair(window, cx);
13157 this.change_selections(Default::default(), window, cx, |s| {
13158 s.move_with(|map, selection| {
13159 if selection.is_empty() {
13160 let cursor = movement::previous_subword_start(map, selection.head());
13161 selection.set_head(cursor, SelectionGoal::None);
13162 }
13163 });
13164 });
13165 this.insert("", window, cx);
13166 });
13167 }
13168
13169 pub fn move_to_next_word_end(
13170 &mut self,
13171 _: &MoveToNextWordEnd,
13172 window: &mut Window,
13173 cx: &mut Context<Self>,
13174 ) {
13175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13176 self.change_selections(Default::default(), window, cx, |s| {
13177 s.move_cursors_with(|map, head, _| {
13178 (movement::next_word_end(map, head), SelectionGoal::None)
13179 });
13180 })
13181 }
13182
13183 pub fn move_to_next_subword_end(
13184 &mut self,
13185 _: &MoveToNextSubwordEnd,
13186 window: &mut Window,
13187 cx: &mut Context<Self>,
13188 ) {
13189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13190 self.change_selections(Default::default(), window, cx, |s| {
13191 s.move_cursors_with(|map, head, _| {
13192 (movement::next_subword_end(map, head), SelectionGoal::None)
13193 });
13194 })
13195 }
13196
13197 pub fn select_to_next_word_end(
13198 &mut self,
13199 _: &SelectToNextWordEnd,
13200 window: &mut Window,
13201 cx: &mut Context<Self>,
13202 ) {
13203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13204 self.change_selections(Default::default(), window, cx, |s| {
13205 s.move_heads_with(|map, head, _| {
13206 (movement::next_word_end(map, head), SelectionGoal::None)
13207 });
13208 })
13209 }
13210
13211 pub fn select_to_next_subword_end(
13212 &mut self,
13213 _: &SelectToNextSubwordEnd,
13214 window: &mut Window,
13215 cx: &mut Context<Self>,
13216 ) {
13217 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13218 self.change_selections(Default::default(), window, cx, |s| {
13219 s.move_heads_with(|map, head, _| {
13220 (movement::next_subword_end(map, head), SelectionGoal::None)
13221 });
13222 })
13223 }
13224
13225 pub fn delete_to_next_word_end(
13226 &mut self,
13227 action: &DeleteToNextWordEnd,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13232 self.transact(window, cx, |this, window, cx| {
13233 this.change_selections(Default::default(), window, cx, |s| {
13234 s.move_with(|map, selection| {
13235 if selection.is_empty() {
13236 let cursor = if action.ignore_newlines {
13237 movement::next_word_end(map, selection.head())
13238 } else {
13239 movement::next_word_end_or_newline(map, selection.head())
13240 };
13241 selection.set_head(cursor, SelectionGoal::None);
13242 }
13243 });
13244 });
13245 this.insert("", window, cx);
13246 });
13247 }
13248
13249 pub fn delete_to_next_subword_end(
13250 &mut self,
13251 _: &DeleteToNextSubwordEnd,
13252 window: &mut Window,
13253 cx: &mut Context<Self>,
13254 ) {
13255 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13256 self.transact(window, cx, |this, window, cx| {
13257 this.change_selections(Default::default(), window, cx, |s| {
13258 s.move_with(|map, selection| {
13259 if selection.is_empty() {
13260 let cursor = movement::next_subword_end(map, selection.head());
13261 selection.set_head(cursor, SelectionGoal::None);
13262 }
13263 });
13264 });
13265 this.insert("", window, cx);
13266 });
13267 }
13268
13269 pub fn move_to_beginning_of_line(
13270 &mut self,
13271 action: &MoveToBeginningOfLine,
13272 window: &mut Window,
13273 cx: &mut Context<Self>,
13274 ) {
13275 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13276 self.change_selections(Default::default(), window, cx, |s| {
13277 s.move_cursors_with(|map, head, _| {
13278 (
13279 movement::indented_line_beginning(
13280 map,
13281 head,
13282 action.stop_at_soft_wraps,
13283 action.stop_at_indent,
13284 ),
13285 SelectionGoal::None,
13286 )
13287 });
13288 })
13289 }
13290
13291 pub fn select_to_beginning_of_line(
13292 &mut self,
13293 action: &SelectToBeginningOfLine,
13294 window: &mut Window,
13295 cx: &mut Context<Self>,
13296 ) {
13297 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13298 self.change_selections(Default::default(), window, cx, |s| {
13299 s.move_heads_with(|map, head, _| {
13300 (
13301 movement::indented_line_beginning(
13302 map,
13303 head,
13304 action.stop_at_soft_wraps,
13305 action.stop_at_indent,
13306 ),
13307 SelectionGoal::None,
13308 )
13309 });
13310 });
13311 }
13312
13313 pub fn delete_to_beginning_of_line(
13314 &mut self,
13315 action: &DeleteToBeginningOfLine,
13316 window: &mut Window,
13317 cx: &mut Context<Self>,
13318 ) {
13319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13320 self.transact(window, cx, |this, window, cx| {
13321 this.change_selections(Default::default(), window, cx, |s| {
13322 s.move_with(|_, selection| {
13323 selection.reversed = true;
13324 });
13325 });
13326
13327 this.select_to_beginning_of_line(
13328 &SelectToBeginningOfLine {
13329 stop_at_soft_wraps: false,
13330 stop_at_indent: action.stop_at_indent,
13331 },
13332 window,
13333 cx,
13334 );
13335 this.backspace(&Backspace, window, cx);
13336 });
13337 }
13338
13339 pub fn move_to_end_of_line(
13340 &mut self,
13341 action: &MoveToEndOfLine,
13342 window: &mut Window,
13343 cx: &mut Context<Self>,
13344 ) {
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13346 self.change_selections(Default::default(), window, cx, |s| {
13347 s.move_cursors_with(|map, head, _| {
13348 (
13349 movement::line_end(map, head, action.stop_at_soft_wraps),
13350 SelectionGoal::None,
13351 )
13352 });
13353 })
13354 }
13355
13356 pub fn select_to_end_of_line(
13357 &mut self,
13358 action: &SelectToEndOfLine,
13359 window: &mut Window,
13360 cx: &mut Context<Self>,
13361 ) {
13362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13363 self.change_selections(Default::default(), window, cx, |s| {
13364 s.move_heads_with(|map, head, _| {
13365 (
13366 movement::line_end(map, head, action.stop_at_soft_wraps),
13367 SelectionGoal::None,
13368 )
13369 });
13370 })
13371 }
13372
13373 pub fn delete_to_end_of_line(
13374 &mut self,
13375 _: &DeleteToEndOfLine,
13376 window: &mut Window,
13377 cx: &mut Context<Self>,
13378 ) {
13379 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13380 self.transact(window, cx, |this, window, cx| {
13381 this.select_to_end_of_line(
13382 &SelectToEndOfLine {
13383 stop_at_soft_wraps: false,
13384 },
13385 window,
13386 cx,
13387 );
13388 this.delete(&Delete, window, cx);
13389 });
13390 }
13391
13392 pub fn cut_to_end_of_line(
13393 &mut self,
13394 _: &CutToEndOfLine,
13395 window: &mut Window,
13396 cx: &mut Context<Self>,
13397 ) {
13398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13399 self.transact(window, cx, |this, window, cx| {
13400 this.select_to_end_of_line(
13401 &SelectToEndOfLine {
13402 stop_at_soft_wraps: false,
13403 },
13404 window,
13405 cx,
13406 );
13407 this.cut(&Cut, window, cx);
13408 });
13409 }
13410
13411 pub fn move_to_start_of_paragraph(
13412 &mut self,
13413 _: &MoveToStartOfParagraph,
13414 window: &mut Window,
13415 cx: &mut Context<Self>,
13416 ) {
13417 if matches!(self.mode, EditorMode::SingleLine) {
13418 cx.propagate();
13419 return;
13420 }
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13422 self.change_selections(Default::default(), window, cx, |s| {
13423 s.move_with(|map, selection| {
13424 selection.collapse_to(
13425 movement::start_of_paragraph(map, selection.head(), 1),
13426 SelectionGoal::None,
13427 )
13428 });
13429 })
13430 }
13431
13432 pub fn move_to_end_of_paragraph(
13433 &mut self,
13434 _: &MoveToEndOfParagraph,
13435 window: &mut Window,
13436 cx: &mut Context<Self>,
13437 ) {
13438 if matches!(self.mode, EditorMode::SingleLine) {
13439 cx.propagate();
13440 return;
13441 }
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13443 self.change_selections(Default::default(), window, cx, |s| {
13444 s.move_with(|map, selection| {
13445 selection.collapse_to(
13446 movement::end_of_paragraph(map, selection.head(), 1),
13447 SelectionGoal::None,
13448 )
13449 });
13450 })
13451 }
13452
13453 pub fn select_to_start_of_paragraph(
13454 &mut self,
13455 _: &SelectToStartOfParagraph,
13456 window: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) {
13459 if matches!(self.mode, EditorMode::SingleLine) {
13460 cx.propagate();
13461 return;
13462 }
13463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13464 self.change_selections(Default::default(), window, cx, |s| {
13465 s.move_heads_with(|map, head, _| {
13466 (
13467 movement::start_of_paragraph(map, head, 1),
13468 SelectionGoal::None,
13469 )
13470 });
13471 })
13472 }
13473
13474 pub fn select_to_end_of_paragraph(
13475 &mut self,
13476 _: &SelectToEndOfParagraph,
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_heads_with(|map, head, _| {
13487 (
13488 movement::end_of_paragraph(map, head, 1),
13489 SelectionGoal::None,
13490 )
13491 });
13492 })
13493 }
13494
13495 pub fn move_to_start_of_excerpt(
13496 &mut self,
13497 _: &MoveToStartOfExcerpt,
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::start_of_excerpt(
13510 map,
13511 selection.head(),
13512 workspace::searchable::Direction::Prev,
13513 ),
13514 SelectionGoal::None,
13515 )
13516 });
13517 })
13518 }
13519
13520 pub fn move_to_start_of_next_excerpt(
13521 &mut self,
13522 _: &MoveToStartOfNextExcerpt,
13523 window: &mut Window,
13524 cx: &mut Context<Self>,
13525 ) {
13526 if matches!(self.mode, EditorMode::SingleLine) {
13527 cx.propagate();
13528 return;
13529 }
13530
13531 self.change_selections(Default::default(), window, cx, |s| {
13532 s.move_with(|map, selection| {
13533 selection.collapse_to(
13534 movement::start_of_excerpt(
13535 map,
13536 selection.head(),
13537 workspace::searchable::Direction::Next,
13538 ),
13539 SelectionGoal::None,
13540 )
13541 });
13542 })
13543 }
13544
13545 pub fn move_to_end_of_excerpt(
13546 &mut self,
13547 _: &MoveToEndOfExcerpt,
13548 window: &mut Window,
13549 cx: &mut Context<Self>,
13550 ) {
13551 if matches!(self.mode, EditorMode::SingleLine) {
13552 cx.propagate();
13553 return;
13554 }
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13556 self.change_selections(Default::default(), window, cx, |s| {
13557 s.move_with(|map, selection| {
13558 selection.collapse_to(
13559 movement::end_of_excerpt(
13560 map,
13561 selection.head(),
13562 workspace::searchable::Direction::Next,
13563 ),
13564 SelectionGoal::None,
13565 )
13566 });
13567 })
13568 }
13569
13570 pub fn move_to_end_of_previous_excerpt(
13571 &mut self,
13572 _: &MoveToEndOfPreviousExcerpt,
13573 window: &mut Window,
13574 cx: &mut Context<Self>,
13575 ) {
13576 if matches!(self.mode, EditorMode::SingleLine) {
13577 cx.propagate();
13578 return;
13579 }
13580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13581 self.change_selections(Default::default(), window, cx, |s| {
13582 s.move_with(|map, selection| {
13583 selection.collapse_to(
13584 movement::end_of_excerpt(
13585 map,
13586 selection.head(),
13587 workspace::searchable::Direction::Prev,
13588 ),
13589 SelectionGoal::None,
13590 )
13591 });
13592 })
13593 }
13594
13595 pub fn select_to_start_of_excerpt(
13596 &mut self,
13597 _: &SelectToStartOfExcerpt,
13598 window: &mut Window,
13599 cx: &mut Context<Self>,
13600 ) {
13601 if matches!(self.mode, EditorMode::SingleLine) {
13602 cx.propagate();
13603 return;
13604 }
13605 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13606 self.change_selections(Default::default(), window, cx, |s| {
13607 s.move_heads_with(|map, head, _| {
13608 (
13609 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13610 SelectionGoal::None,
13611 )
13612 });
13613 })
13614 }
13615
13616 pub fn select_to_start_of_next_excerpt(
13617 &mut self,
13618 _: &SelectToStartOfNextExcerpt,
13619 window: &mut Window,
13620 cx: &mut Context<Self>,
13621 ) {
13622 if matches!(self.mode, EditorMode::SingleLine) {
13623 cx.propagate();
13624 return;
13625 }
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13627 self.change_selections(Default::default(), window, cx, |s| {
13628 s.move_heads_with(|map, head, _| {
13629 (
13630 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13631 SelectionGoal::None,
13632 )
13633 });
13634 })
13635 }
13636
13637 pub fn select_to_end_of_excerpt(
13638 &mut self,
13639 _: &SelectToEndOfExcerpt,
13640 window: &mut Window,
13641 cx: &mut Context<Self>,
13642 ) {
13643 if matches!(self.mode, EditorMode::SingleLine) {
13644 cx.propagate();
13645 return;
13646 }
13647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13648 self.change_selections(Default::default(), window, cx, |s| {
13649 s.move_heads_with(|map, head, _| {
13650 (
13651 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13652 SelectionGoal::None,
13653 )
13654 });
13655 })
13656 }
13657
13658 pub fn select_to_end_of_previous_excerpt(
13659 &mut self,
13660 _: &SelectToEndOfPreviousExcerpt,
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::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13673 SelectionGoal::None,
13674 )
13675 });
13676 })
13677 }
13678
13679 pub fn move_to_beginning(
13680 &mut self,
13681 _: &MoveToBeginning,
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.select_ranges(vec![0..0]);
13692 });
13693 }
13694
13695 pub fn select_to_beginning(
13696 &mut self,
13697 _: &SelectToBeginning,
13698 window: &mut Window,
13699 cx: &mut Context<Self>,
13700 ) {
13701 let mut selection = self.selections.last::<Point>(cx);
13702 selection.set_head(Point::zero(), SelectionGoal::None);
13703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13704 self.change_selections(Default::default(), window, cx, |s| {
13705 s.select(vec![selection]);
13706 });
13707 }
13708
13709 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13710 if matches!(self.mode, EditorMode::SingleLine) {
13711 cx.propagate();
13712 return;
13713 }
13714 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13715 let cursor = self.buffer.read(cx).read(cx).len();
13716 self.change_selections(Default::default(), window, cx, |s| {
13717 s.select_ranges(vec![cursor..cursor])
13718 });
13719 }
13720
13721 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13722 self.nav_history = nav_history;
13723 }
13724
13725 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13726 self.nav_history.as_ref()
13727 }
13728
13729 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13730 self.push_to_nav_history(
13731 self.selections.newest_anchor().head(),
13732 None,
13733 false,
13734 true,
13735 cx,
13736 );
13737 }
13738
13739 fn push_to_nav_history(
13740 &mut self,
13741 cursor_anchor: Anchor,
13742 new_position: Option<Point>,
13743 is_deactivate: bool,
13744 always: bool,
13745 cx: &mut Context<Self>,
13746 ) {
13747 if let Some(nav_history) = self.nav_history.as_mut() {
13748 let buffer = self.buffer.read(cx).read(cx);
13749 let cursor_position = cursor_anchor.to_point(&buffer);
13750 let scroll_state = self.scroll_manager.anchor();
13751 let scroll_top_row = scroll_state.top_row(&buffer);
13752 drop(buffer);
13753
13754 if let Some(new_position) = new_position {
13755 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13756 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13757 return;
13758 }
13759 }
13760
13761 nav_history.push(
13762 Some(NavigationData {
13763 cursor_anchor,
13764 cursor_position,
13765 scroll_anchor: scroll_state,
13766 scroll_top_row,
13767 }),
13768 cx,
13769 );
13770 cx.emit(EditorEvent::PushedToNavHistory {
13771 anchor: cursor_anchor,
13772 is_deactivate,
13773 })
13774 }
13775 }
13776
13777 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13778 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13779 let buffer = self.buffer.read(cx).snapshot(cx);
13780 let mut selection = self.selections.first::<usize>(cx);
13781 selection.set_head(buffer.len(), SelectionGoal::None);
13782 self.change_selections(Default::default(), window, cx, |s| {
13783 s.select(vec![selection]);
13784 });
13785 }
13786
13787 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13789 let end = self.buffer.read(cx).read(cx).len();
13790 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13791 s.select_ranges(vec![0..end]);
13792 });
13793 }
13794
13795 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13796 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13797 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13798 let mut selections = self.selections.all::<Point>(cx);
13799 let max_point = display_map.buffer_snapshot.max_point();
13800 for selection in &mut selections {
13801 let rows = selection.spanned_rows(true, &display_map);
13802 selection.start = Point::new(rows.start.0, 0);
13803 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13804 selection.reversed = false;
13805 }
13806 self.change_selections(Default::default(), window, cx, |s| {
13807 s.select(selections);
13808 });
13809 }
13810
13811 pub fn split_selection_into_lines(
13812 &mut self,
13813 action: &SplitSelectionIntoLines,
13814 window: &mut Window,
13815 cx: &mut Context<Self>,
13816 ) {
13817 let selections = self
13818 .selections
13819 .all::<Point>(cx)
13820 .into_iter()
13821 .map(|selection| selection.start..selection.end)
13822 .collect::<Vec<_>>();
13823 self.unfold_ranges(&selections, true, true, cx);
13824
13825 let mut new_selection_ranges = Vec::new();
13826 {
13827 let buffer = self.buffer.read(cx).read(cx);
13828 for selection in selections {
13829 for row in selection.start.row..selection.end.row {
13830 let line_start = Point::new(row, 0);
13831 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13832
13833 if action.keep_selections {
13834 // Keep the selection range for each line
13835 let selection_start = if row == selection.start.row {
13836 selection.start
13837 } else {
13838 line_start
13839 };
13840 new_selection_ranges.push(selection_start..line_end);
13841 } else {
13842 // Collapse to cursor at end of line
13843 new_selection_ranges.push(line_end..line_end);
13844 }
13845 }
13846
13847 let is_multiline_selection = selection.start.row != selection.end.row;
13848 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13849 // so this action feels more ergonomic when paired with other selection operations
13850 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13851 if !should_skip_last {
13852 if action.keep_selections {
13853 if is_multiline_selection {
13854 let line_start = Point::new(selection.end.row, 0);
13855 new_selection_ranges.push(line_start..selection.end);
13856 } else {
13857 new_selection_ranges.push(selection.start..selection.end);
13858 }
13859 } else {
13860 new_selection_ranges.push(selection.end..selection.end);
13861 }
13862 }
13863 }
13864 }
13865 self.change_selections(Default::default(), window, cx, |s| {
13866 s.select_ranges(new_selection_ranges);
13867 });
13868 }
13869
13870 pub fn add_selection_above(
13871 &mut self,
13872 _: &AddSelectionAbove,
13873 window: &mut Window,
13874 cx: &mut Context<Self>,
13875 ) {
13876 self.add_selection(true, window, cx);
13877 }
13878
13879 pub fn add_selection_below(
13880 &mut self,
13881 _: &AddSelectionBelow,
13882 window: &mut Window,
13883 cx: &mut Context<Self>,
13884 ) {
13885 self.add_selection(false, window, cx);
13886 }
13887
13888 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13890
13891 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13892 let all_selections = self.selections.all::<Point>(cx);
13893 let text_layout_details = self.text_layout_details(window);
13894
13895 let (mut columnar_selections, new_selections_to_columnarize) = {
13896 if let Some(state) = self.add_selections_state.as_ref() {
13897 let columnar_selection_ids: HashSet<_> = state
13898 .groups
13899 .iter()
13900 .flat_map(|group| group.stack.iter())
13901 .copied()
13902 .collect();
13903
13904 all_selections
13905 .into_iter()
13906 .partition(|s| columnar_selection_ids.contains(&s.id))
13907 } else {
13908 (Vec::new(), all_selections)
13909 }
13910 };
13911
13912 let mut state = self
13913 .add_selections_state
13914 .take()
13915 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13916
13917 for selection in new_selections_to_columnarize {
13918 let range = selection.display_range(&display_map).sorted();
13919 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13920 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13921 let positions = start_x.min(end_x)..start_x.max(end_x);
13922 let mut stack = Vec::new();
13923 for row in range.start.row().0..=range.end.row().0 {
13924 if let Some(selection) = self.selections.build_columnar_selection(
13925 &display_map,
13926 DisplayRow(row),
13927 &positions,
13928 selection.reversed,
13929 &text_layout_details,
13930 ) {
13931 stack.push(selection.id);
13932 columnar_selections.push(selection);
13933 }
13934 }
13935 if !stack.is_empty() {
13936 if above {
13937 stack.reverse();
13938 }
13939 state.groups.push(AddSelectionsGroup { above, stack });
13940 }
13941 }
13942
13943 let mut final_selections = Vec::new();
13944 let end_row = if above {
13945 DisplayRow(0)
13946 } else {
13947 display_map.max_point().row()
13948 };
13949
13950 let mut last_added_item_per_group = HashMap::default();
13951 for group in state.groups.iter_mut() {
13952 if let Some(last_id) = group.stack.last() {
13953 last_added_item_per_group.insert(*last_id, group);
13954 }
13955 }
13956
13957 for selection in columnar_selections {
13958 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13959 if above == group.above {
13960 let range = selection.display_range(&display_map).sorted();
13961 debug_assert_eq!(range.start.row(), range.end.row());
13962 let mut row = range.start.row();
13963 let positions =
13964 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13965 px(start)..px(end)
13966 } else {
13967 let start_x =
13968 display_map.x_for_display_point(range.start, &text_layout_details);
13969 let end_x =
13970 display_map.x_for_display_point(range.end, &text_layout_details);
13971 start_x.min(end_x)..start_x.max(end_x)
13972 };
13973
13974 let mut maybe_new_selection = None;
13975 while row != end_row {
13976 if above {
13977 row.0 -= 1;
13978 } else {
13979 row.0 += 1;
13980 }
13981 if let Some(new_selection) = self.selections.build_columnar_selection(
13982 &display_map,
13983 row,
13984 &positions,
13985 selection.reversed,
13986 &text_layout_details,
13987 ) {
13988 maybe_new_selection = Some(new_selection);
13989 break;
13990 }
13991 }
13992
13993 if let Some(new_selection) = maybe_new_selection {
13994 group.stack.push(new_selection.id);
13995 if above {
13996 final_selections.push(new_selection);
13997 final_selections.push(selection);
13998 } else {
13999 final_selections.push(selection);
14000 final_selections.push(new_selection);
14001 }
14002 } else {
14003 final_selections.push(selection);
14004 }
14005 } else {
14006 group.stack.pop();
14007 }
14008 } else {
14009 final_selections.push(selection);
14010 }
14011 }
14012
14013 self.change_selections(Default::default(), window, cx, |s| {
14014 s.select(final_selections);
14015 });
14016
14017 let final_selection_ids: HashSet<_> = self
14018 .selections
14019 .all::<Point>(cx)
14020 .iter()
14021 .map(|s| s.id)
14022 .collect();
14023 state.groups.retain_mut(|group| {
14024 // selections might get merged above so we remove invalid items from stacks
14025 group.stack.retain(|id| final_selection_ids.contains(id));
14026
14027 // single selection in stack can be treated as initial state
14028 group.stack.len() > 1
14029 });
14030
14031 if !state.groups.is_empty() {
14032 self.add_selections_state = Some(state);
14033 }
14034 }
14035
14036 fn select_match_ranges(
14037 &mut self,
14038 range: Range<usize>,
14039 reversed: bool,
14040 replace_newest: bool,
14041 auto_scroll: Option<Autoscroll>,
14042 window: &mut Window,
14043 cx: &mut Context<Editor>,
14044 ) {
14045 self.unfold_ranges(
14046 std::slice::from_ref(&range),
14047 false,
14048 auto_scroll.is_some(),
14049 cx,
14050 );
14051 let effects = if let Some(scroll) = auto_scroll {
14052 SelectionEffects::scroll(scroll)
14053 } else {
14054 SelectionEffects::no_scroll()
14055 };
14056 self.change_selections(effects, window, cx, |s| {
14057 if replace_newest {
14058 s.delete(s.newest_anchor().id);
14059 }
14060 if reversed {
14061 s.insert_range(range.end..range.start);
14062 } else {
14063 s.insert_range(range);
14064 }
14065 });
14066 }
14067
14068 pub fn select_next_match_internal(
14069 &mut self,
14070 display_map: &DisplaySnapshot,
14071 replace_newest: bool,
14072 autoscroll: Option<Autoscroll>,
14073 window: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) -> Result<()> {
14076 let buffer = &display_map.buffer_snapshot;
14077 let mut selections = self.selections.all::<usize>(cx);
14078 if let Some(mut select_next_state) = self.select_next_state.take() {
14079 let query = &select_next_state.query;
14080 if !select_next_state.done {
14081 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14082 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14083 let mut next_selected_range = None;
14084
14085 let bytes_after_last_selection =
14086 buffer.bytes_in_range(last_selection.end..buffer.len());
14087 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14088 let query_matches = query
14089 .stream_find_iter(bytes_after_last_selection)
14090 .map(|result| (last_selection.end, result))
14091 .chain(
14092 query
14093 .stream_find_iter(bytes_before_first_selection)
14094 .map(|result| (0, result)),
14095 );
14096
14097 for (start_offset, query_match) in query_matches {
14098 let query_match = query_match.unwrap(); // can only fail due to I/O
14099 let offset_range =
14100 start_offset + query_match.start()..start_offset + query_match.end();
14101
14102 if !select_next_state.wordwise
14103 || (!buffer.is_inside_word(offset_range.start, false)
14104 && !buffer.is_inside_word(offset_range.end, false))
14105 {
14106 // TODO: This is n^2, because we might check all the selections
14107 if !selections
14108 .iter()
14109 .any(|selection| selection.range().overlaps(&offset_range))
14110 {
14111 next_selected_range = Some(offset_range);
14112 break;
14113 }
14114 }
14115 }
14116
14117 if let Some(next_selected_range) = next_selected_range {
14118 self.select_match_ranges(
14119 next_selected_range,
14120 last_selection.reversed,
14121 replace_newest,
14122 autoscroll,
14123 window,
14124 cx,
14125 );
14126 } else {
14127 select_next_state.done = true;
14128 }
14129 }
14130
14131 self.select_next_state = Some(select_next_state);
14132 } else {
14133 let mut only_carets = true;
14134 let mut same_text_selected = true;
14135 let mut selected_text = None;
14136
14137 let mut selections_iter = selections.iter().peekable();
14138 while let Some(selection) = selections_iter.next() {
14139 if selection.start != selection.end {
14140 only_carets = false;
14141 }
14142
14143 if same_text_selected {
14144 if selected_text.is_none() {
14145 selected_text =
14146 Some(buffer.text_for_range(selection.range()).collect::<String>());
14147 }
14148
14149 if let Some(next_selection) = selections_iter.peek() {
14150 if next_selection.range().len() == selection.range().len() {
14151 let next_selected_text = buffer
14152 .text_for_range(next_selection.range())
14153 .collect::<String>();
14154 if Some(next_selected_text) != selected_text {
14155 same_text_selected = false;
14156 selected_text = None;
14157 }
14158 } else {
14159 same_text_selected = false;
14160 selected_text = None;
14161 }
14162 }
14163 }
14164 }
14165
14166 if only_carets {
14167 for selection in &mut selections {
14168 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14169 selection.start = word_range.start;
14170 selection.end = word_range.end;
14171 selection.goal = SelectionGoal::None;
14172 selection.reversed = false;
14173 self.select_match_ranges(
14174 selection.start..selection.end,
14175 selection.reversed,
14176 replace_newest,
14177 autoscroll,
14178 window,
14179 cx,
14180 );
14181 }
14182
14183 if selections.len() == 1 {
14184 let selection = selections
14185 .last()
14186 .expect("ensured that there's only one selection");
14187 let query = buffer
14188 .text_for_range(selection.start..selection.end)
14189 .collect::<String>();
14190 let is_empty = query.is_empty();
14191 let select_state = SelectNextState {
14192 query: AhoCorasick::new(&[query])?,
14193 wordwise: true,
14194 done: is_empty,
14195 };
14196 self.select_next_state = Some(select_state);
14197 } else {
14198 self.select_next_state = None;
14199 }
14200 } else if let Some(selected_text) = selected_text {
14201 self.select_next_state = Some(SelectNextState {
14202 query: AhoCorasick::new(&[selected_text])?,
14203 wordwise: false,
14204 done: false,
14205 });
14206 self.select_next_match_internal(
14207 display_map,
14208 replace_newest,
14209 autoscroll,
14210 window,
14211 cx,
14212 )?;
14213 }
14214 }
14215 Ok(())
14216 }
14217
14218 pub fn select_all_matches(
14219 &mut self,
14220 _action: &SelectAllMatches,
14221 window: &mut Window,
14222 cx: &mut Context<Self>,
14223 ) -> Result<()> {
14224 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14225
14226 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14227
14228 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14229 let Some(select_next_state) = self.select_next_state.as_mut() else {
14230 return Ok(());
14231 };
14232 if select_next_state.done {
14233 return Ok(());
14234 }
14235
14236 let mut new_selections = Vec::new();
14237
14238 let reversed = self.selections.oldest::<usize>(cx).reversed;
14239 let buffer = &display_map.buffer_snapshot;
14240 let query_matches = select_next_state
14241 .query
14242 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14243
14244 for query_match in query_matches.into_iter() {
14245 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14246 let offset_range = if reversed {
14247 query_match.end()..query_match.start()
14248 } else {
14249 query_match.start()..query_match.end()
14250 };
14251
14252 if !select_next_state.wordwise
14253 || (!buffer.is_inside_word(offset_range.start, false)
14254 && !buffer.is_inside_word(offset_range.end, false))
14255 {
14256 new_selections.push(offset_range.start..offset_range.end);
14257 }
14258 }
14259
14260 select_next_state.done = true;
14261
14262 if new_selections.is_empty() {
14263 log::error!("bug: new_selections is empty in select_all_matches");
14264 return Ok(());
14265 }
14266
14267 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14268 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14269 selections.select_ranges(new_selections)
14270 });
14271
14272 Ok(())
14273 }
14274
14275 pub fn select_next(
14276 &mut self,
14277 action: &SelectNext,
14278 window: &mut Window,
14279 cx: &mut Context<Self>,
14280 ) -> Result<()> {
14281 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14283 self.select_next_match_internal(
14284 &display_map,
14285 action.replace_newest,
14286 Some(Autoscroll::newest()),
14287 window,
14288 cx,
14289 )?;
14290 Ok(())
14291 }
14292
14293 pub fn select_previous(
14294 &mut self,
14295 action: &SelectPrevious,
14296 window: &mut Window,
14297 cx: &mut Context<Self>,
14298 ) -> Result<()> {
14299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14300 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14301 let buffer = &display_map.buffer_snapshot;
14302 let mut selections = self.selections.all::<usize>(cx);
14303 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14304 let query = &select_prev_state.query;
14305 if !select_prev_state.done {
14306 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14307 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14308 let mut next_selected_range = None;
14309 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14310 let bytes_before_last_selection =
14311 buffer.reversed_bytes_in_range(0..last_selection.start);
14312 let bytes_after_first_selection =
14313 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14314 let query_matches = query
14315 .stream_find_iter(bytes_before_last_selection)
14316 .map(|result| (last_selection.start, result))
14317 .chain(
14318 query
14319 .stream_find_iter(bytes_after_first_selection)
14320 .map(|result| (buffer.len(), result)),
14321 );
14322 for (end_offset, query_match) in query_matches {
14323 let query_match = query_match.unwrap(); // can only fail due to I/O
14324 let offset_range =
14325 end_offset - query_match.end()..end_offset - query_match.start();
14326
14327 if !select_prev_state.wordwise
14328 || (!buffer.is_inside_word(offset_range.start, false)
14329 && !buffer.is_inside_word(offset_range.end, false))
14330 {
14331 next_selected_range = Some(offset_range);
14332 break;
14333 }
14334 }
14335
14336 if let Some(next_selected_range) = next_selected_range {
14337 self.select_match_ranges(
14338 next_selected_range,
14339 last_selection.reversed,
14340 action.replace_newest,
14341 Some(Autoscroll::newest()),
14342 window,
14343 cx,
14344 );
14345 } else {
14346 select_prev_state.done = true;
14347 }
14348 }
14349
14350 self.select_prev_state = Some(select_prev_state);
14351 } else {
14352 let mut only_carets = true;
14353 let mut same_text_selected = true;
14354 let mut selected_text = None;
14355
14356 let mut selections_iter = selections.iter().peekable();
14357 while let Some(selection) = selections_iter.next() {
14358 if selection.start != selection.end {
14359 only_carets = false;
14360 }
14361
14362 if same_text_selected {
14363 if selected_text.is_none() {
14364 selected_text =
14365 Some(buffer.text_for_range(selection.range()).collect::<String>());
14366 }
14367
14368 if let Some(next_selection) = selections_iter.peek() {
14369 if next_selection.range().len() == selection.range().len() {
14370 let next_selected_text = buffer
14371 .text_for_range(next_selection.range())
14372 .collect::<String>();
14373 if Some(next_selected_text) != selected_text {
14374 same_text_selected = false;
14375 selected_text = None;
14376 }
14377 } else {
14378 same_text_selected = false;
14379 selected_text = None;
14380 }
14381 }
14382 }
14383 }
14384
14385 if only_carets {
14386 for selection in &mut selections {
14387 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14388 selection.start = word_range.start;
14389 selection.end = word_range.end;
14390 selection.goal = SelectionGoal::None;
14391 selection.reversed = false;
14392 self.select_match_ranges(
14393 selection.start..selection.end,
14394 selection.reversed,
14395 action.replace_newest,
14396 Some(Autoscroll::newest()),
14397 window,
14398 cx,
14399 );
14400 }
14401 if selections.len() == 1 {
14402 let selection = selections
14403 .last()
14404 .expect("ensured that there's only one selection");
14405 let query = buffer
14406 .text_for_range(selection.start..selection.end)
14407 .collect::<String>();
14408 let is_empty = query.is_empty();
14409 let select_state = SelectNextState {
14410 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14411 wordwise: true,
14412 done: is_empty,
14413 };
14414 self.select_prev_state = Some(select_state);
14415 } else {
14416 self.select_prev_state = None;
14417 }
14418 } else if let Some(selected_text) = selected_text {
14419 self.select_prev_state = Some(SelectNextState {
14420 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14421 wordwise: false,
14422 done: false,
14423 });
14424 self.select_previous(action, window, cx)?;
14425 }
14426 }
14427 Ok(())
14428 }
14429
14430 pub fn find_next_match(
14431 &mut self,
14432 _: &FindNextMatch,
14433 window: &mut Window,
14434 cx: &mut Context<Self>,
14435 ) -> Result<()> {
14436 let selections = self.selections.disjoint_anchors();
14437 match selections.first() {
14438 Some(first) if selections.len() >= 2 => {
14439 self.change_selections(Default::default(), window, cx, |s| {
14440 s.select_ranges([first.range()]);
14441 });
14442 }
14443 _ => self.select_next(
14444 &SelectNext {
14445 replace_newest: true,
14446 },
14447 window,
14448 cx,
14449 )?,
14450 }
14451 Ok(())
14452 }
14453
14454 pub fn find_previous_match(
14455 &mut self,
14456 _: &FindPreviousMatch,
14457 window: &mut Window,
14458 cx: &mut Context<Self>,
14459 ) -> Result<()> {
14460 let selections = self.selections.disjoint_anchors();
14461 match selections.last() {
14462 Some(last) if selections.len() >= 2 => {
14463 self.change_selections(Default::default(), window, cx, |s| {
14464 s.select_ranges([last.range()]);
14465 });
14466 }
14467 _ => self.select_previous(
14468 &SelectPrevious {
14469 replace_newest: true,
14470 },
14471 window,
14472 cx,
14473 )?,
14474 }
14475 Ok(())
14476 }
14477
14478 pub fn toggle_comments(
14479 &mut self,
14480 action: &ToggleComments,
14481 window: &mut Window,
14482 cx: &mut Context<Self>,
14483 ) {
14484 if self.read_only(cx) {
14485 return;
14486 }
14487 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14488 let text_layout_details = &self.text_layout_details(window);
14489 self.transact(window, cx, |this, window, cx| {
14490 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14491 let mut edits = Vec::new();
14492 let mut selection_edit_ranges = Vec::new();
14493 let mut last_toggled_row = None;
14494 let snapshot = this.buffer.read(cx).read(cx);
14495 let empty_str: Arc<str> = Arc::default();
14496 let mut suffixes_inserted = Vec::new();
14497 let ignore_indent = action.ignore_indent;
14498
14499 fn comment_prefix_range(
14500 snapshot: &MultiBufferSnapshot,
14501 row: MultiBufferRow,
14502 comment_prefix: &str,
14503 comment_prefix_whitespace: &str,
14504 ignore_indent: bool,
14505 ) -> Range<Point> {
14506 let indent_size = if ignore_indent {
14507 0
14508 } else {
14509 snapshot.indent_size_for_line(row).len
14510 };
14511
14512 let start = Point::new(row.0, indent_size);
14513
14514 let mut line_bytes = snapshot
14515 .bytes_in_range(start..snapshot.max_point())
14516 .flatten()
14517 .copied();
14518
14519 // If this line currently begins with the line comment prefix, then record
14520 // the range containing the prefix.
14521 if line_bytes
14522 .by_ref()
14523 .take(comment_prefix.len())
14524 .eq(comment_prefix.bytes())
14525 {
14526 // Include any whitespace that matches the comment prefix.
14527 let matching_whitespace_len = line_bytes
14528 .zip(comment_prefix_whitespace.bytes())
14529 .take_while(|(a, b)| a == b)
14530 .count() as u32;
14531 let end = Point::new(
14532 start.row,
14533 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14534 );
14535 start..end
14536 } else {
14537 start..start
14538 }
14539 }
14540
14541 fn comment_suffix_range(
14542 snapshot: &MultiBufferSnapshot,
14543 row: MultiBufferRow,
14544 comment_suffix: &str,
14545 comment_suffix_has_leading_space: bool,
14546 ) -> Range<Point> {
14547 let end = Point::new(row.0, snapshot.line_len(row));
14548 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14549
14550 let mut line_end_bytes = snapshot
14551 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14552 .flatten()
14553 .copied();
14554
14555 let leading_space_len = if suffix_start_column > 0
14556 && line_end_bytes.next() == Some(b' ')
14557 && comment_suffix_has_leading_space
14558 {
14559 1
14560 } else {
14561 0
14562 };
14563
14564 // If this line currently begins with the line comment prefix, then record
14565 // the range containing the prefix.
14566 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14567 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14568 start..end
14569 } else {
14570 end..end
14571 }
14572 }
14573
14574 // TODO: Handle selections that cross excerpts
14575 for selection in &mut selections {
14576 let start_column = snapshot
14577 .indent_size_for_line(MultiBufferRow(selection.start.row))
14578 .len;
14579 let language = if let Some(language) =
14580 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14581 {
14582 language
14583 } else {
14584 continue;
14585 };
14586
14587 selection_edit_ranges.clear();
14588
14589 // If multiple selections contain a given row, avoid processing that
14590 // row more than once.
14591 let mut start_row = MultiBufferRow(selection.start.row);
14592 if last_toggled_row == Some(start_row) {
14593 start_row = start_row.next_row();
14594 }
14595 let end_row =
14596 if selection.end.row > selection.start.row && selection.end.column == 0 {
14597 MultiBufferRow(selection.end.row - 1)
14598 } else {
14599 MultiBufferRow(selection.end.row)
14600 };
14601 last_toggled_row = Some(end_row);
14602
14603 if start_row > end_row {
14604 continue;
14605 }
14606
14607 // If the language has line comments, toggle those.
14608 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14609
14610 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14611 if ignore_indent {
14612 full_comment_prefixes = full_comment_prefixes
14613 .into_iter()
14614 .map(|s| Arc::from(s.trim_end()))
14615 .collect();
14616 }
14617
14618 if !full_comment_prefixes.is_empty() {
14619 let first_prefix = full_comment_prefixes
14620 .first()
14621 .expect("prefixes is non-empty");
14622 let prefix_trimmed_lengths = full_comment_prefixes
14623 .iter()
14624 .map(|p| p.trim_end_matches(' ').len())
14625 .collect::<SmallVec<[usize; 4]>>();
14626
14627 let mut all_selection_lines_are_comments = true;
14628
14629 for row in start_row.0..=end_row.0 {
14630 let row = MultiBufferRow(row);
14631 if start_row < end_row && snapshot.is_line_blank(row) {
14632 continue;
14633 }
14634
14635 let prefix_range = full_comment_prefixes
14636 .iter()
14637 .zip(prefix_trimmed_lengths.iter().copied())
14638 .map(|(prefix, trimmed_prefix_len)| {
14639 comment_prefix_range(
14640 snapshot.deref(),
14641 row,
14642 &prefix[..trimmed_prefix_len],
14643 &prefix[trimmed_prefix_len..],
14644 ignore_indent,
14645 )
14646 })
14647 .max_by_key(|range| range.end.column - range.start.column)
14648 .expect("prefixes is non-empty");
14649
14650 if prefix_range.is_empty() {
14651 all_selection_lines_are_comments = false;
14652 }
14653
14654 selection_edit_ranges.push(prefix_range);
14655 }
14656
14657 if all_selection_lines_are_comments {
14658 edits.extend(
14659 selection_edit_ranges
14660 .iter()
14661 .cloned()
14662 .map(|range| (range, empty_str.clone())),
14663 );
14664 } else {
14665 let min_column = selection_edit_ranges
14666 .iter()
14667 .map(|range| range.start.column)
14668 .min()
14669 .unwrap_or(0);
14670 edits.extend(selection_edit_ranges.iter().map(|range| {
14671 let position = Point::new(range.start.row, min_column);
14672 (position..position, first_prefix.clone())
14673 }));
14674 }
14675 } else if let Some(BlockCommentConfig {
14676 start: full_comment_prefix,
14677 end: comment_suffix,
14678 ..
14679 }) = language.block_comment()
14680 {
14681 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14682 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14683 let prefix_range = comment_prefix_range(
14684 snapshot.deref(),
14685 start_row,
14686 comment_prefix,
14687 comment_prefix_whitespace,
14688 ignore_indent,
14689 );
14690 let suffix_range = comment_suffix_range(
14691 snapshot.deref(),
14692 end_row,
14693 comment_suffix.trim_start_matches(' '),
14694 comment_suffix.starts_with(' '),
14695 );
14696
14697 if prefix_range.is_empty() || suffix_range.is_empty() {
14698 edits.push((
14699 prefix_range.start..prefix_range.start,
14700 full_comment_prefix.clone(),
14701 ));
14702 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14703 suffixes_inserted.push((end_row, comment_suffix.len()));
14704 } else {
14705 edits.push((prefix_range, empty_str.clone()));
14706 edits.push((suffix_range, empty_str.clone()));
14707 }
14708 } else {
14709 continue;
14710 }
14711 }
14712
14713 drop(snapshot);
14714 this.buffer.update(cx, |buffer, cx| {
14715 buffer.edit(edits, None, cx);
14716 });
14717
14718 // Adjust selections so that they end before any comment suffixes that
14719 // were inserted.
14720 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14721 let mut selections = this.selections.all::<Point>(cx);
14722 let snapshot = this.buffer.read(cx).read(cx);
14723 for selection in &mut selections {
14724 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14725 match row.cmp(&MultiBufferRow(selection.end.row)) {
14726 Ordering::Less => {
14727 suffixes_inserted.next();
14728 continue;
14729 }
14730 Ordering::Greater => break,
14731 Ordering::Equal => {
14732 if selection.end.column == snapshot.line_len(row) {
14733 if selection.is_empty() {
14734 selection.start.column -= suffix_len as u32;
14735 }
14736 selection.end.column -= suffix_len as u32;
14737 }
14738 break;
14739 }
14740 }
14741 }
14742 }
14743
14744 drop(snapshot);
14745 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14746
14747 let selections = this.selections.all::<Point>(cx);
14748 let selections_on_single_row = selections.windows(2).all(|selections| {
14749 selections[0].start.row == selections[1].start.row
14750 && selections[0].end.row == selections[1].end.row
14751 && selections[0].start.row == selections[0].end.row
14752 });
14753 let selections_selecting = selections
14754 .iter()
14755 .any(|selection| selection.start != selection.end);
14756 let advance_downwards = action.advance_downwards
14757 && selections_on_single_row
14758 && !selections_selecting
14759 && !matches!(this.mode, EditorMode::SingleLine);
14760
14761 if advance_downwards {
14762 let snapshot = this.buffer.read(cx).snapshot(cx);
14763
14764 this.change_selections(Default::default(), window, cx, |s| {
14765 s.move_cursors_with(|display_snapshot, display_point, _| {
14766 let mut point = display_point.to_point(display_snapshot);
14767 point.row += 1;
14768 point = snapshot.clip_point(point, Bias::Left);
14769 let display_point = point.to_display_point(display_snapshot);
14770 let goal = SelectionGoal::HorizontalPosition(
14771 display_snapshot
14772 .x_for_display_point(display_point, text_layout_details)
14773 .into(),
14774 );
14775 (display_point, goal)
14776 })
14777 });
14778 }
14779 });
14780 }
14781
14782 pub fn select_enclosing_symbol(
14783 &mut self,
14784 _: &SelectEnclosingSymbol,
14785 window: &mut Window,
14786 cx: &mut Context<Self>,
14787 ) {
14788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14789
14790 let buffer = self.buffer.read(cx).snapshot(cx);
14791 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14792
14793 fn update_selection(
14794 selection: &Selection<usize>,
14795 buffer_snap: &MultiBufferSnapshot,
14796 ) -> Option<Selection<usize>> {
14797 let cursor = selection.head();
14798 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14799 for symbol in symbols.iter().rev() {
14800 let start = symbol.range.start.to_offset(buffer_snap);
14801 let end = symbol.range.end.to_offset(buffer_snap);
14802 let new_range = start..end;
14803 if start < selection.start || end > selection.end {
14804 return Some(Selection {
14805 id: selection.id,
14806 start: new_range.start,
14807 end: new_range.end,
14808 goal: SelectionGoal::None,
14809 reversed: selection.reversed,
14810 });
14811 }
14812 }
14813 None
14814 }
14815
14816 let mut selected_larger_symbol = false;
14817 let new_selections = old_selections
14818 .iter()
14819 .map(|selection| match update_selection(selection, &buffer) {
14820 Some(new_selection) => {
14821 if new_selection.range() != selection.range() {
14822 selected_larger_symbol = true;
14823 }
14824 new_selection
14825 }
14826 None => selection.clone(),
14827 })
14828 .collect::<Vec<_>>();
14829
14830 if selected_larger_symbol {
14831 self.change_selections(Default::default(), window, cx, |s| {
14832 s.select(new_selections);
14833 });
14834 }
14835 }
14836
14837 pub fn select_larger_syntax_node(
14838 &mut self,
14839 _: &SelectLargerSyntaxNode,
14840 window: &mut Window,
14841 cx: &mut Context<Self>,
14842 ) {
14843 let Some(visible_row_count) = self.visible_row_count() else {
14844 return;
14845 };
14846 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14847 if old_selections.is_empty() {
14848 return;
14849 }
14850
14851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14852
14853 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14854 let buffer = self.buffer.read(cx).snapshot(cx);
14855
14856 let mut selected_larger_node = false;
14857 let mut new_selections = old_selections
14858 .iter()
14859 .map(|selection| {
14860 let old_range = selection.start..selection.end;
14861
14862 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14863 // manually select word at selection
14864 if ["string_content", "inline"].contains(&node.kind()) {
14865 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14866 // ignore if word is already selected
14867 if !word_range.is_empty() && old_range != word_range {
14868 let (last_word_range, _) =
14869 buffer.surrounding_word(old_range.end, false);
14870 // only select word if start and end point belongs to same word
14871 if word_range == last_word_range {
14872 selected_larger_node = true;
14873 return Selection {
14874 id: selection.id,
14875 start: word_range.start,
14876 end: word_range.end,
14877 goal: SelectionGoal::None,
14878 reversed: selection.reversed,
14879 };
14880 }
14881 }
14882 }
14883 }
14884
14885 let mut new_range = old_range.clone();
14886 while let Some((_node, containing_range)) =
14887 buffer.syntax_ancestor(new_range.clone())
14888 {
14889 new_range = match containing_range {
14890 MultiOrSingleBufferOffsetRange::Single(_) => break,
14891 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14892 };
14893 if !display_map.intersects_fold(new_range.start)
14894 && !display_map.intersects_fold(new_range.end)
14895 {
14896 break;
14897 }
14898 }
14899
14900 selected_larger_node |= new_range != old_range;
14901 Selection {
14902 id: selection.id,
14903 start: new_range.start,
14904 end: new_range.end,
14905 goal: SelectionGoal::None,
14906 reversed: selection.reversed,
14907 }
14908 })
14909 .collect::<Vec<_>>();
14910
14911 if !selected_larger_node {
14912 return; // don't put this call in the history
14913 }
14914
14915 // scroll based on transformation done to the last selection created by the user
14916 let (last_old, last_new) = old_selections
14917 .last()
14918 .zip(new_selections.last().cloned())
14919 .expect("old_selections isn't empty");
14920
14921 // revert selection
14922 let is_selection_reversed = {
14923 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14924 new_selections.last_mut().expect("checked above").reversed =
14925 should_newest_selection_be_reversed;
14926 should_newest_selection_be_reversed
14927 };
14928
14929 if selected_larger_node {
14930 self.select_syntax_node_history.disable_clearing = true;
14931 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14932 s.select(new_selections.clone());
14933 });
14934 self.select_syntax_node_history.disable_clearing = false;
14935 }
14936
14937 let start_row = last_new.start.to_display_point(&display_map).row().0;
14938 let end_row = last_new.end.to_display_point(&display_map).row().0;
14939 let selection_height = end_row - start_row + 1;
14940 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14941
14942 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14943 let scroll_behavior = if fits_on_the_screen {
14944 self.request_autoscroll(Autoscroll::fit(), cx);
14945 SelectSyntaxNodeScrollBehavior::FitSelection
14946 } else if is_selection_reversed {
14947 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14948 SelectSyntaxNodeScrollBehavior::CursorTop
14949 } else {
14950 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14951 SelectSyntaxNodeScrollBehavior::CursorBottom
14952 };
14953
14954 self.select_syntax_node_history.push((
14955 old_selections,
14956 scroll_behavior,
14957 is_selection_reversed,
14958 ));
14959 }
14960
14961 pub fn select_smaller_syntax_node(
14962 &mut self,
14963 _: &SelectSmallerSyntaxNode,
14964 window: &mut Window,
14965 cx: &mut Context<Self>,
14966 ) {
14967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14968
14969 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14970 self.select_syntax_node_history.pop()
14971 {
14972 if let Some(selection) = selections.last_mut() {
14973 selection.reversed = is_selection_reversed;
14974 }
14975
14976 self.select_syntax_node_history.disable_clearing = true;
14977 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14978 s.select(selections.to_vec());
14979 });
14980 self.select_syntax_node_history.disable_clearing = false;
14981
14982 match scroll_behavior {
14983 SelectSyntaxNodeScrollBehavior::CursorTop => {
14984 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14985 }
14986 SelectSyntaxNodeScrollBehavior::FitSelection => {
14987 self.request_autoscroll(Autoscroll::fit(), cx);
14988 }
14989 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14990 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14991 }
14992 }
14993 }
14994 }
14995
14996 pub fn unwrap_syntax_node(
14997 &mut self,
14998 _: &UnwrapSyntaxNode,
14999 window: &mut Window,
15000 cx: &mut Context<Self>,
15001 ) {
15002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15003
15004 let buffer = self.buffer.read(cx).snapshot(cx);
15005 let selections = self
15006 .selections
15007 .all::<usize>(cx)
15008 .into_iter()
15009 // subtracting the offset requires sorting
15010 .sorted_by_key(|i| i.start);
15011
15012 let full_edits = selections
15013 .into_iter()
15014 .filter_map(|selection| {
15015 // Only requires two branches once if-let-chains stabilize (#53667)
15016 let child = if !selection.is_empty() {
15017 selection.range()
15018 } else if let Some((_, ancestor_range)) =
15019 buffer.syntax_ancestor(selection.start..selection.end)
15020 {
15021 match ancestor_range {
15022 MultiOrSingleBufferOffsetRange::Single(range) => range,
15023 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15024 }
15025 } else {
15026 selection.range()
15027 };
15028
15029 let mut parent = child.clone();
15030 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15031 parent = match ancestor_range {
15032 MultiOrSingleBufferOffsetRange::Single(range) => range,
15033 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15034 };
15035 if parent.start < child.start || parent.end > child.end {
15036 break;
15037 }
15038 }
15039
15040 if parent == child {
15041 return None;
15042 }
15043 let text = buffer.text_for_range(child).collect::<String>();
15044 Some((selection.id, parent, text))
15045 })
15046 .collect::<Vec<_>>();
15047
15048 self.transact(window, cx, |this, window, cx| {
15049 this.buffer.update(cx, |buffer, cx| {
15050 buffer.edit(
15051 full_edits
15052 .iter()
15053 .map(|(_, p, t)| (p.clone(), t.clone()))
15054 .collect::<Vec<_>>(),
15055 None,
15056 cx,
15057 );
15058 });
15059 this.change_selections(Default::default(), window, cx, |s| {
15060 let mut offset = 0;
15061 let mut selections = vec![];
15062 for (id, parent, text) in full_edits {
15063 let start = parent.start - offset;
15064 offset += parent.len() - text.len();
15065 selections.push(Selection {
15066 id,
15067 start,
15068 end: start + text.len(),
15069 reversed: false,
15070 goal: Default::default(),
15071 });
15072 }
15073 s.select(selections);
15074 });
15075 });
15076 }
15077
15078 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15079 if !EditorSettings::get_global(cx).gutter.runnables {
15080 self.clear_tasks();
15081 return Task::ready(());
15082 }
15083 let project = self.project().map(Entity::downgrade);
15084 let task_sources = self.lsp_task_sources(cx);
15085 let multi_buffer = self.buffer.downgrade();
15086 cx.spawn_in(window, async move |editor, cx| {
15087 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15088 let Some(project) = project.and_then(|p| p.upgrade()) else {
15089 return;
15090 };
15091 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15092 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15093 }) else {
15094 return;
15095 };
15096
15097 let hide_runnables = project
15098 .update(cx, |project, _| project.is_via_collab())
15099 .unwrap_or(true);
15100 if hide_runnables {
15101 return;
15102 }
15103 let new_rows =
15104 cx.background_spawn({
15105 let snapshot = display_snapshot.clone();
15106 async move {
15107 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15108 }
15109 })
15110 .await;
15111 let Ok(lsp_tasks) =
15112 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15113 else {
15114 return;
15115 };
15116 let lsp_tasks = lsp_tasks.await;
15117
15118 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15119 lsp_tasks
15120 .into_iter()
15121 .flat_map(|(kind, tasks)| {
15122 tasks.into_iter().filter_map(move |(location, task)| {
15123 Some((kind.clone(), location?, task))
15124 })
15125 })
15126 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15127 let buffer = location.target.buffer;
15128 let buffer_snapshot = buffer.read(cx).snapshot();
15129 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15130 |(excerpt_id, snapshot, _)| {
15131 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15132 display_snapshot
15133 .buffer_snapshot
15134 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15135 } else {
15136 None
15137 }
15138 },
15139 );
15140 if let Some(offset) = offset {
15141 let task_buffer_range =
15142 location.target.range.to_point(&buffer_snapshot);
15143 let context_buffer_range =
15144 task_buffer_range.to_offset(&buffer_snapshot);
15145 let context_range = BufferOffset(context_buffer_range.start)
15146 ..BufferOffset(context_buffer_range.end);
15147
15148 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15149 .or_insert_with(|| RunnableTasks {
15150 templates: Vec::new(),
15151 offset,
15152 column: task_buffer_range.start.column,
15153 extra_variables: HashMap::default(),
15154 context_range,
15155 })
15156 .templates
15157 .push((kind, task.original_task().clone()));
15158 }
15159
15160 acc
15161 })
15162 }) else {
15163 return;
15164 };
15165
15166 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15167 buffer.language_settings(cx).tasks.prefer_lsp
15168 }) else {
15169 return;
15170 };
15171
15172 let rows = Self::runnable_rows(
15173 project,
15174 display_snapshot,
15175 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15176 new_rows,
15177 cx.clone(),
15178 )
15179 .await;
15180 editor
15181 .update(cx, |editor, _| {
15182 editor.clear_tasks();
15183 for (key, mut value) in rows {
15184 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15185 value.templates.extend(lsp_tasks.templates);
15186 }
15187
15188 editor.insert_tasks(key, value);
15189 }
15190 for (key, value) in lsp_tasks_by_rows {
15191 editor.insert_tasks(key, value);
15192 }
15193 })
15194 .ok();
15195 })
15196 }
15197 fn fetch_runnable_ranges(
15198 snapshot: &DisplaySnapshot,
15199 range: Range<Anchor>,
15200 ) -> Vec<language::RunnableRange> {
15201 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15202 }
15203
15204 fn runnable_rows(
15205 project: Entity<Project>,
15206 snapshot: DisplaySnapshot,
15207 prefer_lsp: bool,
15208 runnable_ranges: Vec<RunnableRange>,
15209 cx: AsyncWindowContext,
15210 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15211 cx.spawn(async move |cx| {
15212 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15213 for mut runnable in runnable_ranges {
15214 let Some(tasks) = cx
15215 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15216 .ok()
15217 else {
15218 continue;
15219 };
15220 let mut tasks = tasks.await;
15221
15222 if prefer_lsp {
15223 tasks.retain(|(task_kind, _)| {
15224 !matches!(task_kind, TaskSourceKind::Language { .. })
15225 });
15226 }
15227 if tasks.is_empty() {
15228 continue;
15229 }
15230
15231 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15232 let Some(row) = snapshot
15233 .buffer_snapshot
15234 .buffer_line_for_row(MultiBufferRow(point.row))
15235 .map(|(_, range)| range.start.row)
15236 else {
15237 continue;
15238 };
15239
15240 let context_range =
15241 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15242 runnable_rows.push((
15243 (runnable.buffer_id, row),
15244 RunnableTasks {
15245 templates: tasks,
15246 offset: snapshot
15247 .buffer_snapshot
15248 .anchor_before(runnable.run_range.start),
15249 context_range,
15250 column: point.column,
15251 extra_variables: runnable.extra_captures,
15252 },
15253 ));
15254 }
15255 runnable_rows
15256 })
15257 }
15258
15259 fn templates_with_tags(
15260 project: &Entity<Project>,
15261 runnable: &mut Runnable,
15262 cx: &mut App,
15263 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15264 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15265 let (worktree_id, file) = project
15266 .buffer_for_id(runnable.buffer, cx)
15267 .and_then(|buffer| buffer.read(cx).file())
15268 .map(|file| (file.worktree_id(cx), file.clone()))
15269 .unzip();
15270
15271 (
15272 project.task_store().read(cx).task_inventory().cloned(),
15273 worktree_id,
15274 file,
15275 )
15276 });
15277
15278 let tags = mem::take(&mut runnable.tags);
15279 let language = runnable.language.clone();
15280 cx.spawn(async move |cx| {
15281 let mut templates_with_tags = Vec::new();
15282 if let Some(inventory) = inventory {
15283 for RunnableTag(tag) in tags {
15284 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15285 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15286 }) else {
15287 return templates_with_tags;
15288 };
15289 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15290 move |(_, template)| {
15291 template.tags.iter().any(|source_tag| source_tag == &tag)
15292 },
15293 ));
15294 }
15295 }
15296 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15297
15298 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15299 // Strongest source wins; if we have worktree tag binding, prefer that to
15300 // global and language bindings;
15301 // if we have a global binding, prefer that to language binding.
15302 let first_mismatch = templates_with_tags
15303 .iter()
15304 .position(|(tag_source, _)| tag_source != leading_tag_source);
15305 if let Some(index) = first_mismatch {
15306 templates_with_tags.truncate(index);
15307 }
15308 }
15309
15310 templates_with_tags
15311 })
15312 }
15313
15314 pub fn move_to_enclosing_bracket(
15315 &mut self,
15316 _: &MoveToEnclosingBracket,
15317 window: &mut Window,
15318 cx: &mut Context<Self>,
15319 ) {
15320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15321 self.change_selections(Default::default(), window, cx, |s| {
15322 s.move_offsets_with(|snapshot, selection| {
15323 let Some(enclosing_bracket_ranges) =
15324 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15325 else {
15326 return;
15327 };
15328
15329 let mut best_length = usize::MAX;
15330 let mut best_inside = false;
15331 let mut best_in_bracket_range = false;
15332 let mut best_destination = None;
15333 for (open, close) in enclosing_bracket_ranges {
15334 let close = close.to_inclusive();
15335 let length = close.end() - open.start;
15336 let inside = selection.start >= open.end && selection.end <= *close.start();
15337 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15338 || close.contains(&selection.head());
15339
15340 // If best is next to a bracket and current isn't, skip
15341 if !in_bracket_range && best_in_bracket_range {
15342 continue;
15343 }
15344
15345 // Prefer smaller lengths unless best is inside and current isn't
15346 if length > best_length && (best_inside || !inside) {
15347 continue;
15348 }
15349
15350 best_length = length;
15351 best_inside = inside;
15352 best_in_bracket_range = in_bracket_range;
15353 best_destination = Some(
15354 if close.contains(&selection.start) && close.contains(&selection.end) {
15355 if inside { open.end } else { open.start }
15356 } else if inside {
15357 *close.start()
15358 } else {
15359 *close.end()
15360 },
15361 );
15362 }
15363
15364 if let Some(destination) = best_destination {
15365 selection.collapse_to(destination, SelectionGoal::None);
15366 }
15367 })
15368 });
15369 }
15370
15371 pub fn undo_selection(
15372 &mut self,
15373 _: &UndoSelection,
15374 window: &mut Window,
15375 cx: &mut Context<Self>,
15376 ) {
15377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15378 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15379 self.selection_history.mode = SelectionHistoryMode::Undoing;
15380 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15381 this.end_selection(window, cx);
15382 this.change_selections(
15383 SelectionEffects::scroll(Autoscroll::newest()),
15384 window,
15385 cx,
15386 |s| s.select_anchors(entry.selections.to_vec()),
15387 );
15388 });
15389 self.selection_history.mode = SelectionHistoryMode::Normal;
15390
15391 self.select_next_state = entry.select_next_state;
15392 self.select_prev_state = entry.select_prev_state;
15393 self.add_selections_state = entry.add_selections_state;
15394 }
15395 }
15396
15397 pub fn redo_selection(
15398 &mut self,
15399 _: &RedoSelection,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) {
15403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15404 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15405 self.selection_history.mode = SelectionHistoryMode::Redoing;
15406 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15407 this.end_selection(window, cx);
15408 this.change_selections(
15409 SelectionEffects::scroll(Autoscroll::newest()),
15410 window,
15411 cx,
15412 |s| s.select_anchors(entry.selections.to_vec()),
15413 );
15414 });
15415 self.selection_history.mode = SelectionHistoryMode::Normal;
15416
15417 self.select_next_state = entry.select_next_state;
15418 self.select_prev_state = entry.select_prev_state;
15419 self.add_selections_state = entry.add_selections_state;
15420 }
15421 }
15422
15423 pub fn expand_excerpts(
15424 &mut self,
15425 action: &ExpandExcerpts,
15426 _: &mut Window,
15427 cx: &mut Context<Self>,
15428 ) {
15429 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15430 }
15431
15432 pub fn expand_excerpts_down(
15433 &mut self,
15434 action: &ExpandExcerptsDown,
15435 _: &mut Window,
15436 cx: &mut Context<Self>,
15437 ) {
15438 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15439 }
15440
15441 pub fn expand_excerpts_up(
15442 &mut self,
15443 action: &ExpandExcerptsUp,
15444 _: &mut Window,
15445 cx: &mut Context<Self>,
15446 ) {
15447 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15448 }
15449
15450 pub fn expand_excerpts_for_direction(
15451 &mut self,
15452 lines: u32,
15453 direction: ExpandExcerptDirection,
15454
15455 cx: &mut Context<Self>,
15456 ) {
15457 let selections = self.selections.disjoint_anchors();
15458
15459 let lines = if lines == 0 {
15460 EditorSettings::get_global(cx).expand_excerpt_lines
15461 } else {
15462 lines
15463 };
15464
15465 self.buffer.update(cx, |buffer, cx| {
15466 let snapshot = buffer.snapshot(cx);
15467 let mut excerpt_ids = selections
15468 .iter()
15469 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15470 .collect::<Vec<_>>();
15471 excerpt_ids.sort();
15472 excerpt_ids.dedup();
15473 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15474 })
15475 }
15476
15477 pub fn expand_excerpt(
15478 &mut self,
15479 excerpt: ExcerptId,
15480 direction: ExpandExcerptDirection,
15481 window: &mut Window,
15482 cx: &mut Context<Self>,
15483 ) {
15484 let current_scroll_position = self.scroll_position(cx);
15485 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15486 let mut should_scroll_up = false;
15487
15488 if direction == ExpandExcerptDirection::Down {
15489 let multi_buffer = self.buffer.read(cx);
15490 let snapshot = multi_buffer.snapshot(cx);
15491 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15492 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15493 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15494 {
15495 let buffer_snapshot = buffer.read(cx).snapshot();
15496 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15497 let last_row = buffer_snapshot.max_point().row;
15498 let lines_below = last_row.saturating_sub(excerpt_end_row);
15499 should_scroll_up = lines_below >= lines_to_expand;
15500 }
15501 }
15502
15503 self.buffer.update(cx, |buffer, cx| {
15504 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15505 });
15506
15507 if should_scroll_up {
15508 let new_scroll_position =
15509 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15510 self.set_scroll_position(new_scroll_position, window, cx);
15511 }
15512 }
15513
15514 pub fn go_to_singleton_buffer_point(
15515 &mut self,
15516 point: Point,
15517 window: &mut Window,
15518 cx: &mut Context<Self>,
15519 ) {
15520 self.go_to_singleton_buffer_range(point..point, window, cx);
15521 }
15522
15523 pub fn go_to_singleton_buffer_range(
15524 &mut self,
15525 range: Range<Point>,
15526 window: &mut Window,
15527 cx: &mut Context<Self>,
15528 ) {
15529 let multibuffer = self.buffer().read(cx);
15530 let Some(buffer) = multibuffer.as_singleton() else {
15531 return;
15532 };
15533 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15534 return;
15535 };
15536 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15537 return;
15538 };
15539 self.change_selections(
15540 SelectionEffects::default().nav_history(true),
15541 window,
15542 cx,
15543 |s| s.select_anchor_ranges([start..end]),
15544 );
15545 }
15546
15547 pub fn go_to_diagnostic(
15548 &mut self,
15549 action: &GoToDiagnostic,
15550 window: &mut Window,
15551 cx: &mut Context<Self>,
15552 ) {
15553 if !self.diagnostics_enabled() {
15554 return;
15555 }
15556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15557 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15558 }
15559
15560 pub fn go_to_prev_diagnostic(
15561 &mut self,
15562 action: &GoToPreviousDiagnostic,
15563 window: &mut Window,
15564 cx: &mut Context<Self>,
15565 ) {
15566 if !self.diagnostics_enabled() {
15567 return;
15568 }
15569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15570 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15571 }
15572
15573 pub fn go_to_diagnostic_impl(
15574 &mut self,
15575 direction: Direction,
15576 severity: GoToDiagnosticSeverityFilter,
15577 window: &mut Window,
15578 cx: &mut Context<Self>,
15579 ) {
15580 let buffer = self.buffer.read(cx).snapshot(cx);
15581 let selection = self.selections.newest::<usize>(cx);
15582
15583 let mut active_group_id = None;
15584 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15585 && active_group.active_range.start.to_offset(&buffer) == selection.start
15586 {
15587 active_group_id = Some(active_group.group_id);
15588 }
15589
15590 fn filtered(
15591 snapshot: EditorSnapshot,
15592 severity: GoToDiagnosticSeverityFilter,
15593 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15594 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15595 diagnostics
15596 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15597 .filter(|entry| entry.range.start != entry.range.end)
15598 .filter(|entry| !entry.diagnostic.is_unnecessary)
15599 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15600 }
15601
15602 let snapshot = self.snapshot(window, cx);
15603 let before = filtered(
15604 snapshot.clone(),
15605 severity,
15606 buffer
15607 .diagnostics_in_range(0..selection.start)
15608 .filter(|entry| entry.range.start <= selection.start),
15609 );
15610 let after = filtered(
15611 snapshot,
15612 severity,
15613 buffer
15614 .diagnostics_in_range(selection.start..buffer.len())
15615 .filter(|entry| entry.range.start >= selection.start),
15616 );
15617
15618 let mut found: Option<DiagnosticEntry<usize>> = None;
15619 if direction == Direction::Prev {
15620 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15621 {
15622 for diagnostic in prev_diagnostics.into_iter().rev() {
15623 if diagnostic.range.start != selection.start
15624 || active_group_id
15625 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15626 {
15627 found = Some(diagnostic);
15628 break 'outer;
15629 }
15630 }
15631 }
15632 } else {
15633 for diagnostic in after.chain(before) {
15634 if diagnostic.range.start != selection.start
15635 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15636 {
15637 found = Some(diagnostic);
15638 break;
15639 }
15640 }
15641 }
15642 let Some(next_diagnostic) = found else {
15643 return;
15644 };
15645
15646 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15647 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15648 return;
15649 };
15650 self.change_selections(Default::default(), window, cx, |s| {
15651 s.select_ranges(vec![
15652 next_diagnostic.range.start..next_diagnostic.range.start,
15653 ])
15654 });
15655 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15656 self.refresh_edit_prediction(false, true, window, cx);
15657 }
15658
15659 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15660 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15661 let snapshot = self.snapshot(window, cx);
15662 let selection = self.selections.newest::<Point>(cx);
15663 self.go_to_hunk_before_or_after_position(
15664 &snapshot,
15665 selection.head(),
15666 Direction::Next,
15667 window,
15668 cx,
15669 );
15670 }
15671
15672 pub fn go_to_hunk_before_or_after_position(
15673 &mut self,
15674 snapshot: &EditorSnapshot,
15675 position: Point,
15676 direction: Direction,
15677 window: &mut Window,
15678 cx: &mut Context<Editor>,
15679 ) {
15680 let row = if direction == Direction::Next {
15681 self.hunk_after_position(snapshot, position)
15682 .map(|hunk| hunk.row_range.start)
15683 } else {
15684 self.hunk_before_position(snapshot, position)
15685 };
15686
15687 if let Some(row) = row {
15688 let destination = Point::new(row.0, 0);
15689 let autoscroll = Autoscroll::center();
15690
15691 self.unfold_ranges(&[destination..destination], false, false, cx);
15692 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15693 s.select_ranges([destination..destination]);
15694 });
15695 }
15696 }
15697
15698 fn hunk_after_position(
15699 &mut self,
15700 snapshot: &EditorSnapshot,
15701 position: Point,
15702 ) -> Option<MultiBufferDiffHunk> {
15703 snapshot
15704 .buffer_snapshot
15705 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15706 .find(|hunk| hunk.row_range.start.0 > position.row)
15707 .or_else(|| {
15708 snapshot
15709 .buffer_snapshot
15710 .diff_hunks_in_range(Point::zero()..position)
15711 .find(|hunk| hunk.row_range.end.0 < position.row)
15712 })
15713 }
15714
15715 fn go_to_prev_hunk(
15716 &mut self,
15717 _: &GoToPreviousHunk,
15718 window: &mut Window,
15719 cx: &mut Context<Self>,
15720 ) {
15721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15722 let snapshot = self.snapshot(window, cx);
15723 let selection = self.selections.newest::<Point>(cx);
15724 self.go_to_hunk_before_or_after_position(
15725 &snapshot,
15726 selection.head(),
15727 Direction::Prev,
15728 window,
15729 cx,
15730 );
15731 }
15732
15733 fn hunk_before_position(
15734 &mut self,
15735 snapshot: &EditorSnapshot,
15736 position: Point,
15737 ) -> Option<MultiBufferRow> {
15738 snapshot
15739 .buffer_snapshot
15740 .diff_hunk_before(position)
15741 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15742 }
15743
15744 fn go_to_next_change(
15745 &mut self,
15746 _: &GoToNextChange,
15747 window: &mut Window,
15748 cx: &mut Context<Self>,
15749 ) {
15750 if let Some(selections) = self
15751 .change_list
15752 .next_change(1, Direction::Next)
15753 .map(|s| s.to_vec())
15754 {
15755 self.change_selections(Default::default(), window, cx, |s| {
15756 let map = s.display_map();
15757 s.select_display_ranges(selections.iter().map(|a| {
15758 let point = a.to_display_point(&map);
15759 point..point
15760 }))
15761 })
15762 }
15763 }
15764
15765 fn go_to_previous_change(
15766 &mut self,
15767 _: &GoToPreviousChange,
15768 window: &mut Window,
15769 cx: &mut Context<Self>,
15770 ) {
15771 if let Some(selections) = self
15772 .change_list
15773 .next_change(1, Direction::Prev)
15774 .map(|s| s.to_vec())
15775 {
15776 self.change_selections(Default::default(), window, cx, |s| {
15777 let map = s.display_map();
15778 s.select_display_ranges(selections.iter().map(|a| {
15779 let point = a.to_display_point(&map);
15780 point..point
15781 }))
15782 })
15783 }
15784 }
15785
15786 fn go_to_line<T: 'static>(
15787 &mut self,
15788 position: Anchor,
15789 highlight_color: Option<Hsla>,
15790 window: &mut Window,
15791 cx: &mut Context<Self>,
15792 ) {
15793 let snapshot = self.snapshot(window, cx).display_snapshot;
15794 let position = position.to_point(&snapshot.buffer_snapshot);
15795 let start = snapshot
15796 .buffer_snapshot
15797 .clip_point(Point::new(position.row, 0), Bias::Left);
15798 let end = start + Point::new(1, 0);
15799 let start = snapshot.buffer_snapshot.anchor_before(start);
15800 let end = snapshot.buffer_snapshot.anchor_before(end);
15801
15802 self.highlight_rows::<T>(
15803 start..end,
15804 highlight_color
15805 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15806 Default::default(),
15807 cx,
15808 );
15809
15810 if self.buffer.read(cx).is_singleton() {
15811 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15812 }
15813 }
15814
15815 pub fn go_to_definition(
15816 &mut self,
15817 _: &GoToDefinition,
15818 window: &mut Window,
15819 cx: &mut Context<Self>,
15820 ) -> Task<Result<Navigated>> {
15821 let definition =
15822 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15823 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15824 cx.spawn_in(window, async move |editor, cx| {
15825 if definition.await? == Navigated::Yes {
15826 return Ok(Navigated::Yes);
15827 }
15828 match fallback_strategy {
15829 GoToDefinitionFallback::None => Ok(Navigated::No),
15830 GoToDefinitionFallback::FindAllReferences => {
15831 match editor.update_in(cx, |editor, window, cx| {
15832 editor.find_all_references(&FindAllReferences, window, cx)
15833 })? {
15834 Some(references) => references.await,
15835 None => Ok(Navigated::No),
15836 }
15837 }
15838 }
15839 })
15840 }
15841
15842 pub fn go_to_declaration(
15843 &mut self,
15844 _: &GoToDeclaration,
15845 window: &mut Window,
15846 cx: &mut Context<Self>,
15847 ) -> Task<Result<Navigated>> {
15848 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15849 }
15850
15851 pub fn go_to_declaration_split(
15852 &mut self,
15853 _: &GoToDeclaration,
15854 window: &mut Window,
15855 cx: &mut Context<Self>,
15856 ) -> Task<Result<Navigated>> {
15857 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15858 }
15859
15860 pub fn go_to_implementation(
15861 &mut self,
15862 _: &GoToImplementation,
15863 window: &mut Window,
15864 cx: &mut Context<Self>,
15865 ) -> Task<Result<Navigated>> {
15866 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15867 }
15868
15869 pub fn go_to_implementation_split(
15870 &mut self,
15871 _: &GoToImplementationSplit,
15872 window: &mut Window,
15873 cx: &mut Context<Self>,
15874 ) -> Task<Result<Navigated>> {
15875 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15876 }
15877
15878 pub fn go_to_type_definition(
15879 &mut self,
15880 _: &GoToTypeDefinition,
15881 window: &mut Window,
15882 cx: &mut Context<Self>,
15883 ) -> Task<Result<Navigated>> {
15884 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15885 }
15886
15887 pub fn go_to_definition_split(
15888 &mut self,
15889 _: &GoToDefinitionSplit,
15890 window: &mut Window,
15891 cx: &mut Context<Self>,
15892 ) -> Task<Result<Navigated>> {
15893 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15894 }
15895
15896 pub fn go_to_type_definition_split(
15897 &mut self,
15898 _: &GoToTypeDefinitionSplit,
15899 window: &mut Window,
15900 cx: &mut Context<Self>,
15901 ) -> Task<Result<Navigated>> {
15902 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15903 }
15904
15905 fn go_to_definition_of_kind(
15906 &mut self,
15907 kind: GotoDefinitionKind,
15908 split: bool,
15909 window: &mut Window,
15910 cx: &mut Context<Self>,
15911 ) -> Task<Result<Navigated>> {
15912 let Some(provider) = self.semantics_provider.clone() else {
15913 return Task::ready(Ok(Navigated::No));
15914 };
15915 let head = self.selections.newest::<usize>(cx).head();
15916 let buffer = self.buffer.read(cx);
15917 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15918 return Task::ready(Ok(Navigated::No));
15919 };
15920 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15921 return Task::ready(Ok(Navigated::No));
15922 };
15923
15924 cx.spawn_in(window, async move |editor, cx| {
15925 let Some(definitions) = definitions.await? else {
15926 return Ok(Navigated::No);
15927 };
15928 let navigated = editor
15929 .update_in(cx, |editor, window, cx| {
15930 editor.navigate_to_hover_links(
15931 Some(kind),
15932 definitions
15933 .into_iter()
15934 .filter(|location| {
15935 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15936 })
15937 .map(HoverLink::Text)
15938 .collect::<Vec<_>>(),
15939 split,
15940 window,
15941 cx,
15942 )
15943 })?
15944 .await?;
15945 anyhow::Ok(navigated)
15946 })
15947 }
15948
15949 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15950 let selection = self.selections.newest_anchor();
15951 let head = selection.head();
15952 let tail = selection.tail();
15953
15954 let Some((buffer, start_position)) =
15955 self.buffer.read(cx).text_anchor_for_position(head, cx)
15956 else {
15957 return;
15958 };
15959
15960 let end_position = if head != tail {
15961 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15962 return;
15963 };
15964 Some(pos)
15965 } else {
15966 None
15967 };
15968
15969 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15970 let url = if let Some(end_pos) = end_position {
15971 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15972 } else {
15973 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15974 };
15975
15976 if let Some(url) = url {
15977 editor.update(cx, |_, cx| {
15978 cx.open_url(&url);
15979 })
15980 } else {
15981 Ok(())
15982 }
15983 });
15984
15985 url_finder.detach();
15986 }
15987
15988 pub fn open_selected_filename(
15989 &mut self,
15990 _: &OpenSelectedFilename,
15991 window: &mut Window,
15992 cx: &mut Context<Self>,
15993 ) {
15994 let Some(workspace) = self.workspace() else {
15995 return;
15996 };
15997
15998 let position = self.selections.newest_anchor().head();
15999
16000 let Some((buffer, buffer_position)) =
16001 self.buffer.read(cx).text_anchor_for_position(position, cx)
16002 else {
16003 return;
16004 };
16005
16006 let project = self.project.clone();
16007
16008 cx.spawn_in(window, async move |_, cx| {
16009 let result = find_file(&buffer, project, buffer_position, cx).await;
16010
16011 if let Some((_, path)) = result {
16012 workspace
16013 .update_in(cx, |workspace, window, cx| {
16014 workspace.open_resolved_path(path, window, cx)
16015 })?
16016 .await?;
16017 }
16018 anyhow::Ok(())
16019 })
16020 .detach();
16021 }
16022
16023 pub(crate) fn navigate_to_hover_links(
16024 &mut self,
16025 kind: Option<GotoDefinitionKind>,
16026 definitions: Vec<HoverLink>,
16027 split: bool,
16028 window: &mut Window,
16029 cx: &mut Context<Editor>,
16030 ) -> Task<Result<Navigated>> {
16031 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16032 let mut first_url_or_file = None;
16033 let definitions: Vec<_> = definitions
16034 .into_iter()
16035 .filter_map(|def| match def {
16036 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16037 HoverLink::InlayHint(lsp_location, server_id) => {
16038 let computation =
16039 self.compute_target_location(lsp_location, server_id, window, cx);
16040 Some(cx.background_spawn(computation))
16041 }
16042 HoverLink::Url(url) => {
16043 first_url_or_file = Some(Either::Left(url));
16044 None
16045 }
16046 HoverLink::File(path) => {
16047 first_url_or_file = Some(Either::Right(path));
16048 None
16049 }
16050 })
16051 .collect();
16052
16053 let workspace = self.workspace();
16054
16055 cx.spawn_in(window, async move |editor, acx| {
16056 let mut locations: Vec<Location> = future::join_all(definitions)
16057 .await
16058 .into_iter()
16059 .filter_map(|location| location.transpose())
16060 .collect::<Result<_>>()
16061 .context("location tasks")?;
16062
16063 if locations.len() > 1 {
16064 let Some(workspace) = workspace else {
16065 return Ok(Navigated::No);
16066 };
16067
16068 let tab_kind = match kind {
16069 Some(GotoDefinitionKind::Implementation) => "Implementations",
16070 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16071 Some(GotoDefinitionKind::Declaration) => "Declarations",
16072 Some(GotoDefinitionKind::Type) => "Types",
16073 };
16074 let title = editor
16075 .update_in(acx, |_, _, cx| {
16076 let target = locations
16077 .iter()
16078 .map(|location| {
16079 location
16080 .buffer
16081 .read(cx)
16082 .text_for_range(location.range.clone())
16083 .collect::<String>()
16084 })
16085 .filter(|text| !text.contains('\n'))
16086 .unique()
16087 .take(3)
16088 .join(", ");
16089 if target.is_empty() {
16090 tab_kind.to_owned()
16091 } else {
16092 format!("{tab_kind} for {target}")
16093 }
16094 })
16095 .context("buffer title")?;
16096
16097 let opened = workspace
16098 .update_in(acx, |workspace, window, cx| {
16099 Self::open_locations_in_multibuffer(
16100 workspace,
16101 locations,
16102 title,
16103 split,
16104 MultibufferSelectionMode::First,
16105 window,
16106 cx,
16107 )
16108 })
16109 .is_ok();
16110
16111 anyhow::Ok(Navigated::from_bool(opened))
16112 } else if locations.is_empty() {
16113 // If there is one definition, just open it directly
16114 match first_url_or_file {
16115 Some(Either::Left(url)) => {
16116 acx.update(|_, cx| cx.open_url(&url))?;
16117 Ok(Navigated::Yes)
16118 }
16119 Some(Either::Right(path)) => {
16120 let Some(workspace) = workspace else {
16121 return Ok(Navigated::No);
16122 };
16123
16124 workspace
16125 .update_in(acx, |workspace, window, cx| {
16126 workspace.open_resolved_path(path, window, cx)
16127 })?
16128 .await?;
16129 Ok(Navigated::Yes)
16130 }
16131 None => Ok(Navigated::No),
16132 }
16133 } else {
16134 let Some(workspace) = workspace else {
16135 return Ok(Navigated::No);
16136 };
16137
16138 let target = locations.pop().unwrap();
16139 editor.update_in(acx, |editor, window, cx| {
16140 let pane = workspace.read(cx).active_pane().clone();
16141
16142 let range = target.range.to_point(target.buffer.read(cx));
16143 let range = editor.range_for_match(&range);
16144 let range = collapse_multiline_range(range);
16145
16146 if !split
16147 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16148 {
16149 editor.go_to_singleton_buffer_range(range, window, cx);
16150 } else {
16151 window.defer(cx, move |window, cx| {
16152 let target_editor: Entity<Self> =
16153 workspace.update(cx, |workspace, cx| {
16154 let pane = if split {
16155 workspace.adjacent_pane(window, cx)
16156 } else {
16157 workspace.active_pane().clone()
16158 };
16159
16160 workspace.open_project_item(
16161 pane,
16162 target.buffer.clone(),
16163 true,
16164 true,
16165 window,
16166 cx,
16167 )
16168 });
16169 target_editor.update(cx, |target_editor, cx| {
16170 // When selecting a definition in a different buffer, disable the nav history
16171 // to avoid creating a history entry at the previous cursor location.
16172 pane.update(cx, |pane, _| pane.disable_history());
16173 target_editor.go_to_singleton_buffer_range(range, window, cx);
16174 pane.update(cx, |pane, _| pane.enable_history());
16175 });
16176 });
16177 }
16178 Navigated::Yes
16179 })
16180 }
16181 })
16182 }
16183
16184 fn compute_target_location(
16185 &self,
16186 lsp_location: lsp::Location,
16187 server_id: LanguageServerId,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) -> Task<anyhow::Result<Option<Location>>> {
16191 let Some(project) = self.project.clone() else {
16192 return Task::ready(Ok(None));
16193 };
16194
16195 cx.spawn_in(window, async move |editor, cx| {
16196 let location_task = editor.update(cx, |_, cx| {
16197 project.update(cx, |project, cx| {
16198 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16199 })
16200 })?;
16201 let location = Some({
16202 let target_buffer_handle = location_task.await.context("open local buffer")?;
16203 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16204 let target_start = target_buffer
16205 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16206 let target_end = target_buffer
16207 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16208 target_buffer.anchor_after(target_start)
16209 ..target_buffer.anchor_before(target_end)
16210 })?;
16211 Location {
16212 buffer: target_buffer_handle,
16213 range,
16214 }
16215 });
16216 Ok(location)
16217 })
16218 }
16219
16220 pub fn find_all_references(
16221 &mut self,
16222 _: &FindAllReferences,
16223 window: &mut Window,
16224 cx: &mut Context<Self>,
16225 ) -> Option<Task<Result<Navigated>>> {
16226 let selection = self.selections.newest::<usize>(cx);
16227 let multi_buffer = self.buffer.read(cx);
16228 let head = selection.head();
16229
16230 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16231 let head_anchor = multi_buffer_snapshot.anchor_at(
16232 head,
16233 if head < selection.tail() {
16234 Bias::Right
16235 } else {
16236 Bias::Left
16237 },
16238 );
16239
16240 match self
16241 .find_all_references_task_sources
16242 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16243 {
16244 Ok(_) => {
16245 log::info!(
16246 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16247 );
16248 return None;
16249 }
16250 Err(i) => {
16251 self.find_all_references_task_sources.insert(i, head_anchor);
16252 }
16253 }
16254
16255 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16256 let workspace = self.workspace()?;
16257 let project = workspace.read(cx).project().clone();
16258 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16259 Some(cx.spawn_in(window, async move |editor, cx| {
16260 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16261 if let Ok(i) = editor
16262 .find_all_references_task_sources
16263 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16264 {
16265 editor.find_all_references_task_sources.remove(i);
16266 }
16267 });
16268
16269 let Some(locations) = references.await? else {
16270 return anyhow::Ok(Navigated::No);
16271 };
16272 if locations.is_empty() {
16273 return anyhow::Ok(Navigated::No);
16274 }
16275
16276 workspace.update_in(cx, |workspace, window, cx| {
16277 let target = locations
16278 .iter()
16279 .map(|location| {
16280 location
16281 .buffer
16282 .read(cx)
16283 .text_for_range(location.range.clone())
16284 .collect::<String>()
16285 })
16286 .filter(|text| !text.contains('\n'))
16287 .unique()
16288 .take(3)
16289 .join(", ");
16290 let title = if target.is_empty() {
16291 "References".to_owned()
16292 } else {
16293 format!("References to {target}")
16294 };
16295 Self::open_locations_in_multibuffer(
16296 workspace,
16297 locations,
16298 title,
16299 false,
16300 MultibufferSelectionMode::First,
16301 window,
16302 cx,
16303 );
16304 Navigated::Yes
16305 })
16306 }))
16307 }
16308
16309 /// Opens a multibuffer with the given project locations in it
16310 pub fn open_locations_in_multibuffer(
16311 workspace: &mut Workspace,
16312 mut locations: Vec<Location>,
16313 title: String,
16314 split: bool,
16315 multibuffer_selection_mode: MultibufferSelectionMode,
16316 window: &mut Window,
16317 cx: &mut Context<Workspace>,
16318 ) {
16319 if locations.is_empty() {
16320 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16321 return;
16322 }
16323
16324 // If there are multiple definitions, open them in a multibuffer
16325 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16326 let mut locations = locations.into_iter().peekable();
16327 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16328 let capability = workspace.project().read(cx).capability();
16329
16330 let excerpt_buffer = cx.new(|cx| {
16331 let mut multibuffer = MultiBuffer::new(capability);
16332 while let Some(location) = locations.next() {
16333 let buffer = location.buffer.read(cx);
16334 let mut ranges_for_buffer = Vec::new();
16335 let range = location.range.to_point(buffer);
16336 ranges_for_buffer.push(range.clone());
16337
16338 while let Some(next_location) = locations.peek() {
16339 if next_location.buffer == location.buffer {
16340 ranges_for_buffer.push(next_location.range.to_point(buffer));
16341 locations.next();
16342 } else {
16343 break;
16344 }
16345 }
16346
16347 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16348 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16349 PathKey::for_buffer(&location.buffer, cx),
16350 location.buffer.clone(),
16351 ranges_for_buffer,
16352 multibuffer_context_lines(cx),
16353 cx,
16354 );
16355 ranges.extend(new_ranges)
16356 }
16357
16358 multibuffer.with_title(title)
16359 });
16360
16361 let editor = cx.new(|cx| {
16362 Editor::for_multibuffer(
16363 excerpt_buffer,
16364 Some(workspace.project().clone()),
16365 window,
16366 cx,
16367 )
16368 });
16369 editor.update(cx, |editor, cx| {
16370 match multibuffer_selection_mode {
16371 MultibufferSelectionMode::First => {
16372 if let Some(first_range) = ranges.first() {
16373 editor.change_selections(
16374 SelectionEffects::no_scroll(),
16375 window,
16376 cx,
16377 |selections| {
16378 selections.clear_disjoint();
16379 selections
16380 .select_anchor_ranges(std::iter::once(first_range.clone()));
16381 },
16382 );
16383 }
16384 editor.highlight_background::<Self>(
16385 &ranges,
16386 |theme| theme.colors().editor_highlighted_line_background,
16387 cx,
16388 );
16389 }
16390 MultibufferSelectionMode::All => {
16391 editor.change_selections(
16392 SelectionEffects::no_scroll(),
16393 window,
16394 cx,
16395 |selections| {
16396 selections.clear_disjoint();
16397 selections.select_anchor_ranges(ranges);
16398 },
16399 );
16400 }
16401 }
16402 editor.register_buffers_with_language_servers(cx);
16403 });
16404
16405 let item = Box::new(editor);
16406 let item_id = item.item_id();
16407
16408 if split {
16409 workspace.split_item(SplitDirection::Right, item, window, cx);
16410 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16411 let (preview_item_id, preview_item_idx) =
16412 workspace.active_pane().read_with(cx, |pane, _| {
16413 (pane.preview_item_id(), pane.preview_item_idx())
16414 });
16415
16416 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16417
16418 if let Some(preview_item_id) = preview_item_id {
16419 workspace.active_pane().update(cx, |pane, cx| {
16420 pane.remove_item(preview_item_id, false, false, window, cx);
16421 });
16422 }
16423 } else {
16424 workspace.add_item_to_active_pane(item, None, true, window, cx);
16425 }
16426 workspace.active_pane().update(cx, |pane, cx| {
16427 pane.set_preview_item_id(Some(item_id), cx);
16428 });
16429 }
16430
16431 pub fn rename(
16432 &mut self,
16433 _: &Rename,
16434 window: &mut Window,
16435 cx: &mut Context<Self>,
16436 ) -> Option<Task<Result<()>>> {
16437 use language::ToOffset as _;
16438
16439 let provider = self.semantics_provider.clone()?;
16440 let selection = self.selections.newest_anchor().clone();
16441 let (cursor_buffer, cursor_buffer_position) = self
16442 .buffer
16443 .read(cx)
16444 .text_anchor_for_position(selection.head(), cx)?;
16445 let (tail_buffer, cursor_buffer_position_end) = self
16446 .buffer
16447 .read(cx)
16448 .text_anchor_for_position(selection.tail(), cx)?;
16449 if tail_buffer != cursor_buffer {
16450 return None;
16451 }
16452
16453 let snapshot = cursor_buffer.read(cx).snapshot();
16454 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16455 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16456 let prepare_rename = provider
16457 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16458 .unwrap_or_else(|| Task::ready(Ok(None)));
16459 drop(snapshot);
16460
16461 Some(cx.spawn_in(window, async move |this, cx| {
16462 let rename_range = if let Some(range) = prepare_rename.await? {
16463 Some(range)
16464 } else {
16465 this.update(cx, |this, cx| {
16466 let buffer = this.buffer.read(cx).snapshot(cx);
16467 let mut buffer_highlights = this
16468 .document_highlights_for_position(selection.head(), &buffer)
16469 .filter(|highlight| {
16470 highlight.start.excerpt_id == selection.head().excerpt_id
16471 && highlight.end.excerpt_id == selection.head().excerpt_id
16472 });
16473 buffer_highlights
16474 .next()
16475 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16476 })?
16477 };
16478 if let Some(rename_range) = rename_range {
16479 this.update_in(cx, |this, window, cx| {
16480 let snapshot = cursor_buffer.read(cx).snapshot();
16481 let rename_buffer_range = rename_range.to_offset(&snapshot);
16482 let cursor_offset_in_rename_range =
16483 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16484 let cursor_offset_in_rename_range_end =
16485 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16486
16487 this.take_rename(false, window, cx);
16488 let buffer = this.buffer.read(cx).read(cx);
16489 let cursor_offset = selection.head().to_offset(&buffer);
16490 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16491 let rename_end = rename_start + rename_buffer_range.len();
16492 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16493 let mut old_highlight_id = None;
16494 let old_name: Arc<str> = buffer
16495 .chunks(rename_start..rename_end, true)
16496 .map(|chunk| {
16497 if old_highlight_id.is_none() {
16498 old_highlight_id = chunk.syntax_highlight_id;
16499 }
16500 chunk.text
16501 })
16502 .collect::<String>()
16503 .into();
16504
16505 drop(buffer);
16506
16507 // Position the selection in the rename editor so that it matches the current selection.
16508 this.show_local_selections = false;
16509 let rename_editor = cx.new(|cx| {
16510 let mut editor = Editor::single_line(window, cx);
16511 editor.buffer.update(cx, |buffer, cx| {
16512 buffer.edit([(0..0, old_name.clone())], None, cx)
16513 });
16514 let rename_selection_range = match cursor_offset_in_rename_range
16515 .cmp(&cursor_offset_in_rename_range_end)
16516 {
16517 Ordering::Equal => {
16518 editor.select_all(&SelectAll, window, cx);
16519 return editor;
16520 }
16521 Ordering::Less => {
16522 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16523 }
16524 Ordering::Greater => {
16525 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16526 }
16527 };
16528 if rename_selection_range.end > old_name.len() {
16529 editor.select_all(&SelectAll, window, cx);
16530 } else {
16531 editor.change_selections(Default::default(), window, cx, |s| {
16532 s.select_ranges([rename_selection_range]);
16533 });
16534 }
16535 editor
16536 });
16537 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16538 if e == &EditorEvent::Focused {
16539 cx.emit(EditorEvent::FocusedIn)
16540 }
16541 })
16542 .detach();
16543
16544 let write_highlights =
16545 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16546 let read_highlights =
16547 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16548 let ranges = write_highlights
16549 .iter()
16550 .flat_map(|(_, ranges)| ranges.iter())
16551 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16552 .cloned()
16553 .collect();
16554
16555 this.highlight_text::<Rename>(
16556 ranges,
16557 HighlightStyle {
16558 fade_out: Some(0.6),
16559 ..Default::default()
16560 },
16561 cx,
16562 );
16563 let rename_focus_handle = rename_editor.focus_handle(cx);
16564 window.focus(&rename_focus_handle);
16565 let block_id = this.insert_blocks(
16566 [BlockProperties {
16567 style: BlockStyle::Flex,
16568 placement: BlockPlacement::Below(range.start),
16569 height: Some(1),
16570 render: Arc::new({
16571 let rename_editor = rename_editor.clone();
16572 move |cx: &mut BlockContext| {
16573 let mut text_style = cx.editor_style.text.clone();
16574 if let Some(highlight_style) = old_highlight_id
16575 .and_then(|h| h.style(&cx.editor_style.syntax))
16576 {
16577 text_style = text_style.highlight(highlight_style);
16578 }
16579 div()
16580 .block_mouse_except_scroll()
16581 .pl(cx.anchor_x)
16582 .child(EditorElement::new(
16583 &rename_editor,
16584 EditorStyle {
16585 background: cx.theme().system().transparent,
16586 local_player: cx.editor_style.local_player,
16587 text: text_style,
16588 scrollbar_width: cx.editor_style.scrollbar_width,
16589 syntax: cx.editor_style.syntax.clone(),
16590 status: cx.editor_style.status.clone(),
16591 inlay_hints_style: HighlightStyle {
16592 font_weight: Some(FontWeight::BOLD),
16593 ..make_inlay_hints_style(cx.app)
16594 },
16595 edit_prediction_styles: make_suggestion_styles(
16596 cx.app,
16597 ),
16598 ..EditorStyle::default()
16599 },
16600 ))
16601 .into_any_element()
16602 }
16603 }),
16604 priority: 0,
16605 }],
16606 Some(Autoscroll::fit()),
16607 cx,
16608 )[0];
16609 this.pending_rename = Some(RenameState {
16610 range,
16611 old_name,
16612 editor: rename_editor,
16613 block_id,
16614 });
16615 })?;
16616 }
16617
16618 Ok(())
16619 }))
16620 }
16621
16622 pub fn confirm_rename(
16623 &mut self,
16624 _: &ConfirmRename,
16625 window: &mut Window,
16626 cx: &mut Context<Self>,
16627 ) -> Option<Task<Result<()>>> {
16628 let rename = self.take_rename(false, window, cx)?;
16629 let workspace = self.workspace()?.downgrade();
16630 let (buffer, start) = self
16631 .buffer
16632 .read(cx)
16633 .text_anchor_for_position(rename.range.start, cx)?;
16634 let (end_buffer, _) = self
16635 .buffer
16636 .read(cx)
16637 .text_anchor_for_position(rename.range.end, cx)?;
16638 if buffer != end_buffer {
16639 return None;
16640 }
16641
16642 let old_name = rename.old_name;
16643 let new_name = rename.editor.read(cx).text(cx);
16644
16645 let rename = self.semantics_provider.as_ref()?.perform_rename(
16646 &buffer,
16647 start,
16648 new_name.clone(),
16649 cx,
16650 )?;
16651
16652 Some(cx.spawn_in(window, async move |editor, cx| {
16653 let project_transaction = rename.await?;
16654 Self::open_project_transaction(
16655 &editor,
16656 workspace,
16657 project_transaction,
16658 format!("Rename: {} → {}", old_name, new_name),
16659 cx,
16660 )
16661 .await?;
16662
16663 editor.update(cx, |editor, cx| {
16664 editor.refresh_document_highlights(cx);
16665 })?;
16666 Ok(())
16667 }))
16668 }
16669
16670 fn take_rename(
16671 &mut self,
16672 moving_cursor: bool,
16673 window: &mut Window,
16674 cx: &mut Context<Self>,
16675 ) -> Option<RenameState> {
16676 let rename = self.pending_rename.take()?;
16677 if rename.editor.focus_handle(cx).is_focused(window) {
16678 window.focus(&self.focus_handle);
16679 }
16680
16681 self.remove_blocks(
16682 [rename.block_id].into_iter().collect(),
16683 Some(Autoscroll::fit()),
16684 cx,
16685 );
16686 self.clear_highlights::<Rename>(cx);
16687 self.show_local_selections = true;
16688
16689 if moving_cursor {
16690 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16691 editor.selections.newest::<usize>(cx).head()
16692 });
16693
16694 // Update the selection to match the position of the selection inside
16695 // the rename editor.
16696 let snapshot = self.buffer.read(cx).read(cx);
16697 let rename_range = rename.range.to_offset(&snapshot);
16698 let cursor_in_editor = snapshot
16699 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16700 .min(rename_range.end);
16701 drop(snapshot);
16702
16703 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16704 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16705 });
16706 } else {
16707 self.refresh_document_highlights(cx);
16708 }
16709
16710 Some(rename)
16711 }
16712
16713 pub fn pending_rename(&self) -> Option<&RenameState> {
16714 self.pending_rename.as_ref()
16715 }
16716
16717 fn format(
16718 &mut self,
16719 _: &Format,
16720 window: &mut Window,
16721 cx: &mut Context<Self>,
16722 ) -> Option<Task<Result<()>>> {
16723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16724
16725 let project = match &self.project {
16726 Some(project) => project.clone(),
16727 None => return None,
16728 };
16729
16730 Some(self.perform_format(
16731 project,
16732 FormatTrigger::Manual,
16733 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16734 window,
16735 cx,
16736 ))
16737 }
16738
16739 fn format_selections(
16740 &mut self,
16741 _: &FormatSelections,
16742 window: &mut Window,
16743 cx: &mut Context<Self>,
16744 ) -> Option<Task<Result<()>>> {
16745 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16746
16747 let project = match &self.project {
16748 Some(project) => project.clone(),
16749 None => return None,
16750 };
16751
16752 let ranges = self
16753 .selections
16754 .all_adjusted(cx)
16755 .into_iter()
16756 .map(|selection| selection.range())
16757 .collect_vec();
16758
16759 Some(self.perform_format(
16760 project,
16761 FormatTrigger::Manual,
16762 FormatTarget::Ranges(ranges),
16763 window,
16764 cx,
16765 ))
16766 }
16767
16768 fn perform_format(
16769 &mut self,
16770 project: Entity<Project>,
16771 trigger: FormatTrigger,
16772 target: FormatTarget,
16773 window: &mut Window,
16774 cx: &mut Context<Self>,
16775 ) -> Task<Result<()>> {
16776 let buffer = self.buffer.clone();
16777 let (buffers, target) = match target {
16778 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16779 FormatTarget::Ranges(selection_ranges) => {
16780 let multi_buffer = buffer.read(cx);
16781 let snapshot = multi_buffer.read(cx);
16782 let mut buffers = HashSet::default();
16783 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16784 BTreeMap::new();
16785 for selection_range in selection_ranges {
16786 for (buffer, buffer_range, _) in
16787 snapshot.range_to_buffer_ranges(selection_range)
16788 {
16789 let buffer_id = buffer.remote_id();
16790 let start = buffer.anchor_before(buffer_range.start);
16791 let end = buffer.anchor_after(buffer_range.end);
16792 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16793 buffer_id_to_ranges
16794 .entry(buffer_id)
16795 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16796 .or_insert_with(|| vec![start..end]);
16797 }
16798 }
16799 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16800 }
16801 };
16802
16803 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16804 let selections_prev = transaction_id_prev
16805 .and_then(|transaction_id_prev| {
16806 // default to selections as they were after the last edit, if we have them,
16807 // instead of how they are now.
16808 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16809 // will take you back to where you made the last edit, instead of staying where you scrolled
16810 self.selection_history
16811 .transaction(transaction_id_prev)
16812 .map(|t| t.0.clone())
16813 })
16814 .unwrap_or_else(|| self.selections.disjoint_anchors());
16815
16816 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16817 let format = project.update(cx, |project, cx| {
16818 project.format(buffers, target, true, trigger, cx)
16819 });
16820
16821 cx.spawn_in(window, async move |editor, cx| {
16822 let transaction = futures::select_biased! {
16823 transaction = format.log_err().fuse() => transaction,
16824 () = timeout => {
16825 log::warn!("timed out waiting for formatting");
16826 None
16827 }
16828 };
16829
16830 buffer
16831 .update(cx, |buffer, cx| {
16832 if let Some(transaction) = transaction
16833 && !buffer.is_singleton()
16834 {
16835 buffer.push_transaction(&transaction.0, cx);
16836 }
16837 cx.notify();
16838 })
16839 .ok();
16840
16841 if let Some(transaction_id_now) =
16842 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16843 {
16844 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16845 if has_new_transaction {
16846 _ = editor.update(cx, |editor, _| {
16847 editor
16848 .selection_history
16849 .insert_transaction(transaction_id_now, selections_prev);
16850 });
16851 }
16852 }
16853
16854 Ok(())
16855 })
16856 }
16857
16858 fn organize_imports(
16859 &mut self,
16860 _: &OrganizeImports,
16861 window: &mut Window,
16862 cx: &mut Context<Self>,
16863 ) -> Option<Task<Result<()>>> {
16864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16865 let project = match &self.project {
16866 Some(project) => project.clone(),
16867 None => return None,
16868 };
16869 Some(self.perform_code_action_kind(
16870 project,
16871 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16872 window,
16873 cx,
16874 ))
16875 }
16876
16877 fn perform_code_action_kind(
16878 &mut self,
16879 project: Entity<Project>,
16880 kind: CodeActionKind,
16881 window: &mut Window,
16882 cx: &mut Context<Self>,
16883 ) -> Task<Result<()>> {
16884 let buffer = self.buffer.clone();
16885 let buffers = buffer.read(cx).all_buffers();
16886 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16887 let apply_action = project.update(cx, |project, cx| {
16888 project.apply_code_action_kind(buffers, kind, true, cx)
16889 });
16890 cx.spawn_in(window, async move |_, cx| {
16891 let transaction = futures::select_biased! {
16892 () = timeout => {
16893 log::warn!("timed out waiting for executing code action");
16894 None
16895 }
16896 transaction = apply_action.log_err().fuse() => transaction,
16897 };
16898 buffer
16899 .update(cx, |buffer, cx| {
16900 // check if we need this
16901 if let Some(transaction) = transaction
16902 && !buffer.is_singleton()
16903 {
16904 buffer.push_transaction(&transaction.0, cx);
16905 }
16906 cx.notify();
16907 })
16908 .ok();
16909 Ok(())
16910 })
16911 }
16912
16913 pub fn restart_language_server(
16914 &mut self,
16915 _: &RestartLanguageServer,
16916 _: &mut Window,
16917 cx: &mut Context<Self>,
16918 ) {
16919 if let Some(project) = self.project.clone() {
16920 self.buffer.update(cx, |multi_buffer, cx| {
16921 project.update(cx, |project, cx| {
16922 project.restart_language_servers_for_buffers(
16923 multi_buffer.all_buffers().into_iter().collect(),
16924 HashSet::default(),
16925 cx,
16926 );
16927 });
16928 })
16929 }
16930 }
16931
16932 pub fn stop_language_server(
16933 &mut self,
16934 _: &StopLanguageServer,
16935 _: &mut Window,
16936 cx: &mut Context<Self>,
16937 ) {
16938 if let Some(project) = self.project.clone() {
16939 self.buffer.update(cx, |multi_buffer, cx| {
16940 project.update(cx, |project, cx| {
16941 project.stop_language_servers_for_buffers(
16942 multi_buffer.all_buffers().into_iter().collect(),
16943 HashSet::default(),
16944 cx,
16945 );
16946 cx.emit(project::Event::RefreshInlayHints);
16947 });
16948 });
16949 }
16950 }
16951
16952 fn cancel_language_server_work(
16953 workspace: &mut Workspace,
16954 _: &actions::CancelLanguageServerWork,
16955 _: &mut Window,
16956 cx: &mut Context<Workspace>,
16957 ) {
16958 let project = workspace.project();
16959 let buffers = workspace
16960 .active_item(cx)
16961 .and_then(|item| item.act_as::<Editor>(cx))
16962 .map_or(HashSet::default(), |editor| {
16963 editor.read(cx).buffer.read(cx).all_buffers()
16964 });
16965 project.update(cx, |project, cx| {
16966 project.cancel_language_server_work_for_buffers(buffers, cx);
16967 });
16968 }
16969
16970 fn show_character_palette(
16971 &mut self,
16972 _: &ShowCharacterPalette,
16973 window: &mut Window,
16974 _: &mut Context<Self>,
16975 ) {
16976 window.show_character_palette();
16977 }
16978
16979 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16980 if !self.diagnostics_enabled() {
16981 return;
16982 }
16983
16984 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16985 let buffer = self.buffer.read(cx).snapshot(cx);
16986 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16987 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16988 let is_valid = buffer
16989 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16990 .any(|entry| {
16991 entry.diagnostic.is_primary
16992 && !entry.range.is_empty()
16993 && entry.range.start == primary_range_start
16994 && entry.diagnostic.message == active_diagnostics.active_message
16995 });
16996
16997 if !is_valid {
16998 self.dismiss_diagnostics(cx);
16999 }
17000 }
17001 }
17002
17003 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17004 match &self.active_diagnostics {
17005 ActiveDiagnostic::Group(group) => Some(group),
17006 _ => None,
17007 }
17008 }
17009
17010 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17011 if !self.diagnostics_enabled() {
17012 return;
17013 }
17014 self.dismiss_diagnostics(cx);
17015 self.active_diagnostics = ActiveDiagnostic::All;
17016 }
17017
17018 fn activate_diagnostics(
17019 &mut self,
17020 buffer_id: BufferId,
17021 diagnostic: DiagnosticEntry<usize>,
17022 window: &mut Window,
17023 cx: &mut Context<Self>,
17024 ) {
17025 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17026 return;
17027 }
17028 self.dismiss_diagnostics(cx);
17029 let snapshot = self.snapshot(window, cx);
17030 let buffer = self.buffer.read(cx).snapshot(cx);
17031 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17032 return;
17033 };
17034
17035 let diagnostic_group = buffer
17036 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17037 .collect::<Vec<_>>();
17038
17039 let blocks =
17040 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17041
17042 let blocks = self.display_map.update(cx, |display_map, cx| {
17043 display_map.insert_blocks(blocks, cx).into_iter().collect()
17044 });
17045 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17046 active_range: buffer.anchor_before(diagnostic.range.start)
17047 ..buffer.anchor_after(diagnostic.range.end),
17048 active_message: diagnostic.diagnostic.message.clone(),
17049 group_id: diagnostic.diagnostic.group_id,
17050 blocks,
17051 });
17052 cx.notify();
17053 }
17054
17055 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17056 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17057 return;
17058 };
17059
17060 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17061 if let ActiveDiagnostic::Group(group) = prev {
17062 self.display_map.update(cx, |display_map, cx| {
17063 display_map.remove_blocks(group.blocks, cx);
17064 });
17065 cx.notify();
17066 }
17067 }
17068
17069 /// Disable inline diagnostics rendering for this editor.
17070 pub fn disable_inline_diagnostics(&mut self) {
17071 self.inline_diagnostics_enabled = false;
17072 self.inline_diagnostics_update = Task::ready(());
17073 self.inline_diagnostics.clear();
17074 }
17075
17076 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17077 self.diagnostics_enabled = false;
17078 self.dismiss_diagnostics(cx);
17079 self.inline_diagnostics_update = Task::ready(());
17080 self.inline_diagnostics.clear();
17081 }
17082
17083 pub fn diagnostics_enabled(&self) -> bool {
17084 self.diagnostics_enabled && self.mode.is_full()
17085 }
17086
17087 pub fn inline_diagnostics_enabled(&self) -> bool {
17088 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17089 }
17090
17091 pub fn show_inline_diagnostics(&self) -> bool {
17092 self.show_inline_diagnostics
17093 }
17094
17095 pub fn toggle_inline_diagnostics(
17096 &mut self,
17097 _: &ToggleInlineDiagnostics,
17098 window: &mut Window,
17099 cx: &mut Context<Editor>,
17100 ) {
17101 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17102 self.refresh_inline_diagnostics(false, window, cx);
17103 }
17104
17105 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17106 self.diagnostics_max_severity = severity;
17107 self.display_map.update(cx, |display_map, _| {
17108 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17109 });
17110 }
17111
17112 pub fn toggle_diagnostics(
17113 &mut self,
17114 _: &ToggleDiagnostics,
17115 window: &mut Window,
17116 cx: &mut Context<Editor>,
17117 ) {
17118 if !self.diagnostics_enabled() {
17119 return;
17120 }
17121
17122 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17123 EditorSettings::get_global(cx)
17124 .diagnostics_max_severity
17125 .filter(|severity| severity != &DiagnosticSeverity::Off)
17126 .unwrap_or(DiagnosticSeverity::Hint)
17127 } else {
17128 DiagnosticSeverity::Off
17129 };
17130 self.set_max_diagnostics_severity(new_severity, cx);
17131 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17132 self.active_diagnostics = ActiveDiagnostic::None;
17133 self.inline_diagnostics_update = Task::ready(());
17134 self.inline_diagnostics.clear();
17135 } else {
17136 self.refresh_inline_diagnostics(false, window, cx);
17137 }
17138
17139 cx.notify();
17140 }
17141
17142 pub fn toggle_minimap(
17143 &mut self,
17144 _: &ToggleMinimap,
17145 window: &mut Window,
17146 cx: &mut Context<Editor>,
17147 ) {
17148 if self.supports_minimap(cx) {
17149 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17150 }
17151 }
17152
17153 fn refresh_inline_diagnostics(
17154 &mut self,
17155 debounce: bool,
17156 window: &mut Window,
17157 cx: &mut Context<Self>,
17158 ) {
17159 let max_severity = ProjectSettings::get_global(cx)
17160 .diagnostics
17161 .inline
17162 .max_severity
17163 .unwrap_or(self.diagnostics_max_severity);
17164
17165 if !self.inline_diagnostics_enabled()
17166 || !self.show_inline_diagnostics
17167 || max_severity == DiagnosticSeverity::Off
17168 {
17169 self.inline_diagnostics_update = Task::ready(());
17170 self.inline_diagnostics.clear();
17171 return;
17172 }
17173
17174 let debounce_ms = ProjectSettings::get_global(cx)
17175 .diagnostics
17176 .inline
17177 .update_debounce_ms;
17178 let debounce = if debounce && debounce_ms > 0 {
17179 Some(Duration::from_millis(debounce_ms))
17180 } else {
17181 None
17182 };
17183 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17184 if let Some(debounce) = debounce {
17185 cx.background_executor().timer(debounce).await;
17186 }
17187 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17188 editor
17189 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17190 .ok()
17191 }) else {
17192 return;
17193 };
17194
17195 let new_inline_diagnostics = cx
17196 .background_spawn(async move {
17197 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17198 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17199 let message = diagnostic_entry
17200 .diagnostic
17201 .message
17202 .split_once('\n')
17203 .map(|(line, _)| line)
17204 .map(SharedString::new)
17205 .unwrap_or_else(|| {
17206 SharedString::from(diagnostic_entry.diagnostic.message)
17207 });
17208 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17209 let (Ok(i) | Err(i)) = inline_diagnostics
17210 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17211 inline_diagnostics.insert(
17212 i,
17213 (
17214 start_anchor,
17215 InlineDiagnostic {
17216 message,
17217 group_id: diagnostic_entry.diagnostic.group_id,
17218 start: diagnostic_entry.range.start.to_point(&snapshot),
17219 is_primary: diagnostic_entry.diagnostic.is_primary,
17220 severity: diagnostic_entry.diagnostic.severity,
17221 },
17222 ),
17223 );
17224 }
17225 inline_diagnostics
17226 })
17227 .await;
17228
17229 editor
17230 .update(cx, |editor, cx| {
17231 editor.inline_diagnostics = new_inline_diagnostics;
17232 cx.notify();
17233 })
17234 .ok();
17235 });
17236 }
17237
17238 fn pull_diagnostics(
17239 &mut self,
17240 buffer_id: Option<BufferId>,
17241 window: &Window,
17242 cx: &mut Context<Self>,
17243 ) -> Option<()> {
17244 if !self.mode().is_full() {
17245 return None;
17246 }
17247 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17248 .diagnostics
17249 .lsp_pull_diagnostics;
17250 if !pull_diagnostics_settings.enabled {
17251 return None;
17252 }
17253 let project = self.project()?.downgrade();
17254 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17255 let mut buffers = self.buffer.read(cx).all_buffers();
17256 if let Some(buffer_id) = buffer_id {
17257 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17258 }
17259
17260 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17261 cx.background_executor().timer(debounce).await;
17262
17263 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17264 buffers
17265 .into_iter()
17266 .filter_map(|buffer| {
17267 project
17268 .update(cx, |project, cx| {
17269 project.lsp_store().update(cx, |lsp_store, cx| {
17270 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17271 })
17272 })
17273 .ok()
17274 })
17275 .collect::<FuturesUnordered<_>>()
17276 }) else {
17277 return;
17278 };
17279
17280 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17281 match pull_task {
17282 Ok(()) => {
17283 if editor
17284 .update_in(cx, |editor, window, cx| {
17285 editor.update_diagnostics_state(window, cx);
17286 })
17287 .is_err()
17288 {
17289 return;
17290 }
17291 }
17292 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17293 }
17294 }
17295 });
17296
17297 Some(())
17298 }
17299
17300 pub fn set_selections_from_remote(
17301 &mut self,
17302 selections: Vec<Selection<Anchor>>,
17303 pending_selection: Option<Selection<Anchor>>,
17304 window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) {
17307 let old_cursor_position = self.selections.newest_anchor().head();
17308 self.selections.change_with(cx, |s| {
17309 s.select_anchors(selections);
17310 if let Some(pending_selection) = pending_selection {
17311 s.set_pending(pending_selection, SelectMode::Character);
17312 } else {
17313 s.clear_pending();
17314 }
17315 });
17316 self.selections_did_change(
17317 false,
17318 &old_cursor_position,
17319 SelectionEffects::default(),
17320 window,
17321 cx,
17322 );
17323 }
17324
17325 pub fn transact(
17326 &mut self,
17327 window: &mut Window,
17328 cx: &mut Context<Self>,
17329 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17330 ) -> Option<TransactionId> {
17331 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17332 this.start_transaction_at(Instant::now(), window, cx);
17333 update(this, window, cx);
17334 this.end_transaction_at(Instant::now(), cx)
17335 })
17336 }
17337
17338 pub fn start_transaction_at(
17339 &mut self,
17340 now: Instant,
17341 window: &mut Window,
17342 cx: &mut Context<Self>,
17343 ) -> Option<TransactionId> {
17344 self.end_selection(window, cx);
17345 if let Some(tx_id) = self
17346 .buffer
17347 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17348 {
17349 self.selection_history
17350 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17351 cx.emit(EditorEvent::TransactionBegun {
17352 transaction_id: tx_id,
17353 });
17354 Some(tx_id)
17355 } else {
17356 None
17357 }
17358 }
17359
17360 pub fn end_transaction_at(
17361 &mut self,
17362 now: Instant,
17363 cx: &mut Context<Self>,
17364 ) -> Option<TransactionId> {
17365 if let Some(transaction_id) = self
17366 .buffer
17367 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17368 {
17369 if let Some((_, end_selections)) =
17370 self.selection_history.transaction_mut(transaction_id)
17371 {
17372 *end_selections = Some(self.selections.disjoint_anchors());
17373 } else {
17374 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17375 }
17376
17377 cx.emit(EditorEvent::Edited { transaction_id });
17378 Some(transaction_id)
17379 } else {
17380 None
17381 }
17382 }
17383
17384 pub fn modify_transaction_selection_history(
17385 &mut self,
17386 transaction_id: TransactionId,
17387 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17388 ) -> bool {
17389 self.selection_history
17390 .transaction_mut(transaction_id)
17391 .map(modify)
17392 .is_some()
17393 }
17394
17395 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17396 if self.selection_mark_mode {
17397 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17398 s.move_with(|_, sel| {
17399 sel.collapse_to(sel.head(), SelectionGoal::None);
17400 });
17401 })
17402 }
17403 self.selection_mark_mode = true;
17404 cx.notify();
17405 }
17406
17407 pub fn swap_selection_ends(
17408 &mut self,
17409 _: &actions::SwapSelectionEnds,
17410 window: &mut Window,
17411 cx: &mut Context<Self>,
17412 ) {
17413 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17414 s.move_with(|_, sel| {
17415 if sel.start != sel.end {
17416 sel.reversed = !sel.reversed
17417 }
17418 });
17419 });
17420 self.request_autoscroll(Autoscroll::newest(), cx);
17421 cx.notify();
17422 }
17423
17424 pub fn toggle_focus(
17425 workspace: &mut Workspace,
17426 _: &actions::ToggleFocus,
17427 window: &mut Window,
17428 cx: &mut Context<Workspace>,
17429 ) {
17430 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17431 return;
17432 };
17433 workspace.activate_item(&item, true, true, window, cx);
17434 }
17435
17436 pub fn toggle_fold(
17437 &mut self,
17438 _: &actions::ToggleFold,
17439 window: &mut Window,
17440 cx: &mut Context<Self>,
17441 ) {
17442 if self.is_singleton(cx) {
17443 let selection = self.selections.newest::<Point>(cx);
17444
17445 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17446 let range = if selection.is_empty() {
17447 let point = selection.head().to_display_point(&display_map);
17448 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17449 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17450 .to_point(&display_map);
17451 start..end
17452 } else {
17453 selection.range()
17454 };
17455 if display_map.folds_in_range(range).next().is_some() {
17456 self.unfold_lines(&Default::default(), window, cx)
17457 } else {
17458 self.fold(&Default::default(), window, cx)
17459 }
17460 } else {
17461 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17462 let buffer_ids: HashSet<_> = self
17463 .selections
17464 .disjoint_anchor_ranges()
17465 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17466 .collect();
17467
17468 let should_unfold = buffer_ids
17469 .iter()
17470 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17471
17472 for buffer_id in buffer_ids {
17473 if should_unfold {
17474 self.unfold_buffer(buffer_id, cx);
17475 } else {
17476 self.fold_buffer(buffer_id, cx);
17477 }
17478 }
17479 }
17480 }
17481
17482 pub fn toggle_fold_recursive(
17483 &mut self,
17484 _: &actions::ToggleFoldRecursive,
17485 window: &mut Window,
17486 cx: &mut Context<Self>,
17487 ) {
17488 let selection = self.selections.newest::<Point>(cx);
17489
17490 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17491 let range = if selection.is_empty() {
17492 let point = selection.head().to_display_point(&display_map);
17493 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17494 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17495 .to_point(&display_map);
17496 start..end
17497 } else {
17498 selection.range()
17499 };
17500 if display_map.folds_in_range(range).next().is_some() {
17501 self.unfold_recursive(&Default::default(), window, cx)
17502 } else {
17503 self.fold_recursive(&Default::default(), window, cx)
17504 }
17505 }
17506
17507 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17508 if self.is_singleton(cx) {
17509 let mut to_fold = Vec::new();
17510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17511 let selections = self.selections.all_adjusted(cx);
17512
17513 for selection in selections {
17514 let range = selection.range().sorted();
17515 let buffer_start_row = range.start.row;
17516
17517 if range.start.row != range.end.row {
17518 let mut found = false;
17519 let mut row = range.start.row;
17520 while row <= range.end.row {
17521 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17522 {
17523 found = true;
17524 row = crease.range().end.row + 1;
17525 to_fold.push(crease);
17526 } else {
17527 row += 1
17528 }
17529 }
17530 if found {
17531 continue;
17532 }
17533 }
17534
17535 for row in (0..=range.start.row).rev() {
17536 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17537 && crease.range().end.row >= buffer_start_row
17538 {
17539 to_fold.push(crease);
17540 if row <= range.start.row {
17541 break;
17542 }
17543 }
17544 }
17545 }
17546
17547 self.fold_creases(to_fold, true, window, cx);
17548 } else {
17549 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17550 let buffer_ids = self
17551 .selections
17552 .disjoint_anchor_ranges()
17553 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17554 .collect::<HashSet<_>>();
17555 for buffer_id in buffer_ids {
17556 self.fold_buffer(buffer_id, cx);
17557 }
17558 }
17559 }
17560
17561 pub fn toggle_fold_all(
17562 &mut self,
17563 _: &actions::ToggleFoldAll,
17564 window: &mut Window,
17565 cx: &mut Context<Self>,
17566 ) {
17567 if self.buffer.read(cx).is_singleton() {
17568 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17569 let has_folds = display_map
17570 .folds_in_range(0..display_map.buffer_snapshot.len())
17571 .next()
17572 .is_some();
17573
17574 if has_folds {
17575 self.unfold_all(&actions::UnfoldAll, window, cx);
17576 } else {
17577 self.fold_all(&actions::FoldAll, window, cx);
17578 }
17579 } else {
17580 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17581 let should_unfold = buffer_ids
17582 .iter()
17583 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17584
17585 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17586 editor
17587 .update_in(cx, |editor, _, cx| {
17588 for buffer_id in buffer_ids {
17589 if should_unfold {
17590 editor.unfold_buffer(buffer_id, cx);
17591 } else {
17592 editor.fold_buffer(buffer_id, cx);
17593 }
17594 }
17595 })
17596 .ok();
17597 });
17598 }
17599 }
17600
17601 fn fold_at_level(
17602 &mut self,
17603 fold_at: &FoldAtLevel,
17604 window: &mut Window,
17605 cx: &mut Context<Self>,
17606 ) {
17607 if !self.buffer.read(cx).is_singleton() {
17608 return;
17609 }
17610
17611 let fold_at_level = fold_at.0;
17612 let snapshot = self.buffer.read(cx).snapshot(cx);
17613 let mut to_fold = Vec::new();
17614 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17615
17616 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17617 while start_row < end_row {
17618 match self
17619 .snapshot(window, cx)
17620 .crease_for_buffer_row(MultiBufferRow(start_row))
17621 {
17622 Some(crease) => {
17623 let nested_start_row = crease.range().start.row + 1;
17624 let nested_end_row = crease.range().end.row;
17625
17626 if current_level < fold_at_level {
17627 stack.push((nested_start_row, nested_end_row, current_level + 1));
17628 } else if current_level == fold_at_level {
17629 to_fold.push(crease);
17630 }
17631
17632 start_row = nested_end_row + 1;
17633 }
17634 None => start_row += 1,
17635 }
17636 }
17637 }
17638
17639 self.fold_creases(to_fold, true, window, cx);
17640 }
17641
17642 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17643 if self.buffer.read(cx).is_singleton() {
17644 let mut fold_ranges = Vec::new();
17645 let snapshot = self.buffer.read(cx).snapshot(cx);
17646
17647 for row in 0..snapshot.max_row().0 {
17648 if let Some(foldable_range) = self
17649 .snapshot(window, cx)
17650 .crease_for_buffer_row(MultiBufferRow(row))
17651 {
17652 fold_ranges.push(foldable_range);
17653 }
17654 }
17655
17656 self.fold_creases(fold_ranges, true, window, cx);
17657 } else {
17658 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17659 editor
17660 .update_in(cx, |editor, _, cx| {
17661 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17662 editor.fold_buffer(buffer_id, cx);
17663 }
17664 })
17665 .ok();
17666 });
17667 }
17668 }
17669
17670 pub fn fold_function_bodies(
17671 &mut self,
17672 _: &actions::FoldFunctionBodies,
17673 window: &mut Window,
17674 cx: &mut Context<Self>,
17675 ) {
17676 let snapshot = self.buffer.read(cx).snapshot(cx);
17677
17678 let ranges = snapshot
17679 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17680 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17681 .collect::<Vec<_>>();
17682
17683 let creases = ranges
17684 .into_iter()
17685 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17686 .collect();
17687
17688 self.fold_creases(creases, true, window, cx);
17689 }
17690
17691 pub fn fold_recursive(
17692 &mut self,
17693 _: &actions::FoldRecursive,
17694 window: &mut Window,
17695 cx: &mut Context<Self>,
17696 ) {
17697 let mut to_fold = Vec::new();
17698 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17699 let selections = self.selections.all_adjusted(cx);
17700
17701 for selection in selections {
17702 let range = selection.range().sorted();
17703 let buffer_start_row = range.start.row;
17704
17705 if range.start.row != range.end.row {
17706 let mut found = false;
17707 for row in range.start.row..=range.end.row {
17708 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17709 found = true;
17710 to_fold.push(crease);
17711 }
17712 }
17713 if found {
17714 continue;
17715 }
17716 }
17717
17718 for row in (0..=range.start.row).rev() {
17719 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17720 if crease.range().end.row >= buffer_start_row {
17721 to_fold.push(crease);
17722 } else {
17723 break;
17724 }
17725 }
17726 }
17727 }
17728
17729 self.fold_creases(to_fold, true, window, cx);
17730 }
17731
17732 pub fn fold_at(
17733 &mut self,
17734 buffer_row: MultiBufferRow,
17735 window: &mut Window,
17736 cx: &mut Context<Self>,
17737 ) {
17738 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17739
17740 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17741 let autoscroll = self
17742 .selections
17743 .all::<Point>(cx)
17744 .iter()
17745 .any(|selection| crease.range().overlaps(&selection.range()));
17746
17747 self.fold_creases(vec![crease], autoscroll, window, cx);
17748 }
17749 }
17750
17751 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17752 if self.is_singleton(cx) {
17753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17754 let buffer = &display_map.buffer_snapshot;
17755 let selections = self.selections.all::<Point>(cx);
17756 let ranges = selections
17757 .iter()
17758 .map(|s| {
17759 let range = s.display_range(&display_map).sorted();
17760 let mut start = range.start.to_point(&display_map);
17761 let mut end = range.end.to_point(&display_map);
17762 start.column = 0;
17763 end.column = buffer.line_len(MultiBufferRow(end.row));
17764 start..end
17765 })
17766 .collect::<Vec<_>>();
17767
17768 self.unfold_ranges(&ranges, true, true, cx);
17769 } else {
17770 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17771 let buffer_ids = self
17772 .selections
17773 .disjoint_anchor_ranges()
17774 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17775 .collect::<HashSet<_>>();
17776 for buffer_id in buffer_ids {
17777 self.unfold_buffer(buffer_id, cx);
17778 }
17779 }
17780 }
17781
17782 pub fn unfold_recursive(
17783 &mut self,
17784 _: &UnfoldRecursive,
17785 _window: &mut Window,
17786 cx: &mut Context<Self>,
17787 ) {
17788 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17789 let selections = self.selections.all::<Point>(cx);
17790 let ranges = selections
17791 .iter()
17792 .map(|s| {
17793 let mut range = s.display_range(&display_map).sorted();
17794 *range.start.column_mut() = 0;
17795 *range.end.column_mut() = display_map.line_len(range.end.row());
17796 let start = range.start.to_point(&display_map);
17797 let end = range.end.to_point(&display_map);
17798 start..end
17799 })
17800 .collect::<Vec<_>>();
17801
17802 self.unfold_ranges(&ranges, true, true, cx);
17803 }
17804
17805 pub fn unfold_at(
17806 &mut self,
17807 buffer_row: MultiBufferRow,
17808 _window: &mut Window,
17809 cx: &mut Context<Self>,
17810 ) {
17811 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17812
17813 let intersection_range = Point::new(buffer_row.0, 0)
17814 ..Point::new(
17815 buffer_row.0,
17816 display_map.buffer_snapshot.line_len(buffer_row),
17817 );
17818
17819 let autoscroll = self
17820 .selections
17821 .all::<Point>(cx)
17822 .iter()
17823 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17824
17825 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17826 }
17827
17828 pub fn unfold_all(
17829 &mut self,
17830 _: &actions::UnfoldAll,
17831 _window: &mut Window,
17832 cx: &mut Context<Self>,
17833 ) {
17834 if self.buffer.read(cx).is_singleton() {
17835 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17836 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17837 } else {
17838 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17839 editor
17840 .update(cx, |editor, cx| {
17841 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17842 editor.unfold_buffer(buffer_id, cx);
17843 }
17844 })
17845 .ok();
17846 });
17847 }
17848 }
17849
17850 pub fn fold_selected_ranges(
17851 &mut self,
17852 _: &FoldSelectedRanges,
17853 window: &mut Window,
17854 cx: &mut Context<Self>,
17855 ) {
17856 let selections = self.selections.all_adjusted(cx);
17857 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17858 let ranges = selections
17859 .into_iter()
17860 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17861 .collect::<Vec<_>>();
17862 self.fold_creases(ranges, true, window, cx);
17863 }
17864
17865 pub fn fold_ranges<T: ToOffset + Clone>(
17866 &mut self,
17867 ranges: Vec<Range<T>>,
17868 auto_scroll: bool,
17869 window: &mut Window,
17870 cx: &mut Context<Self>,
17871 ) {
17872 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17873 let ranges = ranges
17874 .into_iter()
17875 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17876 .collect::<Vec<_>>();
17877 self.fold_creases(ranges, auto_scroll, window, cx);
17878 }
17879
17880 pub fn fold_creases<T: ToOffset + Clone>(
17881 &mut self,
17882 creases: Vec<Crease<T>>,
17883 auto_scroll: bool,
17884 _window: &mut Window,
17885 cx: &mut Context<Self>,
17886 ) {
17887 if creases.is_empty() {
17888 return;
17889 }
17890
17891 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17892
17893 if auto_scroll {
17894 self.request_autoscroll(Autoscroll::fit(), cx);
17895 }
17896
17897 cx.notify();
17898
17899 self.scrollbar_marker_state.dirty = true;
17900 self.folds_did_change(cx);
17901 }
17902
17903 /// Removes any folds whose ranges intersect any of the given ranges.
17904 pub fn unfold_ranges<T: ToOffset + Clone>(
17905 &mut self,
17906 ranges: &[Range<T>],
17907 inclusive: bool,
17908 auto_scroll: bool,
17909 cx: &mut Context<Self>,
17910 ) {
17911 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17912 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17913 });
17914 self.folds_did_change(cx);
17915 }
17916
17917 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17918 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17919 return;
17920 }
17921 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17922 self.display_map.update(cx, |display_map, cx| {
17923 display_map.fold_buffers([buffer_id], cx)
17924 });
17925 cx.emit(EditorEvent::BufferFoldToggled {
17926 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17927 folded: true,
17928 });
17929 cx.notify();
17930 }
17931
17932 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17933 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17934 return;
17935 }
17936 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17937 self.display_map.update(cx, |display_map, cx| {
17938 display_map.unfold_buffers([buffer_id], cx);
17939 });
17940 cx.emit(EditorEvent::BufferFoldToggled {
17941 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17942 folded: false,
17943 });
17944 cx.notify();
17945 }
17946
17947 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17948 self.display_map.read(cx).is_buffer_folded(buffer)
17949 }
17950
17951 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17952 self.display_map.read(cx).folded_buffers()
17953 }
17954
17955 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17956 self.display_map.update(cx, |display_map, cx| {
17957 display_map.disable_header_for_buffer(buffer_id, cx);
17958 });
17959 cx.notify();
17960 }
17961
17962 /// Removes any folds with the given ranges.
17963 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17964 &mut self,
17965 ranges: &[Range<T>],
17966 type_id: TypeId,
17967 auto_scroll: bool,
17968 cx: &mut Context<Self>,
17969 ) {
17970 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17971 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17972 });
17973 self.folds_did_change(cx);
17974 }
17975
17976 fn remove_folds_with<T: ToOffset + Clone>(
17977 &mut self,
17978 ranges: &[Range<T>],
17979 auto_scroll: bool,
17980 cx: &mut Context<Self>,
17981 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17982 ) {
17983 if ranges.is_empty() {
17984 return;
17985 }
17986
17987 let mut buffers_affected = HashSet::default();
17988 let multi_buffer = self.buffer().read(cx);
17989 for range in ranges {
17990 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17991 buffers_affected.insert(buffer.read(cx).remote_id());
17992 };
17993 }
17994
17995 self.display_map.update(cx, update);
17996
17997 if auto_scroll {
17998 self.request_autoscroll(Autoscroll::fit(), cx);
17999 }
18000
18001 cx.notify();
18002 self.scrollbar_marker_state.dirty = true;
18003 self.active_indent_guides_state.dirty = true;
18004 }
18005
18006 pub fn update_renderer_widths(
18007 &mut self,
18008 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18009 cx: &mut Context<Self>,
18010 ) -> bool {
18011 self.display_map
18012 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18013 }
18014
18015 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18016 self.display_map.read(cx).fold_placeholder.clone()
18017 }
18018
18019 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18020 self.buffer.update(cx, |buffer, cx| {
18021 buffer.set_all_diff_hunks_expanded(cx);
18022 });
18023 }
18024
18025 pub fn expand_all_diff_hunks(
18026 &mut self,
18027 _: &ExpandAllDiffHunks,
18028 _window: &mut Window,
18029 cx: &mut Context<Self>,
18030 ) {
18031 self.buffer.update(cx, |buffer, cx| {
18032 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18033 });
18034 }
18035
18036 pub fn toggle_selected_diff_hunks(
18037 &mut self,
18038 _: &ToggleSelectedDiffHunks,
18039 _window: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) {
18042 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18043 self.toggle_diff_hunks_in_ranges(ranges, cx);
18044 }
18045
18046 pub fn diff_hunks_in_ranges<'a>(
18047 &'a self,
18048 ranges: &'a [Range<Anchor>],
18049 buffer: &'a MultiBufferSnapshot,
18050 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18051 ranges.iter().flat_map(move |range| {
18052 let end_excerpt_id = range.end.excerpt_id;
18053 let range = range.to_point(buffer);
18054 let mut peek_end = range.end;
18055 if range.end.row < buffer.max_row().0 {
18056 peek_end = Point::new(range.end.row + 1, 0);
18057 }
18058 buffer
18059 .diff_hunks_in_range(range.start..peek_end)
18060 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18061 })
18062 }
18063
18064 pub fn has_stageable_diff_hunks_in_ranges(
18065 &self,
18066 ranges: &[Range<Anchor>],
18067 snapshot: &MultiBufferSnapshot,
18068 ) -> bool {
18069 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18070 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18071 }
18072
18073 pub fn toggle_staged_selected_diff_hunks(
18074 &mut self,
18075 _: &::git::ToggleStaged,
18076 _: &mut Window,
18077 cx: &mut Context<Self>,
18078 ) {
18079 let snapshot = self.buffer.read(cx).snapshot(cx);
18080 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18081 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18082 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18083 }
18084
18085 pub fn set_render_diff_hunk_controls(
18086 &mut self,
18087 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18088 cx: &mut Context<Self>,
18089 ) {
18090 self.render_diff_hunk_controls = render_diff_hunk_controls;
18091 cx.notify();
18092 }
18093
18094 pub fn stage_and_next(
18095 &mut self,
18096 _: &::git::StageAndNext,
18097 window: &mut Window,
18098 cx: &mut Context<Self>,
18099 ) {
18100 self.do_stage_or_unstage_and_next(true, window, cx);
18101 }
18102
18103 pub fn unstage_and_next(
18104 &mut self,
18105 _: &::git::UnstageAndNext,
18106 window: &mut Window,
18107 cx: &mut Context<Self>,
18108 ) {
18109 self.do_stage_or_unstage_and_next(false, window, cx);
18110 }
18111
18112 pub fn stage_or_unstage_diff_hunks(
18113 &mut self,
18114 stage: bool,
18115 ranges: Vec<Range<Anchor>>,
18116 cx: &mut Context<Self>,
18117 ) {
18118 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18119 cx.spawn(async move |this, cx| {
18120 task.await?;
18121 this.update(cx, |this, cx| {
18122 let snapshot = this.buffer.read(cx).snapshot(cx);
18123 let chunk_by = this
18124 .diff_hunks_in_ranges(&ranges, &snapshot)
18125 .chunk_by(|hunk| hunk.buffer_id);
18126 for (buffer_id, hunks) in &chunk_by {
18127 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18128 }
18129 })
18130 })
18131 .detach_and_log_err(cx);
18132 }
18133
18134 fn save_buffers_for_ranges_if_needed(
18135 &mut self,
18136 ranges: &[Range<Anchor>],
18137 cx: &mut Context<Editor>,
18138 ) -> Task<Result<()>> {
18139 let multibuffer = self.buffer.read(cx);
18140 let snapshot = multibuffer.read(cx);
18141 let buffer_ids: HashSet<_> = ranges
18142 .iter()
18143 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18144 .collect();
18145 drop(snapshot);
18146
18147 let mut buffers = HashSet::default();
18148 for buffer_id in buffer_ids {
18149 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18150 let buffer = buffer_entity.read(cx);
18151 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18152 {
18153 buffers.insert(buffer_entity);
18154 }
18155 }
18156 }
18157
18158 if let Some(project) = &self.project {
18159 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18160 } else {
18161 Task::ready(Ok(()))
18162 }
18163 }
18164
18165 fn do_stage_or_unstage_and_next(
18166 &mut self,
18167 stage: bool,
18168 window: &mut Window,
18169 cx: &mut Context<Self>,
18170 ) {
18171 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18172
18173 if ranges.iter().any(|range| range.start != range.end) {
18174 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18175 return;
18176 }
18177
18178 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18179 let snapshot = self.snapshot(window, cx);
18180 let position = self.selections.newest::<Point>(cx).head();
18181 let mut row = snapshot
18182 .buffer_snapshot
18183 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18184 .find(|hunk| hunk.row_range.start.0 > position.row)
18185 .map(|hunk| hunk.row_range.start);
18186
18187 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18188 // Outside of the project diff editor, wrap around to the beginning.
18189 if !all_diff_hunks_expanded {
18190 row = row.or_else(|| {
18191 snapshot
18192 .buffer_snapshot
18193 .diff_hunks_in_range(Point::zero()..position)
18194 .find(|hunk| hunk.row_range.end.0 < position.row)
18195 .map(|hunk| hunk.row_range.start)
18196 });
18197 }
18198
18199 if let Some(row) = row {
18200 let destination = Point::new(row.0, 0);
18201 let autoscroll = Autoscroll::center();
18202
18203 self.unfold_ranges(&[destination..destination], false, false, cx);
18204 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18205 s.select_ranges([destination..destination]);
18206 });
18207 }
18208 }
18209
18210 fn do_stage_or_unstage(
18211 &self,
18212 stage: bool,
18213 buffer_id: BufferId,
18214 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18215 cx: &mut App,
18216 ) -> Option<()> {
18217 let project = self.project()?;
18218 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18219 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18220 let buffer_snapshot = buffer.read(cx).snapshot();
18221 let file_exists = buffer_snapshot
18222 .file()
18223 .is_some_and(|file| file.disk_state().exists());
18224 diff.update(cx, |diff, cx| {
18225 diff.stage_or_unstage_hunks(
18226 stage,
18227 &hunks
18228 .map(|hunk| buffer_diff::DiffHunk {
18229 buffer_range: hunk.buffer_range,
18230 diff_base_byte_range: hunk.diff_base_byte_range,
18231 secondary_status: hunk.secondary_status,
18232 range: Point::zero()..Point::zero(), // unused
18233 })
18234 .collect::<Vec<_>>(),
18235 &buffer_snapshot,
18236 file_exists,
18237 cx,
18238 )
18239 });
18240 None
18241 }
18242
18243 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18244 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18245 self.buffer
18246 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18247 }
18248
18249 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18250 self.buffer.update(cx, |buffer, cx| {
18251 let ranges = vec![Anchor::min()..Anchor::max()];
18252 if !buffer.all_diff_hunks_expanded()
18253 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18254 {
18255 buffer.collapse_diff_hunks(ranges, cx);
18256 true
18257 } else {
18258 false
18259 }
18260 })
18261 }
18262
18263 fn toggle_diff_hunks_in_ranges(
18264 &mut self,
18265 ranges: Vec<Range<Anchor>>,
18266 cx: &mut Context<Editor>,
18267 ) {
18268 self.buffer.update(cx, |buffer, cx| {
18269 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18270 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18271 })
18272 }
18273
18274 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18275 self.buffer.update(cx, |buffer, cx| {
18276 let snapshot = buffer.snapshot(cx);
18277 let excerpt_id = range.end.excerpt_id;
18278 let point_range = range.to_point(&snapshot);
18279 let expand = !buffer.single_hunk_is_expanded(range, cx);
18280 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18281 })
18282 }
18283
18284 pub(crate) fn apply_all_diff_hunks(
18285 &mut self,
18286 _: &ApplyAllDiffHunks,
18287 window: &mut Window,
18288 cx: &mut Context<Self>,
18289 ) {
18290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18291
18292 let buffers = self.buffer.read(cx).all_buffers();
18293 for branch_buffer in buffers {
18294 branch_buffer.update(cx, |branch_buffer, cx| {
18295 branch_buffer.merge_into_base(Vec::new(), cx);
18296 });
18297 }
18298
18299 if let Some(project) = self.project.clone() {
18300 self.save(
18301 SaveOptions {
18302 format: true,
18303 autosave: false,
18304 },
18305 project,
18306 window,
18307 cx,
18308 )
18309 .detach_and_log_err(cx);
18310 }
18311 }
18312
18313 pub(crate) fn apply_selected_diff_hunks(
18314 &mut self,
18315 _: &ApplyDiffHunk,
18316 window: &mut Window,
18317 cx: &mut Context<Self>,
18318 ) {
18319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18320 let snapshot = self.snapshot(window, cx);
18321 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18322 let mut ranges_by_buffer = HashMap::default();
18323 self.transact(window, cx, |editor, _window, cx| {
18324 for hunk in hunks {
18325 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18326 ranges_by_buffer
18327 .entry(buffer.clone())
18328 .or_insert_with(Vec::new)
18329 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18330 }
18331 }
18332
18333 for (buffer, ranges) in ranges_by_buffer {
18334 buffer.update(cx, |buffer, cx| {
18335 buffer.merge_into_base(ranges, cx);
18336 });
18337 }
18338 });
18339
18340 if let Some(project) = self.project.clone() {
18341 self.save(
18342 SaveOptions {
18343 format: true,
18344 autosave: false,
18345 },
18346 project,
18347 window,
18348 cx,
18349 )
18350 .detach_and_log_err(cx);
18351 }
18352 }
18353
18354 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18355 if hovered != self.gutter_hovered {
18356 self.gutter_hovered = hovered;
18357 cx.notify();
18358 }
18359 }
18360
18361 pub fn insert_blocks(
18362 &mut self,
18363 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18364 autoscroll: Option<Autoscroll>,
18365 cx: &mut Context<Self>,
18366 ) -> Vec<CustomBlockId> {
18367 let blocks = self
18368 .display_map
18369 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18370 if let Some(autoscroll) = autoscroll {
18371 self.request_autoscroll(autoscroll, cx);
18372 }
18373 cx.notify();
18374 blocks
18375 }
18376
18377 pub fn resize_blocks(
18378 &mut self,
18379 heights: HashMap<CustomBlockId, u32>,
18380 autoscroll: Option<Autoscroll>,
18381 cx: &mut Context<Self>,
18382 ) {
18383 self.display_map
18384 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18385 if let Some(autoscroll) = autoscroll {
18386 self.request_autoscroll(autoscroll, cx);
18387 }
18388 cx.notify();
18389 }
18390
18391 pub fn replace_blocks(
18392 &mut self,
18393 renderers: HashMap<CustomBlockId, RenderBlock>,
18394 autoscroll: Option<Autoscroll>,
18395 cx: &mut Context<Self>,
18396 ) {
18397 self.display_map
18398 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18399 if let Some(autoscroll) = autoscroll {
18400 self.request_autoscroll(autoscroll, cx);
18401 }
18402 cx.notify();
18403 }
18404
18405 pub fn remove_blocks(
18406 &mut self,
18407 block_ids: HashSet<CustomBlockId>,
18408 autoscroll: Option<Autoscroll>,
18409 cx: &mut Context<Self>,
18410 ) {
18411 self.display_map.update(cx, |display_map, cx| {
18412 display_map.remove_blocks(block_ids, cx)
18413 });
18414 if let Some(autoscroll) = autoscroll {
18415 self.request_autoscroll(autoscroll, cx);
18416 }
18417 cx.notify();
18418 }
18419
18420 pub fn row_for_block(
18421 &self,
18422 block_id: CustomBlockId,
18423 cx: &mut Context<Self>,
18424 ) -> Option<DisplayRow> {
18425 self.display_map
18426 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18427 }
18428
18429 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18430 self.focused_block = Some(focused_block);
18431 }
18432
18433 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18434 self.focused_block.take()
18435 }
18436
18437 pub fn insert_creases(
18438 &mut self,
18439 creases: impl IntoIterator<Item = Crease<Anchor>>,
18440 cx: &mut Context<Self>,
18441 ) -> Vec<CreaseId> {
18442 self.display_map
18443 .update(cx, |map, cx| map.insert_creases(creases, cx))
18444 }
18445
18446 pub fn remove_creases(
18447 &mut self,
18448 ids: impl IntoIterator<Item = CreaseId>,
18449 cx: &mut Context<Self>,
18450 ) -> Vec<(CreaseId, Range<Anchor>)> {
18451 self.display_map
18452 .update(cx, |map, cx| map.remove_creases(ids, cx))
18453 }
18454
18455 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18456 self.display_map
18457 .update(cx, |map, cx| map.snapshot(cx))
18458 .longest_row()
18459 }
18460
18461 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18462 self.display_map
18463 .update(cx, |map, cx| map.snapshot(cx))
18464 .max_point()
18465 }
18466
18467 pub fn text(&self, cx: &App) -> String {
18468 self.buffer.read(cx).read(cx).text()
18469 }
18470
18471 pub fn is_empty(&self, cx: &App) -> bool {
18472 self.buffer.read(cx).read(cx).is_empty()
18473 }
18474
18475 pub fn text_option(&self, cx: &App) -> Option<String> {
18476 let text = self.text(cx);
18477 let text = text.trim();
18478
18479 if text.is_empty() {
18480 return None;
18481 }
18482
18483 Some(text.to_string())
18484 }
18485
18486 pub fn set_text(
18487 &mut self,
18488 text: impl Into<Arc<str>>,
18489 window: &mut Window,
18490 cx: &mut Context<Self>,
18491 ) {
18492 self.transact(window, cx, |this, _, cx| {
18493 this.buffer
18494 .read(cx)
18495 .as_singleton()
18496 .expect("you can only call set_text on editors for singleton buffers")
18497 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18498 });
18499 }
18500
18501 pub fn display_text(&self, cx: &mut App) -> String {
18502 self.display_map
18503 .update(cx, |map, cx| map.snapshot(cx))
18504 .text()
18505 }
18506
18507 fn create_minimap(
18508 &self,
18509 minimap_settings: MinimapSettings,
18510 window: &mut Window,
18511 cx: &mut Context<Self>,
18512 ) -> Option<Entity<Self>> {
18513 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18514 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18515 }
18516
18517 fn initialize_new_minimap(
18518 &self,
18519 minimap_settings: MinimapSettings,
18520 window: &mut Window,
18521 cx: &mut Context<Self>,
18522 ) -> Entity<Self> {
18523 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18524
18525 let mut minimap = Editor::new_internal(
18526 EditorMode::Minimap {
18527 parent: cx.weak_entity(),
18528 },
18529 self.buffer.clone(),
18530 None,
18531 Some(self.display_map.clone()),
18532 window,
18533 cx,
18534 );
18535 minimap.scroll_manager.clone_state(&self.scroll_manager);
18536 minimap.set_text_style_refinement(TextStyleRefinement {
18537 font_size: Some(MINIMAP_FONT_SIZE),
18538 font_weight: Some(MINIMAP_FONT_WEIGHT),
18539 ..Default::default()
18540 });
18541 minimap.update_minimap_configuration(minimap_settings, cx);
18542 cx.new(|_| minimap)
18543 }
18544
18545 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18546 let current_line_highlight = minimap_settings
18547 .current_line_highlight
18548 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18549 self.set_current_line_highlight(Some(current_line_highlight));
18550 }
18551
18552 pub fn minimap(&self) -> Option<&Entity<Self>> {
18553 self.minimap
18554 .as_ref()
18555 .filter(|_| self.minimap_visibility.visible())
18556 }
18557
18558 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18559 let mut wrap_guides = smallvec![];
18560
18561 if self.show_wrap_guides == Some(false) {
18562 return wrap_guides;
18563 }
18564
18565 let settings = self.buffer.read(cx).language_settings(cx);
18566 if settings.show_wrap_guides {
18567 match self.soft_wrap_mode(cx) {
18568 SoftWrap::Column(soft_wrap) => {
18569 wrap_guides.push((soft_wrap as usize, true));
18570 }
18571 SoftWrap::Bounded(soft_wrap) => {
18572 wrap_guides.push((soft_wrap as usize, true));
18573 }
18574 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18575 }
18576 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18577 }
18578
18579 wrap_guides
18580 }
18581
18582 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18583 let settings = self.buffer.read(cx).language_settings(cx);
18584 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18585 match mode {
18586 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18587 SoftWrap::None
18588 }
18589 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18590 language_settings::SoftWrap::PreferredLineLength => {
18591 SoftWrap::Column(settings.preferred_line_length)
18592 }
18593 language_settings::SoftWrap::Bounded => {
18594 SoftWrap::Bounded(settings.preferred_line_length)
18595 }
18596 }
18597 }
18598
18599 pub fn set_soft_wrap_mode(
18600 &mut self,
18601 mode: language_settings::SoftWrap,
18602
18603 cx: &mut Context<Self>,
18604 ) {
18605 self.soft_wrap_mode_override = Some(mode);
18606 cx.notify();
18607 }
18608
18609 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18610 self.hard_wrap = hard_wrap;
18611 cx.notify();
18612 }
18613
18614 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18615 self.text_style_refinement = Some(style);
18616 }
18617
18618 /// called by the Element so we know what style we were most recently rendered with.
18619 pub(crate) fn set_style(
18620 &mut self,
18621 style: EditorStyle,
18622 window: &mut Window,
18623 cx: &mut Context<Self>,
18624 ) {
18625 // We intentionally do not inform the display map about the minimap style
18626 // so that wrapping is not recalculated and stays consistent for the editor
18627 // and its linked minimap.
18628 if !self.mode.is_minimap() {
18629 let rem_size = window.rem_size();
18630 self.display_map.update(cx, |map, cx| {
18631 map.set_font(
18632 style.text.font(),
18633 style.text.font_size.to_pixels(rem_size),
18634 cx,
18635 )
18636 });
18637 }
18638 self.style = Some(style);
18639 }
18640
18641 pub fn style(&self) -> Option<&EditorStyle> {
18642 self.style.as_ref()
18643 }
18644
18645 // Called by the element. This method is not designed to be called outside of the editor
18646 // element's layout code because it does not notify when rewrapping is computed synchronously.
18647 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18648 self.display_map
18649 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18650 }
18651
18652 pub fn set_soft_wrap(&mut self) {
18653 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18654 }
18655
18656 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18657 if self.soft_wrap_mode_override.is_some() {
18658 self.soft_wrap_mode_override.take();
18659 } else {
18660 let soft_wrap = match self.soft_wrap_mode(cx) {
18661 SoftWrap::GitDiff => return,
18662 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18663 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18664 language_settings::SoftWrap::None
18665 }
18666 };
18667 self.soft_wrap_mode_override = Some(soft_wrap);
18668 }
18669 cx.notify();
18670 }
18671
18672 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18673 let Some(workspace) = self.workspace() else {
18674 return;
18675 };
18676 let fs = workspace.read(cx).app_state().fs.clone();
18677 let current_show = TabBarSettings::get_global(cx).show;
18678 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18679 setting.show = Some(!current_show);
18680 });
18681 }
18682
18683 pub fn toggle_indent_guides(
18684 &mut self,
18685 _: &ToggleIndentGuides,
18686 _: &mut Window,
18687 cx: &mut Context<Self>,
18688 ) {
18689 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18690 self.buffer
18691 .read(cx)
18692 .language_settings(cx)
18693 .indent_guides
18694 .enabled
18695 });
18696 self.show_indent_guides = Some(!currently_enabled);
18697 cx.notify();
18698 }
18699
18700 fn should_show_indent_guides(&self) -> Option<bool> {
18701 self.show_indent_guides
18702 }
18703
18704 pub fn toggle_line_numbers(
18705 &mut self,
18706 _: &ToggleLineNumbers,
18707 _: &mut Window,
18708 cx: &mut Context<Self>,
18709 ) {
18710 let mut editor_settings = EditorSettings::get_global(cx).clone();
18711 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18712 EditorSettings::override_global(editor_settings, cx);
18713 }
18714
18715 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18716 if let Some(show_line_numbers) = self.show_line_numbers {
18717 return show_line_numbers;
18718 }
18719 EditorSettings::get_global(cx).gutter.line_numbers
18720 }
18721
18722 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18723 self.use_relative_line_numbers
18724 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18725 }
18726
18727 pub fn toggle_relative_line_numbers(
18728 &mut self,
18729 _: &ToggleRelativeLineNumbers,
18730 _: &mut Window,
18731 cx: &mut Context<Self>,
18732 ) {
18733 let is_relative = self.should_use_relative_line_numbers(cx);
18734 self.set_relative_line_number(Some(!is_relative), cx)
18735 }
18736
18737 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18738 self.use_relative_line_numbers = is_relative;
18739 cx.notify();
18740 }
18741
18742 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18743 self.show_gutter = show_gutter;
18744 cx.notify();
18745 }
18746
18747 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18748 self.show_scrollbars = ScrollbarAxes {
18749 horizontal: show,
18750 vertical: show,
18751 };
18752 cx.notify();
18753 }
18754
18755 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18756 self.show_scrollbars.vertical = show;
18757 cx.notify();
18758 }
18759
18760 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18761 self.show_scrollbars.horizontal = show;
18762 cx.notify();
18763 }
18764
18765 pub fn set_minimap_visibility(
18766 &mut self,
18767 minimap_visibility: MinimapVisibility,
18768 window: &mut Window,
18769 cx: &mut Context<Self>,
18770 ) {
18771 if self.minimap_visibility != minimap_visibility {
18772 if minimap_visibility.visible() && self.minimap.is_none() {
18773 let minimap_settings = EditorSettings::get_global(cx).minimap;
18774 self.minimap =
18775 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18776 }
18777 self.minimap_visibility = minimap_visibility;
18778 cx.notify();
18779 }
18780 }
18781
18782 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18783 self.set_show_scrollbars(false, cx);
18784 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18785 }
18786
18787 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18788 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18789 }
18790
18791 /// Normally the text in full mode and auto height editors is padded on the
18792 /// left side by roughly half a character width for improved hit testing.
18793 ///
18794 /// Use this method to disable this for cases where this is not wanted (e.g.
18795 /// if you want to align the editor text with some other text above or below)
18796 /// or if you want to add this padding to single-line editors.
18797 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18798 self.offset_content = offset_content;
18799 cx.notify();
18800 }
18801
18802 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18803 self.show_line_numbers = Some(show_line_numbers);
18804 cx.notify();
18805 }
18806
18807 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18808 self.disable_expand_excerpt_buttons = true;
18809 cx.notify();
18810 }
18811
18812 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18813 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18814 cx.notify();
18815 }
18816
18817 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18818 self.show_code_actions = Some(show_code_actions);
18819 cx.notify();
18820 }
18821
18822 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18823 self.show_runnables = Some(show_runnables);
18824 cx.notify();
18825 }
18826
18827 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18828 self.show_breakpoints = Some(show_breakpoints);
18829 cx.notify();
18830 }
18831
18832 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18833 if self.display_map.read(cx).masked != masked {
18834 self.display_map.update(cx, |map, _| map.masked = masked);
18835 }
18836 cx.notify()
18837 }
18838
18839 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18840 self.show_wrap_guides = Some(show_wrap_guides);
18841 cx.notify();
18842 }
18843
18844 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18845 self.show_indent_guides = Some(show_indent_guides);
18846 cx.notify();
18847 }
18848
18849 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18850 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18851 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18852 && let Some(dir) = file.abs_path(cx).parent()
18853 {
18854 return Some(dir.to_owned());
18855 }
18856
18857 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18858 return Some(project_path.path.to_path_buf());
18859 }
18860 }
18861
18862 None
18863 }
18864
18865 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18866 self.active_excerpt(cx)?
18867 .1
18868 .read(cx)
18869 .file()
18870 .and_then(|f| f.as_local())
18871 }
18872
18873 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18874 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18875 let buffer = buffer.read(cx);
18876 if let Some(project_path) = buffer.project_path(cx) {
18877 let project = self.project()?.read(cx);
18878 project.absolute_path(&project_path, cx)
18879 } else {
18880 buffer
18881 .file()
18882 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18883 }
18884 })
18885 }
18886
18887 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18888 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18889 let project_path = buffer.read(cx).project_path(cx)?;
18890 let project = self.project()?.read(cx);
18891 let entry = project.entry_for_path(&project_path, cx)?;
18892 let path = entry.path.to_path_buf();
18893 Some(path)
18894 })
18895 }
18896
18897 pub fn reveal_in_finder(
18898 &mut self,
18899 _: &RevealInFileManager,
18900 _window: &mut Window,
18901 cx: &mut Context<Self>,
18902 ) {
18903 if let Some(target) = self.target_file(cx) {
18904 cx.reveal_path(&target.abs_path(cx));
18905 }
18906 }
18907
18908 pub fn copy_path(
18909 &mut self,
18910 _: &zed_actions::workspace::CopyPath,
18911 _window: &mut Window,
18912 cx: &mut Context<Self>,
18913 ) {
18914 if let Some(path) = self.target_file_abs_path(cx)
18915 && let Some(path) = path.to_str()
18916 {
18917 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18918 }
18919 }
18920
18921 pub fn copy_relative_path(
18922 &mut self,
18923 _: &zed_actions::workspace::CopyRelativePath,
18924 _window: &mut Window,
18925 cx: &mut Context<Self>,
18926 ) {
18927 if let Some(path) = self.target_file_path(cx)
18928 && let Some(path) = path.to_str()
18929 {
18930 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18931 }
18932 }
18933
18934 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18935 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18936 buffer.read(cx).project_path(cx)
18937 } else {
18938 None
18939 }
18940 }
18941
18942 // Returns true if the editor handled a go-to-line request
18943 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18944 maybe!({
18945 let breakpoint_store = self.breakpoint_store.as_ref()?;
18946
18947 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18948 else {
18949 self.clear_row_highlights::<ActiveDebugLine>();
18950 return None;
18951 };
18952
18953 let position = active_stack_frame.position;
18954 let buffer_id = position.buffer_id?;
18955 let snapshot = self
18956 .project
18957 .as_ref()?
18958 .read(cx)
18959 .buffer_for_id(buffer_id, cx)?
18960 .read(cx)
18961 .snapshot();
18962
18963 let mut handled = false;
18964 for (id, ExcerptRange { context, .. }) in
18965 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18966 {
18967 if context.start.cmp(&position, &snapshot).is_ge()
18968 || context.end.cmp(&position, &snapshot).is_lt()
18969 {
18970 continue;
18971 }
18972 let snapshot = self.buffer.read(cx).snapshot(cx);
18973 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18974
18975 handled = true;
18976 self.clear_row_highlights::<ActiveDebugLine>();
18977
18978 self.go_to_line::<ActiveDebugLine>(
18979 multibuffer_anchor,
18980 Some(cx.theme().colors().editor_debugger_active_line_background),
18981 window,
18982 cx,
18983 );
18984
18985 cx.notify();
18986 }
18987
18988 handled.then_some(())
18989 })
18990 .is_some()
18991 }
18992
18993 pub fn copy_file_name_without_extension(
18994 &mut self,
18995 _: &CopyFileNameWithoutExtension,
18996 _: &mut Window,
18997 cx: &mut Context<Self>,
18998 ) {
18999 if let Some(file) = self.target_file(cx)
19000 && let Some(file_stem) = file.path().file_stem()
19001 && let Some(name) = file_stem.to_str()
19002 {
19003 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19004 }
19005 }
19006
19007 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19008 if let Some(file) = self.target_file(cx)
19009 && let Some(file_name) = file.path().file_name()
19010 && let Some(name) = file_name.to_str()
19011 {
19012 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19013 }
19014 }
19015
19016 pub fn toggle_git_blame(
19017 &mut self,
19018 _: &::git::Blame,
19019 window: &mut Window,
19020 cx: &mut Context<Self>,
19021 ) {
19022 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19023
19024 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19025 self.start_git_blame(true, window, cx);
19026 }
19027
19028 cx.notify();
19029 }
19030
19031 pub fn toggle_git_blame_inline(
19032 &mut self,
19033 _: &ToggleGitBlameInline,
19034 window: &mut Window,
19035 cx: &mut Context<Self>,
19036 ) {
19037 self.toggle_git_blame_inline_internal(true, window, cx);
19038 cx.notify();
19039 }
19040
19041 pub fn open_git_blame_commit(
19042 &mut self,
19043 _: &OpenGitBlameCommit,
19044 window: &mut Window,
19045 cx: &mut Context<Self>,
19046 ) {
19047 self.open_git_blame_commit_internal(window, cx);
19048 }
19049
19050 fn open_git_blame_commit_internal(
19051 &mut self,
19052 window: &mut Window,
19053 cx: &mut Context<Self>,
19054 ) -> Option<()> {
19055 let blame = self.blame.as_ref()?;
19056 let snapshot = self.snapshot(window, cx);
19057 let cursor = self.selections.newest::<Point>(cx).head();
19058 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19059 let blame_entry = blame
19060 .update(cx, |blame, cx| {
19061 blame
19062 .blame_for_rows(
19063 &[RowInfo {
19064 buffer_id: Some(buffer.remote_id()),
19065 buffer_row: Some(point.row),
19066 ..Default::default()
19067 }],
19068 cx,
19069 )
19070 .next()
19071 })
19072 .flatten()?;
19073 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19074 let repo = blame.read(cx).repository(cx)?;
19075 let workspace = self.workspace()?.downgrade();
19076 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19077 None
19078 }
19079
19080 pub fn git_blame_inline_enabled(&self) -> bool {
19081 self.git_blame_inline_enabled
19082 }
19083
19084 pub fn toggle_selection_menu(
19085 &mut self,
19086 _: &ToggleSelectionMenu,
19087 _: &mut Window,
19088 cx: &mut Context<Self>,
19089 ) {
19090 self.show_selection_menu = self
19091 .show_selection_menu
19092 .map(|show_selections_menu| !show_selections_menu)
19093 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19094
19095 cx.notify();
19096 }
19097
19098 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19099 self.show_selection_menu
19100 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19101 }
19102
19103 fn start_git_blame(
19104 &mut self,
19105 user_triggered: bool,
19106 window: &mut Window,
19107 cx: &mut Context<Self>,
19108 ) {
19109 if let Some(project) = self.project() {
19110 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
19111 return;
19112 };
19113
19114 if buffer.read(cx).file().is_none() {
19115 return;
19116 }
19117
19118 let focused = self.focus_handle(cx).contains_focused(window, cx);
19119
19120 let project = project.clone();
19121 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
19122 self.blame_subscription =
19123 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19124 self.blame = Some(blame);
19125 }
19126 }
19127
19128 fn toggle_git_blame_inline_internal(
19129 &mut self,
19130 user_triggered: bool,
19131 window: &mut Window,
19132 cx: &mut Context<Self>,
19133 ) {
19134 if self.git_blame_inline_enabled {
19135 self.git_blame_inline_enabled = false;
19136 self.show_git_blame_inline = false;
19137 self.show_git_blame_inline_delay_task.take();
19138 } else {
19139 self.git_blame_inline_enabled = true;
19140 self.start_git_blame_inline(user_triggered, window, cx);
19141 }
19142
19143 cx.notify();
19144 }
19145
19146 fn start_git_blame_inline(
19147 &mut self,
19148 user_triggered: bool,
19149 window: &mut Window,
19150 cx: &mut Context<Self>,
19151 ) {
19152 self.start_git_blame(user_triggered, window, cx);
19153
19154 if ProjectSettings::get_global(cx)
19155 .git
19156 .inline_blame_delay()
19157 .is_some()
19158 {
19159 self.start_inline_blame_timer(window, cx);
19160 } else {
19161 self.show_git_blame_inline = true
19162 }
19163 }
19164
19165 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19166 self.blame.as_ref()
19167 }
19168
19169 pub fn show_git_blame_gutter(&self) -> bool {
19170 self.show_git_blame_gutter
19171 }
19172
19173 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19174 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19175 }
19176
19177 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19178 self.show_git_blame_inline
19179 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19180 && !self.newest_selection_head_on_empty_line(cx)
19181 && self.has_blame_entries(cx)
19182 }
19183
19184 fn has_blame_entries(&self, cx: &App) -> bool {
19185 self.blame()
19186 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19187 }
19188
19189 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19190 let cursor_anchor = self.selections.newest_anchor().head();
19191
19192 let snapshot = self.buffer.read(cx).snapshot(cx);
19193 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19194
19195 snapshot.line_len(buffer_row) == 0
19196 }
19197
19198 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19199 let buffer_and_selection = maybe!({
19200 let selection = self.selections.newest::<Point>(cx);
19201 let selection_range = selection.range();
19202
19203 let multi_buffer = self.buffer().read(cx);
19204 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19205 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19206
19207 let (buffer, range, _) = if selection.reversed {
19208 buffer_ranges.first()
19209 } else {
19210 buffer_ranges.last()
19211 }?;
19212
19213 let selection = text::ToPoint::to_point(&range.start, buffer).row
19214 ..text::ToPoint::to_point(&range.end, buffer).row;
19215 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19216 });
19217
19218 let Some((buffer, selection)) = buffer_and_selection else {
19219 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19220 };
19221
19222 let Some(project) = self.project() else {
19223 return Task::ready(Err(anyhow!("editor does not have project")));
19224 };
19225
19226 project.update(cx, |project, cx| {
19227 project.get_permalink_to_line(&buffer, selection, cx)
19228 })
19229 }
19230
19231 pub fn copy_permalink_to_line(
19232 &mut self,
19233 _: &CopyPermalinkToLine,
19234 window: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) {
19237 let permalink_task = self.get_permalink_to_line(cx);
19238 let workspace = self.workspace();
19239
19240 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19241 Ok(permalink) => {
19242 cx.update(|_, cx| {
19243 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19244 })
19245 .ok();
19246 }
19247 Err(err) => {
19248 let message = format!("Failed to copy permalink: {err}");
19249
19250 anyhow::Result::<()>::Err(err).log_err();
19251
19252 if let Some(workspace) = workspace {
19253 workspace
19254 .update_in(cx, |workspace, _, cx| {
19255 struct CopyPermalinkToLine;
19256
19257 workspace.show_toast(
19258 Toast::new(
19259 NotificationId::unique::<CopyPermalinkToLine>(),
19260 message,
19261 ),
19262 cx,
19263 )
19264 })
19265 .ok();
19266 }
19267 }
19268 })
19269 .detach();
19270 }
19271
19272 pub fn copy_file_location(
19273 &mut self,
19274 _: &CopyFileLocation,
19275 _: &mut Window,
19276 cx: &mut Context<Self>,
19277 ) {
19278 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19279 if let Some(file) = self.target_file(cx)
19280 && let Some(path) = file.path().to_str()
19281 {
19282 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19283 }
19284 }
19285
19286 pub fn open_permalink_to_line(
19287 &mut self,
19288 _: &OpenPermalinkToLine,
19289 window: &mut Window,
19290 cx: &mut Context<Self>,
19291 ) {
19292 let permalink_task = self.get_permalink_to_line(cx);
19293 let workspace = self.workspace();
19294
19295 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19296 Ok(permalink) => {
19297 cx.update(|_, cx| {
19298 cx.open_url(permalink.as_ref());
19299 })
19300 .ok();
19301 }
19302 Err(err) => {
19303 let message = format!("Failed to open permalink: {err}");
19304
19305 anyhow::Result::<()>::Err(err).log_err();
19306
19307 if let Some(workspace) = workspace {
19308 workspace
19309 .update(cx, |workspace, cx| {
19310 struct OpenPermalinkToLine;
19311
19312 workspace.show_toast(
19313 Toast::new(
19314 NotificationId::unique::<OpenPermalinkToLine>(),
19315 message,
19316 ),
19317 cx,
19318 )
19319 })
19320 .ok();
19321 }
19322 }
19323 })
19324 .detach();
19325 }
19326
19327 pub fn insert_uuid_v4(
19328 &mut self,
19329 _: &InsertUuidV4,
19330 window: &mut Window,
19331 cx: &mut Context<Self>,
19332 ) {
19333 self.insert_uuid(UuidVersion::V4, window, cx);
19334 }
19335
19336 pub fn insert_uuid_v7(
19337 &mut self,
19338 _: &InsertUuidV7,
19339 window: &mut Window,
19340 cx: &mut Context<Self>,
19341 ) {
19342 self.insert_uuid(UuidVersion::V7, window, cx);
19343 }
19344
19345 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19347 self.transact(window, cx, |this, window, cx| {
19348 let edits = this
19349 .selections
19350 .all::<Point>(cx)
19351 .into_iter()
19352 .map(|selection| {
19353 let uuid = match version {
19354 UuidVersion::V4 => uuid::Uuid::new_v4(),
19355 UuidVersion::V7 => uuid::Uuid::now_v7(),
19356 };
19357
19358 (selection.range(), uuid.to_string())
19359 });
19360 this.edit(edits, cx);
19361 this.refresh_edit_prediction(true, false, window, cx);
19362 });
19363 }
19364
19365 pub fn open_selections_in_multibuffer(
19366 &mut self,
19367 _: &OpenSelectionsInMultibuffer,
19368 window: &mut Window,
19369 cx: &mut Context<Self>,
19370 ) {
19371 let multibuffer = self.buffer.read(cx);
19372
19373 let Some(buffer) = multibuffer.as_singleton() else {
19374 return;
19375 };
19376
19377 let Some(workspace) = self.workspace() else {
19378 return;
19379 };
19380
19381 let title = multibuffer.title(cx).to_string();
19382
19383 let locations = self
19384 .selections
19385 .all_anchors(cx)
19386 .iter()
19387 .map(|selection| Location {
19388 buffer: buffer.clone(),
19389 range: selection.start.text_anchor..selection.end.text_anchor,
19390 })
19391 .collect::<Vec<_>>();
19392
19393 cx.spawn_in(window, async move |_, cx| {
19394 workspace.update_in(cx, |workspace, window, cx| {
19395 Self::open_locations_in_multibuffer(
19396 workspace,
19397 locations,
19398 format!("Selections for '{title}'"),
19399 false,
19400 MultibufferSelectionMode::All,
19401 window,
19402 cx,
19403 );
19404 })
19405 })
19406 .detach();
19407 }
19408
19409 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19410 /// last highlight added will be used.
19411 ///
19412 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19413 pub fn highlight_rows<T: 'static>(
19414 &mut self,
19415 range: Range<Anchor>,
19416 color: Hsla,
19417 options: RowHighlightOptions,
19418 cx: &mut Context<Self>,
19419 ) {
19420 let snapshot = self.buffer().read(cx).snapshot(cx);
19421 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19422 let ix = row_highlights.binary_search_by(|highlight| {
19423 Ordering::Equal
19424 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19425 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19426 });
19427
19428 if let Err(mut ix) = ix {
19429 let index = post_inc(&mut self.highlight_order);
19430
19431 // If this range intersects with the preceding highlight, then merge it with
19432 // the preceding highlight. Otherwise insert a new highlight.
19433 let mut merged = false;
19434 if ix > 0 {
19435 let prev_highlight = &mut row_highlights[ix - 1];
19436 if prev_highlight
19437 .range
19438 .end
19439 .cmp(&range.start, &snapshot)
19440 .is_ge()
19441 {
19442 ix -= 1;
19443 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19444 prev_highlight.range.end = range.end;
19445 }
19446 merged = true;
19447 prev_highlight.index = index;
19448 prev_highlight.color = color;
19449 prev_highlight.options = options;
19450 }
19451 }
19452
19453 if !merged {
19454 row_highlights.insert(
19455 ix,
19456 RowHighlight {
19457 range,
19458 index,
19459 color,
19460 options,
19461 type_id: TypeId::of::<T>(),
19462 },
19463 );
19464 }
19465
19466 // If any of the following highlights intersect with this one, merge them.
19467 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19468 let highlight = &row_highlights[ix];
19469 if next_highlight
19470 .range
19471 .start
19472 .cmp(&highlight.range.end, &snapshot)
19473 .is_le()
19474 {
19475 if next_highlight
19476 .range
19477 .end
19478 .cmp(&highlight.range.end, &snapshot)
19479 .is_gt()
19480 {
19481 row_highlights[ix].range.end = next_highlight.range.end;
19482 }
19483 row_highlights.remove(ix + 1);
19484 } else {
19485 break;
19486 }
19487 }
19488 }
19489 }
19490
19491 /// Remove any highlighted row ranges of the given type that intersect the
19492 /// given ranges.
19493 pub fn remove_highlighted_rows<T: 'static>(
19494 &mut self,
19495 ranges_to_remove: Vec<Range<Anchor>>,
19496 cx: &mut Context<Self>,
19497 ) {
19498 let snapshot = self.buffer().read(cx).snapshot(cx);
19499 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19500 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19501 row_highlights.retain(|highlight| {
19502 while let Some(range_to_remove) = ranges_to_remove.peek() {
19503 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19504 Ordering::Less | Ordering::Equal => {
19505 ranges_to_remove.next();
19506 }
19507 Ordering::Greater => {
19508 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19509 Ordering::Less | Ordering::Equal => {
19510 return false;
19511 }
19512 Ordering::Greater => break,
19513 }
19514 }
19515 }
19516 }
19517
19518 true
19519 })
19520 }
19521
19522 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19523 pub fn clear_row_highlights<T: 'static>(&mut self) {
19524 self.highlighted_rows.remove(&TypeId::of::<T>());
19525 }
19526
19527 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19528 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19529 self.highlighted_rows
19530 .get(&TypeId::of::<T>())
19531 .map_or(&[] as &[_], |vec| vec.as_slice())
19532 .iter()
19533 .map(|highlight| (highlight.range.clone(), highlight.color))
19534 }
19535
19536 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19537 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19538 /// Allows to ignore certain kinds of highlights.
19539 pub fn highlighted_display_rows(
19540 &self,
19541 window: &mut Window,
19542 cx: &mut App,
19543 ) -> BTreeMap<DisplayRow, LineHighlight> {
19544 let snapshot = self.snapshot(window, cx);
19545 let mut used_highlight_orders = HashMap::default();
19546 self.highlighted_rows
19547 .iter()
19548 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19549 .fold(
19550 BTreeMap::<DisplayRow, LineHighlight>::new(),
19551 |mut unique_rows, highlight| {
19552 let start = highlight.range.start.to_display_point(&snapshot);
19553 let end = highlight.range.end.to_display_point(&snapshot);
19554 let start_row = start.row().0;
19555 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19556 && end.column() == 0
19557 {
19558 end.row().0.saturating_sub(1)
19559 } else {
19560 end.row().0
19561 };
19562 for row in start_row..=end_row {
19563 let used_index =
19564 used_highlight_orders.entry(row).or_insert(highlight.index);
19565 if highlight.index >= *used_index {
19566 *used_index = highlight.index;
19567 unique_rows.insert(
19568 DisplayRow(row),
19569 LineHighlight {
19570 include_gutter: highlight.options.include_gutter,
19571 border: None,
19572 background: highlight.color.into(),
19573 type_id: Some(highlight.type_id),
19574 },
19575 );
19576 }
19577 }
19578 unique_rows
19579 },
19580 )
19581 }
19582
19583 pub fn highlighted_display_row_for_autoscroll(
19584 &self,
19585 snapshot: &DisplaySnapshot,
19586 ) -> Option<DisplayRow> {
19587 self.highlighted_rows
19588 .values()
19589 .flat_map(|highlighted_rows| highlighted_rows.iter())
19590 .filter_map(|highlight| {
19591 if highlight.options.autoscroll {
19592 Some(highlight.range.start.to_display_point(snapshot).row())
19593 } else {
19594 None
19595 }
19596 })
19597 .min()
19598 }
19599
19600 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19601 self.highlight_background::<SearchWithinRange>(
19602 ranges,
19603 |colors| colors.colors().editor_document_highlight_read_background,
19604 cx,
19605 )
19606 }
19607
19608 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19609 self.breadcrumb_header = Some(new_header);
19610 }
19611
19612 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19613 self.clear_background_highlights::<SearchWithinRange>(cx);
19614 }
19615
19616 pub fn highlight_background<T: 'static>(
19617 &mut self,
19618 ranges: &[Range<Anchor>],
19619 color_fetcher: fn(&Theme) -> Hsla,
19620 cx: &mut Context<Self>,
19621 ) {
19622 self.background_highlights.insert(
19623 HighlightKey::Type(TypeId::of::<T>()),
19624 (color_fetcher, Arc::from(ranges)),
19625 );
19626 self.scrollbar_marker_state.dirty = true;
19627 cx.notify();
19628 }
19629
19630 pub fn highlight_background_key<T: 'static>(
19631 &mut self,
19632 key: usize,
19633 ranges: &[Range<Anchor>],
19634 color_fetcher: fn(&Theme) -> Hsla,
19635 cx: &mut Context<Self>,
19636 ) {
19637 self.background_highlights.insert(
19638 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19639 (color_fetcher, Arc::from(ranges)),
19640 );
19641 self.scrollbar_marker_state.dirty = true;
19642 cx.notify();
19643 }
19644
19645 pub fn clear_background_highlights<T: 'static>(
19646 &mut self,
19647 cx: &mut Context<Self>,
19648 ) -> Option<BackgroundHighlight> {
19649 let text_highlights = self
19650 .background_highlights
19651 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19652 if !text_highlights.1.is_empty() {
19653 self.scrollbar_marker_state.dirty = true;
19654 cx.notify();
19655 }
19656 Some(text_highlights)
19657 }
19658
19659 pub fn highlight_gutter<T: 'static>(
19660 &mut self,
19661 ranges: impl Into<Vec<Range<Anchor>>>,
19662 color_fetcher: fn(&App) -> Hsla,
19663 cx: &mut Context<Self>,
19664 ) {
19665 self.gutter_highlights
19666 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19667 cx.notify();
19668 }
19669
19670 pub fn clear_gutter_highlights<T: 'static>(
19671 &mut self,
19672 cx: &mut Context<Self>,
19673 ) -> Option<GutterHighlight> {
19674 cx.notify();
19675 self.gutter_highlights.remove(&TypeId::of::<T>())
19676 }
19677
19678 pub fn insert_gutter_highlight<T: 'static>(
19679 &mut self,
19680 range: Range<Anchor>,
19681 color_fetcher: fn(&App) -> Hsla,
19682 cx: &mut Context<Self>,
19683 ) {
19684 let snapshot = self.buffer().read(cx).snapshot(cx);
19685 let mut highlights = self
19686 .gutter_highlights
19687 .remove(&TypeId::of::<T>())
19688 .map(|(_, highlights)| highlights)
19689 .unwrap_or_default();
19690 let ix = highlights.binary_search_by(|highlight| {
19691 Ordering::Equal
19692 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19693 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19694 });
19695 if let Err(ix) = ix {
19696 highlights.insert(ix, range);
19697 }
19698 self.gutter_highlights
19699 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19700 }
19701
19702 pub fn remove_gutter_highlights<T: 'static>(
19703 &mut self,
19704 ranges_to_remove: Vec<Range<Anchor>>,
19705 cx: &mut Context<Self>,
19706 ) {
19707 let snapshot = self.buffer().read(cx).snapshot(cx);
19708 let Some((color_fetcher, mut gutter_highlights)) =
19709 self.gutter_highlights.remove(&TypeId::of::<T>())
19710 else {
19711 return;
19712 };
19713 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19714 gutter_highlights.retain(|highlight| {
19715 while let Some(range_to_remove) = ranges_to_remove.peek() {
19716 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19717 Ordering::Less | Ordering::Equal => {
19718 ranges_to_remove.next();
19719 }
19720 Ordering::Greater => {
19721 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19722 Ordering::Less | Ordering::Equal => {
19723 return false;
19724 }
19725 Ordering::Greater => break,
19726 }
19727 }
19728 }
19729 }
19730
19731 true
19732 });
19733 self.gutter_highlights
19734 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19735 }
19736
19737 #[cfg(feature = "test-support")]
19738 pub fn all_text_highlights(
19739 &self,
19740 window: &mut Window,
19741 cx: &mut Context<Self>,
19742 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19743 let snapshot = self.snapshot(window, cx);
19744 self.display_map.update(cx, |display_map, _| {
19745 display_map
19746 .all_text_highlights()
19747 .map(|highlight| {
19748 let (style, ranges) = highlight.as_ref();
19749 (
19750 *style,
19751 ranges
19752 .iter()
19753 .map(|range| range.clone().to_display_points(&snapshot))
19754 .collect(),
19755 )
19756 })
19757 .collect()
19758 })
19759 }
19760
19761 #[cfg(feature = "test-support")]
19762 pub fn all_text_background_highlights(
19763 &self,
19764 window: &mut Window,
19765 cx: &mut Context<Self>,
19766 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19767 let snapshot = self.snapshot(window, cx);
19768 let buffer = &snapshot.buffer_snapshot;
19769 let start = buffer.anchor_before(0);
19770 let end = buffer.anchor_after(buffer.len());
19771 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19772 }
19773
19774 #[cfg(feature = "test-support")]
19775 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19776 let snapshot = self.buffer().read(cx).snapshot(cx);
19777
19778 let highlights = self
19779 .background_highlights
19780 .get(&HighlightKey::Type(TypeId::of::<
19781 items::BufferSearchHighlights,
19782 >()));
19783
19784 if let Some((_color, ranges)) = highlights {
19785 ranges
19786 .iter()
19787 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19788 .collect_vec()
19789 } else {
19790 vec![]
19791 }
19792 }
19793
19794 fn document_highlights_for_position<'a>(
19795 &'a self,
19796 position: Anchor,
19797 buffer: &'a MultiBufferSnapshot,
19798 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19799 let read_highlights = self
19800 .background_highlights
19801 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19802 .map(|h| &h.1);
19803 let write_highlights = self
19804 .background_highlights
19805 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19806 .map(|h| &h.1);
19807 let left_position = position.bias_left(buffer);
19808 let right_position = position.bias_right(buffer);
19809 read_highlights
19810 .into_iter()
19811 .chain(write_highlights)
19812 .flat_map(move |ranges| {
19813 let start_ix = match ranges.binary_search_by(|probe| {
19814 let cmp = probe.end.cmp(&left_position, buffer);
19815 if cmp.is_ge() {
19816 Ordering::Greater
19817 } else {
19818 Ordering::Less
19819 }
19820 }) {
19821 Ok(i) | Err(i) => i,
19822 };
19823
19824 ranges[start_ix..]
19825 .iter()
19826 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19827 })
19828 }
19829
19830 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19831 self.background_highlights
19832 .get(&HighlightKey::Type(TypeId::of::<T>()))
19833 .is_some_and(|(_, highlights)| !highlights.is_empty())
19834 }
19835
19836 pub fn background_highlights_in_range(
19837 &self,
19838 search_range: Range<Anchor>,
19839 display_snapshot: &DisplaySnapshot,
19840 theme: &Theme,
19841 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19842 let mut results = Vec::new();
19843 for (color_fetcher, ranges) in self.background_highlights.values() {
19844 let color = color_fetcher(theme);
19845 let start_ix = match ranges.binary_search_by(|probe| {
19846 let cmp = probe
19847 .end
19848 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19849 if cmp.is_gt() {
19850 Ordering::Greater
19851 } else {
19852 Ordering::Less
19853 }
19854 }) {
19855 Ok(i) | Err(i) => i,
19856 };
19857 for range in &ranges[start_ix..] {
19858 if range
19859 .start
19860 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19861 .is_ge()
19862 {
19863 break;
19864 }
19865
19866 let start = range.start.to_display_point(display_snapshot);
19867 let end = range.end.to_display_point(display_snapshot);
19868 results.push((start..end, color))
19869 }
19870 }
19871 results
19872 }
19873
19874 pub fn background_highlight_row_ranges<T: 'static>(
19875 &self,
19876 search_range: Range<Anchor>,
19877 display_snapshot: &DisplaySnapshot,
19878 count: usize,
19879 ) -> Vec<RangeInclusive<DisplayPoint>> {
19880 let mut results = Vec::new();
19881 let Some((_, ranges)) = self
19882 .background_highlights
19883 .get(&HighlightKey::Type(TypeId::of::<T>()))
19884 else {
19885 return vec![];
19886 };
19887
19888 let start_ix = match ranges.binary_search_by(|probe| {
19889 let cmp = probe
19890 .end
19891 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19892 if cmp.is_gt() {
19893 Ordering::Greater
19894 } else {
19895 Ordering::Less
19896 }
19897 }) {
19898 Ok(i) | Err(i) => i,
19899 };
19900 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19901 if let (Some(start_display), Some(end_display)) = (start, end) {
19902 results.push(
19903 start_display.to_display_point(display_snapshot)
19904 ..=end_display.to_display_point(display_snapshot),
19905 );
19906 }
19907 };
19908 let mut start_row: Option<Point> = None;
19909 let mut end_row: Option<Point> = None;
19910 if ranges.len() > count {
19911 return Vec::new();
19912 }
19913 for range in &ranges[start_ix..] {
19914 if range
19915 .start
19916 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19917 .is_ge()
19918 {
19919 break;
19920 }
19921 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19922 if let Some(current_row) = &end_row
19923 && end.row == current_row.row
19924 {
19925 continue;
19926 }
19927 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19928 if start_row.is_none() {
19929 assert_eq!(end_row, None);
19930 start_row = Some(start);
19931 end_row = Some(end);
19932 continue;
19933 }
19934 if let Some(current_end) = end_row.as_mut() {
19935 if start.row > current_end.row + 1 {
19936 push_region(start_row, end_row);
19937 start_row = Some(start);
19938 end_row = Some(end);
19939 } else {
19940 // Merge two hunks.
19941 *current_end = end;
19942 }
19943 } else {
19944 unreachable!();
19945 }
19946 }
19947 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19948 push_region(start_row, end_row);
19949 results
19950 }
19951
19952 pub fn gutter_highlights_in_range(
19953 &self,
19954 search_range: Range<Anchor>,
19955 display_snapshot: &DisplaySnapshot,
19956 cx: &App,
19957 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19958 let mut results = Vec::new();
19959 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19960 let color = color_fetcher(cx);
19961 let start_ix = match ranges.binary_search_by(|probe| {
19962 let cmp = probe
19963 .end
19964 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19965 if cmp.is_gt() {
19966 Ordering::Greater
19967 } else {
19968 Ordering::Less
19969 }
19970 }) {
19971 Ok(i) | Err(i) => i,
19972 };
19973 for range in &ranges[start_ix..] {
19974 if range
19975 .start
19976 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19977 .is_ge()
19978 {
19979 break;
19980 }
19981
19982 let start = range.start.to_display_point(display_snapshot);
19983 let end = range.end.to_display_point(display_snapshot);
19984 results.push((start..end, color))
19985 }
19986 }
19987 results
19988 }
19989
19990 /// Get the text ranges corresponding to the redaction query
19991 pub fn redacted_ranges(
19992 &self,
19993 search_range: Range<Anchor>,
19994 display_snapshot: &DisplaySnapshot,
19995 cx: &App,
19996 ) -> Vec<Range<DisplayPoint>> {
19997 display_snapshot
19998 .buffer_snapshot
19999 .redacted_ranges(search_range, |file| {
20000 if let Some(file) = file {
20001 file.is_private()
20002 && EditorSettings::get(
20003 Some(SettingsLocation {
20004 worktree_id: file.worktree_id(cx),
20005 path: file.path().as_ref(),
20006 }),
20007 cx,
20008 )
20009 .redact_private_values
20010 } else {
20011 false
20012 }
20013 })
20014 .map(|range| {
20015 range.start.to_display_point(display_snapshot)
20016 ..range.end.to_display_point(display_snapshot)
20017 })
20018 .collect()
20019 }
20020
20021 pub fn highlight_text_key<T: 'static>(
20022 &mut self,
20023 key: usize,
20024 ranges: Vec<Range<Anchor>>,
20025 style: HighlightStyle,
20026 cx: &mut Context<Self>,
20027 ) {
20028 self.display_map.update(cx, |map, _| {
20029 map.highlight_text(
20030 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20031 ranges,
20032 style,
20033 );
20034 });
20035 cx.notify();
20036 }
20037
20038 pub fn highlight_text<T: 'static>(
20039 &mut self,
20040 ranges: Vec<Range<Anchor>>,
20041 style: HighlightStyle,
20042 cx: &mut Context<Self>,
20043 ) {
20044 self.display_map.update(cx, |map, _| {
20045 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20046 });
20047 cx.notify();
20048 }
20049
20050 pub(crate) fn highlight_inlays<T: 'static>(
20051 &mut self,
20052 highlights: Vec<InlayHighlight>,
20053 style: HighlightStyle,
20054 cx: &mut Context<Self>,
20055 ) {
20056 self.display_map.update(cx, |map, _| {
20057 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20058 });
20059 cx.notify();
20060 }
20061
20062 pub fn text_highlights<'a, T: 'static>(
20063 &'a self,
20064 cx: &'a App,
20065 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20066 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20067 }
20068
20069 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20070 let cleared = self
20071 .display_map
20072 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20073 if cleared {
20074 cx.notify();
20075 }
20076 }
20077
20078 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20079 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20080 && self.focus_handle.is_focused(window)
20081 }
20082
20083 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20084 self.show_cursor_when_unfocused = is_enabled;
20085 cx.notify();
20086 }
20087
20088 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20089 cx.notify();
20090 }
20091
20092 fn on_debug_session_event(
20093 &mut self,
20094 _session: Entity<Session>,
20095 event: &SessionEvent,
20096 cx: &mut Context<Self>,
20097 ) {
20098 if let SessionEvent::InvalidateInlineValue = event {
20099 self.refresh_inline_values(cx);
20100 }
20101 }
20102
20103 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20104 let Some(project) = self.project.clone() else {
20105 return;
20106 };
20107
20108 if !self.inline_value_cache.enabled {
20109 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20110 self.splice_inlays(&inlays, Vec::new(), cx);
20111 return;
20112 }
20113
20114 let current_execution_position = self
20115 .highlighted_rows
20116 .get(&TypeId::of::<ActiveDebugLine>())
20117 .and_then(|lines| lines.last().map(|line| line.range.end));
20118
20119 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20120 let inline_values = editor
20121 .update(cx, |editor, cx| {
20122 let Some(current_execution_position) = current_execution_position else {
20123 return Some(Task::ready(Ok(Vec::new())));
20124 };
20125
20126 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20127 let snapshot = buffer.snapshot(cx);
20128
20129 let excerpt = snapshot.excerpt_containing(
20130 current_execution_position..current_execution_position,
20131 )?;
20132
20133 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20134 })?;
20135
20136 let range =
20137 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20138
20139 project.inline_values(buffer, range, cx)
20140 })
20141 .ok()
20142 .flatten()?
20143 .await
20144 .context("refreshing debugger inlays")
20145 .log_err()?;
20146
20147 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20148
20149 for (buffer_id, inline_value) in inline_values
20150 .into_iter()
20151 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20152 {
20153 buffer_inline_values
20154 .entry(buffer_id)
20155 .or_default()
20156 .push(inline_value);
20157 }
20158
20159 editor
20160 .update(cx, |editor, cx| {
20161 let snapshot = editor.buffer.read(cx).snapshot(cx);
20162 let mut new_inlays = Vec::default();
20163
20164 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20165 let buffer_id = buffer_snapshot.remote_id();
20166 buffer_inline_values
20167 .get(&buffer_id)
20168 .into_iter()
20169 .flatten()
20170 .for_each(|hint| {
20171 let inlay = Inlay::debugger(
20172 post_inc(&mut editor.next_inlay_id),
20173 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20174 hint.text(),
20175 );
20176 if !inlay.text.chars().contains(&'\n') {
20177 new_inlays.push(inlay);
20178 }
20179 });
20180 }
20181
20182 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20183 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20184
20185 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20186 })
20187 .ok()?;
20188 Some(())
20189 });
20190 }
20191
20192 fn on_buffer_event(
20193 &mut self,
20194 multibuffer: &Entity<MultiBuffer>,
20195 event: &multi_buffer::Event,
20196 window: &mut Window,
20197 cx: &mut Context<Self>,
20198 ) {
20199 match event {
20200 multi_buffer::Event::Edited {
20201 singleton_buffer_edited,
20202 edited_buffer,
20203 } => {
20204 self.scrollbar_marker_state.dirty = true;
20205 self.active_indent_guides_state.dirty = true;
20206 self.refresh_active_diagnostics(cx);
20207 self.refresh_code_actions(window, cx);
20208 self.refresh_selected_text_highlights(true, window, cx);
20209 self.refresh_single_line_folds(window, cx);
20210 refresh_matching_bracket_highlights(self, window, cx);
20211 if self.has_active_edit_prediction() {
20212 self.update_visible_edit_prediction(window, cx);
20213 }
20214 if let Some(project) = self.project.as_ref()
20215 && let Some(edited_buffer) = edited_buffer
20216 {
20217 project.update(cx, |project, cx| {
20218 self.registered_buffers
20219 .entry(edited_buffer.read(cx).remote_id())
20220 .or_insert_with(|| {
20221 project.register_buffer_with_language_servers(edited_buffer, cx)
20222 });
20223 });
20224 }
20225 cx.emit(EditorEvent::BufferEdited);
20226 cx.emit(SearchEvent::MatchesInvalidated);
20227
20228 if let Some(buffer) = edited_buffer {
20229 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20230 }
20231
20232 if *singleton_buffer_edited {
20233 if let Some(buffer) = edited_buffer
20234 && buffer.read(cx).file().is_none()
20235 {
20236 cx.emit(EditorEvent::TitleChanged);
20237 }
20238 if let Some(project) = &self.project {
20239 #[allow(clippy::mutable_key_type)]
20240 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20241 multibuffer
20242 .all_buffers()
20243 .into_iter()
20244 .filter_map(|buffer| {
20245 buffer.update(cx, |buffer, cx| {
20246 let language = buffer.language()?;
20247 let should_discard = project.update(cx, |project, cx| {
20248 project.is_local()
20249 && !project.has_language_servers_for(buffer, cx)
20250 });
20251 should_discard.not().then_some(language.clone())
20252 })
20253 })
20254 .collect::<HashSet<_>>()
20255 });
20256 if !languages_affected.is_empty() {
20257 self.refresh_inlay_hints(
20258 InlayHintRefreshReason::BufferEdited(languages_affected),
20259 cx,
20260 );
20261 }
20262 }
20263 }
20264
20265 let Some(project) = &self.project else { return };
20266 let (telemetry, is_via_ssh) = {
20267 let project = project.read(cx);
20268 let telemetry = project.client().telemetry().clone();
20269 let is_via_ssh = project.is_via_remote_server();
20270 (telemetry, is_via_ssh)
20271 };
20272 refresh_linked_ranges(self, window, cx);
20273 telemetry.log_edit_event("editor", is_via_ssh);
20274 }
20275 multi_buffer::Event::ExcerptsAdded {
20276 buffer,
20277 predecessor,
20278 excerpts,
20279 } => {
20280 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20281 let buffer_id = buffer.read(cx).remote_id();
20282 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20283 && let Some(project) = &self.project
20284 {
20285 update_uncommitted_diff_for_buffer(
20286 cx.entity(),
20287 project,
20288 [buffer.clone()],
20289 self.buffer.clone(),
20290 cx,
20291 )
20292 .detach();
20293 }
20294 self.update_lsp_data(false, Some(buffer_id), window, cx);
20295 cx.emit(EditorEvent::ExcerptsAdded {
20296 buffer: buffer.clone(),
20297 predecessor: *predecessor,
20298 excerpts: excerpts.clone(),
20299 });
20300 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20301 }
20302 multi_buffer::Event::ExcerptsRemoved {
20303 ids,
20304 removed_buffer_ids,
20305 } => {
20306 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20307 let buffer = self.buffer.read(cx);
20308 self.registered_buffers
20309 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20310 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20311 cx.emit(EditorEvent::ExcerptsRemoved {
20312 ids: ids.clone(),
20313 removed_buffer_ids: removed_buffer_ids.clone(),
20314 });
20315 }
20316 multi_buffer::Event::ExcerptsEdited {
20317 excerpt_ids,
20318 buffer_ids,
20319 } => {
20320 self.display_map.update(cx, |map, cx| {
20321 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20322 });
20323 cx.emit(EditorEvent::ExcerptsEdited {
20324 ids: excerpt_ids.clone(),
20325 });
20326 }
20327 multi_buffer::Event::ExcerptsExpanded { ids } => {
20328 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20329 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20330 }
20331 multi_buffer::Event::Reparsed(buffer_id) => {
20332 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20333 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20334
20335 cx.emit(EditorEvent::Reparsed(*buffer_id));
20336 }
20337 multi_buffer::Event::DiffHunksToggled => {
20338 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20339 }
20340 multi_buffer::Event::LanguageChanged(buffer_id) => {
20341 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20342 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20343 cx.emit(EditorEvent::Reparsed(*buffer_id));
20344 cx.notify();
20345 }
20346 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20347 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20348 multi_buffer::Event::FileHandleChanged
20349 | multi_buffer::Event::Reloaded
20350 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20351 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
20352 multi_buffer::Event::DiagnosticsUpdated => {
20353 self.update_diagnostics_state(window, cx);
20354 }
20355 _ => {}
20356 };
20357 }
20358
20359 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20360 if !self.diagnostics_enabled() {
20361 return;
20362 }
20363 self.refresh_active_diagnostics(cx);
20364 self.refresh_inline_diagnostics(true, window, cx);
20365 self.scrollbar_marker_state.dirty = true;
20366 cx.notify();
20367 }
20368
20369 pub fn start_temporary_diff_override(&mut self) {
20370 self.load_diff_task.take();
20371 self.temporary_diff_override = true;
20372 }
20373
20374 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20375 self.temporary_diff_override = false;
20376 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20377 self.buffer.update(cx, |buffer, cx| {
20378 buffer.set_all_diff_hunks_collapsed(cx);
20379 });
20380
20381 if let Some(project) = self.project.clone() {
20382 self.load_diff_task = Some(
20383 update_uncommitted_diff_for_buffer(
20384 cx.entity(),
20385 &project,
20386 self.buffer.read(cx).all_buffers(),
20387 self.buffer.clone(),
20388 cx,
20389 )
20390 .shared(),
20391 );
20392 }
20393 }
20394
20395 fn on_display_map_changed(
20396 &mut self,
20397 _: Entity<DisplayMap>,
20398 _: &mut Window,
20399 cx: &mut Context<Self>,
20400 ) {
20401 cx.notify();
20402 }
20403
20404 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20405 if self.diagnostics_enabled() {
20406 let new_severity = EditorSettings::get_global(cx)
20407 .diagnostics_max_severity
20408 .unwrap_or(DiagnosticSeverity::Hint);
20409 self.set_max_diagnostics_severity(new_severity, cx);
20410 }
20411 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20412 self.update_edit_prediction_settings(cx);
20413 self.refresh_edit_prediction(true, false, window, cx);
20414 self.refresh_inline_values(cx);
20415 self.refresh_inlay_hints(
20416 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20417 self.selections.newest_anchor().head(),
20418 &self.buffer.read(cx).snapshot(cx),
20419 cx,
20420 )),
20421 cx,
20422 );
20423
20424 let old_cursor_shape = self.cursor_shape;
20425 let old_show_breadcrumbs = self.show_breadcrumbs;
20426
20427 {
20428 let editor_settings = EditorSettings::get_global(cx);
20429 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20430 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20431 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20432 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20433 }
20434
20435 if old_cursor_shape != self.cursor_shape {
20436 cx.emit(EditorEvent::CursorShapeChanged);
20437 }
20438
20439 if old_show_breadcrumbs != self.show_breadcrumbs {
20440 cx.emit(EditorEvent::BreadcrumbsChanged);
20441 }
20442
20443 let project_settings = ProjectSettings::get_global(cx);
20444 self.serialize_dirty_buffers =
20445 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20446
20447 if self.mode.is_full() {
20448 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20449 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20450 if self.show_inline_diagnostics != show_inline_diagnostics {
20451 self.show_inline_diagnostics = show_inline_diagnostics;
20452 self.refresh_inline_diagnostics(false, window, cx);
20453 }
20454
20455 if self.git_blame_inline_enabled != inline_blame_enabled {
20456 self.toggle_git_blame_inline_internal(false, window, cx);
20457 }
20458
20459 let minimap_settings = EditorSettings::get_global(cx).minimap;
20460 if self.minimap_visibility != MinimapVisibility::Disabled {
20461 if self.minimap_visibility.settings_visibility()
20462 != minimap_settings.minimap_enabled()
20463 {
20464 self.set_minimap_visibility(
20465 MinimapVisibility::for_mode(self.mode(), cx),
20466 window,
20467 cx,
20468 );
20469 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20470 minimap_entity.update(cx, |minimap_editor, cx| {
20471 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20472 })
20473 }
20474 }
20475 }
20476
20477 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20478 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20479 }) {
20480 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20481 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20482 }
20483 self.refresh_colors(false, None, window, cx);
20484 }
20485
20486 cx.notify();
20487 }
20488
20489 pub fn set_searchable(&mut self, searchable: bool) {
20490 self.searchable = searchable;
20491 }
20492
20493 pub fn searchable(&self) -> bool {
20494 self.searchable
20495 }
20496
20497 fn open_proposed_changes_editor(
20498 &mut self,
20499 _: &OpenProposedChangesEditor,
20500 window: &mut Window,
20501 cx: &mut Context<Self>,
20502 ) {
20503 let Some(workspace) = self.workspace() else {
20504 cx.propagate();
20505 return;
20506 };
20507
20508 let selections = self.selections.all::<usize>(cx);
20509 let multi_buffer = self.buffer.read(cx);
20510 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20511 let mut new_selections_by_buffer = HashMap::default();
20512 for selection in selections {
20513 for (buffer, range, _) in
20514 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20515 {
20516 let mut range = range.to_point(buffer);
20517 range.start.column = 0;
20518 range.end.column = buffer.line_len(range.end.row);
20519 new_selections_by_buffer
20520 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20521 .or_insert(Vec::new())
20522 .push(range)
20523 }
20524 }
20525
20526 let proposed_changes_buffers = new_selections_by_buffer
20527 .into_iter()
20528 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20529 .collect::<Vec<_>>();
20530 let proposed_changes_editor = cx.new(|cx| {
20531 ProposedChangesEditor::new(
20532 "Proposed changes",
20533 proposed_changes_buffers,
20534 self.project.clone(),
20535 window,
20536 cx,
20537 )
20538 });
20539
20540 window.defer(cx, move |window, cx| {
20541 workspace.update(cx, |workspace, cx| {
20542 workspace.active_pane().update(cx, |pane, cx| {
20543 pane.add_item(
20544 Box::new(proposed_changes_editor),
20545 true,
20546 true,
20547 None,
20548 window,
20549 cx,
20550 );
20551 });
20552 });
20553 });
20554 }
20555
20556 pub fn open_excerpts_in_split(
20557 &mut self,
20558 _: &OpenExcerptsSplit,
20559 window: &mut Window,
20560 cx: &mut Context<Self>,
20561 ) {
20562 self.open_excerpts_common(None, true, window, cx)
20563 }
20564
20565 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20566 self.open_excerpts_common(None, false, window, cx)
20567 }
20568
20569 fn open_excerpts_common(
20570 &mut self,
20571 jump_data: Option<JumpData>,
20572 split: bool,
20573 window: &mut Window,
20574 cx: &mut Context<Self>,
20575 ) {
20576 let Some(workspace) = self.workspace() else {
20577 cx.propagate();
20578 return;
20579 };
20580
20581 if self.buffer.read(cx).is_singleton() {
20582 cx.propagate();
20583 return;
20584 }
20585
20586 let mut new_selections_by_buffer = HashMap::default();
20587 match &jump_data {
20588 Some(JumpData::MultiBufferPoint {
20589 excerpt_id,
20590 position,
20591 anchor,
20592 line_offset_from_top,
20593 }) => {
20594 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20595 if let Some(buffer) = multi_buffer_snapshot
20596 .buffer_id_for_excerpt(*excerpt_id)
20597 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20598 {
20599 let buffer_snapshot = buffer.read(cx).snapshot();
20600 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20601 language::ToPoint::to_point(anchor, &buffer_snapshot)
20602 } else {
20603 buffer_snapshot.clip_point(*position, Bias::Left)
20604 };
20605 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20606 new_selections_by_buffer.insert(
20607 buffer,
20608 (
20609 vec![jump_to_offset..jump_to_offset],
20610 Some(*line_offset_from_top),
20611 ),
20612 );
20613 }
20614 }
20615 Some(JumpData::MultiBufferRow {
20616 row,
20617 line_offset_from_top,
20618 }) => {
20619 let point = MultiBufferPoint::new(row.0, 0);
20620 if let Some((buffer, buffer_point, _)) =
20621 self.buffer.read(cx).point_to_buffer_point(point, cx)
20622 {
20623 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20624 new_selections_by_buffer
20625 .entry(buffer)
20626 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20627 .0
20628 .push(buffer_offset..buffer_offset)
20629 }
20630 }
20631 None => {
20632 let selections = self.selections.all::<usize>(cx);
20633 let multi_buffer = self.buffer.read(cx);
20634 for selection in selections {
20635 for (snapshot, range, _, anchor) in multi_buffer
20636 .snapshot(cx)
20637 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20638 {
20639 if let Some(anchor) = anchor {
20640 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20641 else {
20642 continue;
20643 };
20644 let offset = text::ToOffset::to_offset(
20645 &anchor.text_anchor,
20646 &buffer_handle.read(cx).snapshot(),
20647 );
20648 let range = offset..offset;
20649 new_selections_by_buffer
20650 .entry(buffer_handle)
20651 .or_insert((Vec::new(), None))
20652 .0
20653 .push(range)
20654 } else {
20655 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20656 else {
20657 continue;
20658 };
20659 new_selections_by_buffer
20660 .entry(buffer_handle)
20661 .or_insert((Vec::new(), None))
20662 .0
20663 .push(range)
20664 }
20665 }
20666 }
20667 }
20668 }
20669
20670 new_selections_by_buffer
20671 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20672
20673 if new_selections_by_buffer.is_empty() {
20674 return;
20675 }
20676
20677 // We defer the pane interaction because we ourselves are a workspace item
20678 // and activating a new item causes the pane to call a method on us reentrantly,
20679 // which panics if we're on the stack.
20680 window.defer(cx, move |window, cx| {
20681 workspace.update(cx, |workspace, cx| {
20682 let pane = if split {
20683 workspace.adjacent_pane(window, cx)
20684 } else {
20685 workspace.active_pane().clone()
20686 };
20687
20688 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20689 let editor = buffer
20690 .read(cx)
20691 .file()
20692 .is_none()
20693 .then(|| {
20694 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20695 // so `workspace.open_project_item` will never find them, always opening a new editor.
20696 // Instead, we try to activate the existing editor in the pane first.
20697 let (editor, pane_item_index) =
20698 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20699 let editor = item.downcast::<Editor>()?;
20700 let singleton_buffer =
20701 editor.read(cx).buffer().read(cx).as_singleton()?;
20702 if singleton_buffer == buffer {
20703 Some((editor, i))
20704 } else {
20705 None
20706 }
20707 })?;
20708 pane.update(cx, |pane, cx| {
20709 pane.activate_item(pane_item_index, true, true, window, cx)
20710 });
20711 Some(editor)
20712 })
20713 .flatten()
20714 .unwrap_or_else(|| {
20715 workspace.open_project_item::<Self>(
20716 pane.clone(),
20717 buffer,
20718 true,
20719 true,
20720 window,
20721 cx,
20722 )
20723 });
20724
20725 editor.update(cx, |editor, cx| {
20726 let autoscroll = match scroll_offset {
20727 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20728 None => Autoscroll::newest(),
20729 };
20730 let nav_history = editor.nav_history.take();
20731 editor.change_selections(
20732 SelectionEffects::scroll(autoscroll),
20733 window,
20734 cx,
20735 |s| {
20736 s.select_ranges(ranges);
20737 },
20738 );
20739 editor.nav_history = nav_history;
20740 });
20741 }
20742 })
20743 });
20744 }
20745
20746 // For now, don't allow opening excerpts in buffers that aren't backed by
20747 // regular project files.
20748 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20749 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20750 }
20751
20752 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20753 let snapshot = self.buffer.read(cx).read(cx);
20754 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20755 Some(
20756 ranges
20757 .iter()
20758 .map(move |range| {
20759 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20760 })
20761 .collect(),
20762 )
20763 }
20764
20765 fn selection_replacement_ranges(
20766 &self,
20767 range: Range<OffsetUtf16>,
20768 cx: &mut App,
20769 ) -> Vec<Range<OffsetUtf16>> {
20770 let selections = self.selections.all::<OffsetUtf16>(cx);
20771 let newest_selection = selections
20772 .iter()
20773 .max_by_key(|selection| selection.id)
20774 .unwrap();
20775 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20776 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20777 let snapshot = self.buffer.read(cx).read(cx);
20778 selections
20779 .into_iter()
20780 .map(|mut selection| {
20781 selection.start.0 =
20782 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20783 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20784 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20785 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20786 })
20787 .collect()
20788 }
20789
20790 fn report_editor_event(
20791 &self,
20792 reported_event: ReportEditorEvent,
20793 file_extension: Option<String>,
20794 cx: &App,
20795 ) {
20796 if cfg!(any(test, feature = "test-support")) {
20797 return;
20798 }
20799
20800 let Some(project) = &self.project else { return };
20801
20802 // If None, we are in a file without an extension
20803 let file = self
20804 .buffer
20805 .read(cx)
20806 .as_singleton()
20807 .and_then(|b| b.read(cx).file());
20808 let file_extension = file_extension.or(file
20809 .as_ref()
20810 .and_then(|file| Path::new(file.file_name(cx)).extension())
20811 .and_then(|e| e.to_str())
20812 .map(|a| a.to_string()));
20813
20814 let vim_mode = vim_enabled(cx);
20815
20816 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20817 let copilot_enabled = edit_predictions_provider
20818 == language::language_settings::EditPredictionProvider::Copilot;
20819 let copilot_enabled_for_language = self
20820 .buffer
20821 .read(cx)
20822 .language_settings(cx)
20823 .show_edit_predictions;
20824
20825 let project = project.read(cx);
20826 let event_type = reported_event.event_type();
20827
20828 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20829 telemetry::event!(
20830 event_type,
20831 type = if auto_saved {"autosave"} else {"manual"},
20832 file_extension,
20833 vim_mode,
20834 copilot_enabled,
20835 copilot_enabled_for_language,
20836 edit_predictions_provider,
20837 is_via_ssh = project.is_via_remote_server(),
20838 );
20839 } else {
20840 telemetry::event!(
20841 event_type,
20842 file_extension,
20843 vim_mode,
20844 copilot_enabled,
20845 copilot_enabled_for_language,
20846 edit_predictions_provider,
20847 is_via_ssh = project.is_via_remote_server(),
20848 );
20849 };
20850 }
20851
20852 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20853 /// with each line being an array of {text, highlight} objects.
20854 fn copy_highlight_json(
20855 &mut self,
20856 _: &CopyHighlightJson,
20857 window: &mut Window,
20858 cx: &mut Context<Self>,
20859 ) {
20860 #[derive(Serialize)]
20861 struct Chunk<'a> {
20862 text: String,
20863 highlight: Option<&'a str>,
20864 }
20865
20866 let snapshot = self.buffer.read(cx).snapshot(cx);
20867 let range = self
20868 .selected_text_range(false, window, cx)
20869 .and_then(|selection| {
20870 if selection.range.is_empty() {
20871 None
20872 } else {
20873 Some(selection.range)
20874 }
20875 })
20876 .unwrap_or_else(|| 0..snapshot.len());
20877
20878 let chunks = snapshot.chunks(range, true);
20879 let mut lines = Vec::new();
20880 let mut line: VecDeque<Chunk> = VecDeque::new();
20881
20882 let Some(style) = self.style.as_ref() else {
20883 return;
20884 };
20885
20886 for chunk in chunks {
20887 let highlight = chunk
20888 .syntax_highlight_id
20889 .and_then(|id| id.name(&style.syntax));
20890 let mut chunk_lines = chunk.text.split('\n').peekable();
20891 while let Some(text) = chunk_lines.next() {
20892 let mut merged_with_last_token = false;
20893 if let Some(last_token) = line.back_mut()
20894 && last_token.highlight == highlight
20895 {
20896 last_token.text.push_str(text);
20897 merged_with_last_token = true;
20898 }
20899
20900 if !merged_with_last_token {
20901 line.push_back(Chunk {
20902 text: text.into(),
20903 highlight,
20904 });
20905 }
20906
20907 if chunk_lines.peek().is_some() {
20908 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20909 line.pop_front();
20910 }
20911 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20912 line.pop_back();
20913 }
20914
20915 lines.push(mem::take(&mut line));
20916 }
20917 }
20918 }
20919
20920 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20921 return;
20922 };
20923 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20924 }
20925
20926 pub fn open_context_menu(
20927 &mut self,
20928 _: &OpenContextMenu,
20929 window: &mut Window,
20930 cx: &mut Context<Self>,
20931 ) {
20932 self.request_autoscroll(Autoscroll::newest(), cx);
20933 let position = self.selections.newest_display(cx).start;
20934 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20935 }
20936
20937 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20938 &self.inlay_hint_cache
20939 }
20940
20941 pub fn replay_insert_event(
20942 &mut self,
20943 text: &str,
20944 relative_utf16_range: Option<Range<isize>>,
20945 window: &mut Window,
20946 cx: &mut Context<Self>,
20947 ) {
20948 if !self.input_enabled {
20949 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20950 return;
20951 }
20952 if let Some(relative_utf16_range) = relative_utf16_range {
20953 let selections = self.selections.all::<OffsetUtf16>(cx);
20954 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20955 let new_ranges = selections.into_iter().map(|range| {
20956 let start = OffsetUtf16(
20957 range
20958 .head()
20959 .0
20960 .saturating_add_signed(relative_utf16_range.start),
20961 );
20962 let end = OffsetUtf16(
20963 range
20964 .head()
20965 .0
20966 .saturating_add_signed(relative_utf16_range.end),
20967 );
20968 start..end
20969 });
20970 s.select_ranges(new_ranges);
20971 });
20972 }
20973
20974 self.handle_input(text, window, cx);
20975 }
20976
20977 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20978 let Some(provider) = self.semantics_provider.as_ref() else {
20979 return false;
20980 };
20981
20982 let mut supports = false;
20983 self.buffer().update(cx, |this, cx| {
20984 this.for_each_buffer(|buffer| {
20985 supports |= provider.supports_inlay_hints(buffer, cx);
20986 });
20987 });
20988
20989 supports
20990 }
20991
20992 pub fn is_focused(&self, window: &Window) -> bool {
20993 self.focus_handle.is_focused(window)
20994 }
20995
20996 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20997 cx.emit(EditorEvent::Focused);
20998
20999 if let Some(descendant) = self
21000 .last_focused_descendant
21001 .take()
21002 .and_then(|descendant| descendant.upgrade())
21003 {
21004 window.focus(&descendant);
21005 } else {
21006 if let Some(blame) = self.blame.as_ref() {
21007 blame.update(cx, GitBlame::focus)
21008 }
21009
21010 self.blink_manager.update(cx, BlinkManager::enable);
21011 self.show_cursor_names(window, cx);
21012 self.buffer.update(cx, |buffer, cx| {
21013 buffer.finalize_last_transaction(cx);
21014 if self.leader_id.is_none() {
21015 buffer.set_active_selections(
21016 &self.selections.disjoint_anchors(),
21017 self.selections.line_mode,
21018 self.cursor_shape,
21019 cx,
21020 );
21021 }
21022 });
21023 }
21024 }
21025
21026 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21027 cx.emit(EditorEvent::FocusedIn)
21028 }
21029
21030 fn handle_focus_out(
21031 &mut self,
21032 event: FocusOutEvent,
21033 _window: &mut Window,
21034 cx: &mut Context<Self>,
21035 ) {
21036 if event.blurred != self.focus_handle {
21037 self.last_focused_descendant = Some(event.blurred);
21038 }
21039 self.selection_drag_state = SelectionDragState::None;
21040 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21041 }
21042
21043 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21044 self.blink_manager.update(cx, BlinkManager::disable);
21045 self.buffer
21046 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21047
21048 if let Some(blame) = self.blame.as_ref() {
21049 blame.update(cx, GitBlame::blur)
21050 }
21051 if !self.hover_state.focused(window, cx) {
21052 hide_hover(self, cx);
21053 }
21054 if !self
21055 .context_menu
21056 .borrow()
21057 .as_ref()
21058 .is_some_and(|context_menu| context_menu.focused(window, cx))
21059 {
21060 self.hide_context_menu(window, cx);
21061 }
21062 self.discard_edit_prediction(false, cx);
21063 cx.emit(EditorEvent::Blurred);
21064 cx.notify();
21065 }
21066
21067 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21068 let mut pending: String = window
21069 .pending_input_keystrokes()
21070 .into_iter()
21071 .flatten()
21072 .filter_map(|keystroke| {
21073 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21074 keystroke.key_char.clone()
21075 } else {
21076 None
21077 }
21078 })
21079 .collect();
21080
21081 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21082 pending = "".to_string();
21083 }
21084
21085 let existing_pending = self
21086 .text_highlights::<PendingInput>(cx)
21087 .map(|(_, ranges)| ranges.to_vec());
21088 if existing_pending.is_none() && pending.is_empty() {
21089 return;
21090 }
21091 let transaction =
21092 self.transact(window, cx, |this, window, cx| {
21093 let selections = this.selections.all::<usize>(cx);
21094 let edits = selections
21095 .iter()
21096 .map(|selection| (selection.end..selection.end, pending.clone()));
21097 this.edit(edits, cx);
21098 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21099 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21100 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21101 }));
21102 });
21103 if let Some(existing_ranges) = existing_pending {
21104 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21105 this.edit(edits, cx);
21106 }
21107 });
21108
21109 let snapshot = self.snapshot(window, cx);
21110 let ranges = self
21111 .selections
21112 .all::<usize>(cx)
21113 .into_iter()
21114 .map(|selection| {
21115 snapshot.buffer_snapshot.anchor_after(selection.end)
21116 ..snapshot
21117 .buffer_snapshot
21118 .anchor_before(selection.end + pending.len())
21119 })
21120 .collect();
21121
21122 if pending.is_empty() {
21123 self.clear_highlights::<PendingInput>(cx);
21124 } else {
21125 self.highlight_text::<PendingInput>(
21126 ranges,
21127 HighlightStyle {
21128 underline: Some(UnderlineStyle {
21129 thickness: px(1.),
21130 color: None,
21131 wavy: false,
21132 }),
21133 ..Default::default()
21134 },
21135 cx,
21136 );
21137 }
21138
21139 self.ime_transaction = self.ime_transaction.or(transaction);
21140 if let Some(transaction) = self.ime_transaction {
21141 self.buffer.update(cx, |buffer, cx| {
21142 buffer.group_until_transaction(transaction, cx);
21143 });
21144 }
21145
21146 if self.text_highlights::<PendingInput>(cx).is_none() {
21147 self.ime_transaction.take();
21148 }
21149 }
21150
21151 pub fn register_action_renderer(
21152 &mut self,
21153 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21154 ) -> Subscription {
21155 let id = self.next_editor_action_id.post_inc();
21156 self.editor_actions
21157 .borrow_mut()
21158 .insert(id, Box::new(listener));
21159
21160 let editor_actions = self.editor_actions.clone();
21161 Subscription::new(move || {
21162 editor_actions.borrow_mut().remove(&id);
21163 })
21164 }
21165
21166 pub fn register_action<A: Action>(
21167 &mut self,
21168 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21169 ) -> Subscription {
21170 let id = self.next_editor_action_id.post_inc();
21171 let listener = Arc::new(listener);
21172 self.editor_actions.borrow_mut().insert(
21173 id,
21174 Box::new(move |_, window, _| {
21175 let listener = listener.clone();
21176 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21177 let action = action.downcast_ref().unwrap();
21178 if phase == DispatchPhase::Bubble {
21179 listener(action, window, cx)
21180 }
21181 })
21182 }),
21183 );
21184
21185 let editor_actions = self.editor_actions.clone();
21186 Subscription::new(move || {
21187 editor_actions.borrow_mut().remove(&id);
21188 })
21189 }
21190
21191 pub fn file_header_size(&self) -> u32 {
21192 FILE_HEADER_HEIGHT
21193 }
21194
21195 pub fn restore(
21196 &mut self,
21197 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21198 window: &mut Window,
21199 cx: &mut Context<Self>,
21200 ) {
21201 let workspace = self.workspace();
21202 let project = self.project();
21203 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21204 let mut tasks = Vec::new();
21205 for (buffer_id, changes) in revert_changes {
21206 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21207 buffer.update(cx, |buffer, cx| {
21208 buffer.edit(
21209 changes
21210 .into_iter()
21211 .map(|(range, text)| (range, text.to_string())),
21212 None,
21213 cx,
21214 );
21215 });
21216
21217 if let Some(project) =
21218 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21219 {
21220 project.update(cx, |project, cx| {
21221 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21222 })
21223 }
21224 }
21225 }
21226 tasks
21227 });
21228 cx.spawn_in(window, async move |_, cx| {
21229 for (buffer, task) in save_tasks {
21230 let result = task.await;
21231 if result.is_err() {
21232 let Some(path) = buffer
21233 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21234 .ok()
21235 else {
21236 continue;
21237 };
21238 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21239 let Some(task) = cx
21240 .update_window_entity(workspace, |workspace, window, cx| {
21241 workspace
21242 .open_path_preview(path, None, false, false, false, window, cx)
21243 })
21244 .ok()
21245 else {
21246 continue;
21247 };
21248 task.await.log_err();
21249 }
21250 }
21251 }
21252 })
21253 .detach();
21254 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21255 selections.refresh()
21256 });
21257 }
21258
21259 pub fn to_pixel_point(
21260 &self,
21261 source: multi_buffer::Anchor,
21262 editor_snapshot: &EditorSnapshot,
21263 window: &mut Window,
21264 ) -> Option<gpui::Point<Pixels>> {
21265 let source_point = source.to_display_point(editor_snapshot);
21266 self.display_to_pixel_point(source_point, editor_snapshot, window)
21267 }
21268
21269 pub fn display_to_pixel_point(
21270 &self,
21271 source: DisplayPoint,
21272 editor_snapshot: &EditorSnapshot,
21273 window: &mut Window,
21274 ) -> Option<gpui::Point<Pixels>> {
21275 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21276 let text_layout_details = self.text_layout_details(window);
21277 let scroll_top = text_layout_details
21278 .scroll_anchor
21279 .scroll_position(editor_snapshot)
21280 .y;
21281
21282 if source.row().as_f32() < scroll_top.floor() {
21283 return None;
21284 }
21285 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21286 let source_y = line_height * (source.row().as_f32() - scroll_top);
21287 Some(gpui::Point::new(source_x, source_y))
21288 }
21289
21290 pub fn has_visible_completions_menu(&self) -> bool {
21291 !self.edit_prediction_preview_is_active()
21292 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21293 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21294 })
21295 }
21296
21297 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21298 if self.mode.is_minimap() {
21299 return;
21300 }
21301 self.addons
21302 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21303 }
21304
21305 pub fn unregister_addon<T: Addon>(&mut self) {
21306 self.addons.remove(&std::any::TypeId::of::<T>());
21307 }
21308
21309 pub fn addon<T: Addon>(&self) -> Option<&T> {
21310 let type_id = std::any::TypeId::of::<T>();
21311 self.addons
21312 .get(&type_id)
21313 .and_then(|item| item.to_any().downcast_ref::<T>())
21314 }
21315
21316 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21317 let type_id = std::any::TypeId::of::<T>();
21318 self.addons
21319 .get_mut(&type_id)
21320 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21321 }
21322
21323 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21324 let text_layout_details = self.text_layout_details(window);
21325 let style = &text_layout_details.editor_style;
21326 let font_id = window.text_system().resolve_font(&style.text.font());
21327 let font_size = style.text.font_size.to_pixels(window.rem_size());
21328 let line_height = style.text.line_height_in_pixels(window.rem_size());
21329 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21330 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21331
21332 CharacterDimensions {
21333 em_width,
21334 em_advance,
21335 line_height,
21336 }
21337 }
21338
21339 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21340 self.load_diff_task.clone()
21341 }
21342
21343 fn read_metadata_from_db(
21344 &mut self,
21345 item_id: u64,
21346 workspace_id: WorkspaceId,
21347 window: &mut Window,
21348 cx: &mut Context<Editor>,
21349 ) {
21350 if self.is_singleton(cx)
21351 && !self.mode.is_minimap()
21352 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21353 {
21354 let buffer_snapshot = OnceCell::new();
21355
21356 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21357 && !folds.is_empty()
21358 {
21359 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21360 self.fold_ranges(
21361 folds
21362 .into_iter()
21363 .map(|(start, end)| {
21364 snapshot.clip_offset(start, Bias::Left)
21365 ..snapshot.clip_offset(end, Bias::Right)
21366 })
21367 .collect(),
21368 false,
21369 window,
21370 cx,
21371 );
21372 }
21373
21374 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21375 && !selections.is_empty()
21376 {
21377 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21378 // skip adding the initial selection to selection history
21379 self.selection_history.mode = SelectionHistoryMode::Skipping;
21380 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21381 s.select_ranges(selections.into_iter().map(|(start, end)| {
21382 snapshot.clip_offset(start, Bias::Left)
21383 ..snapshot.clip_offset(end, Bias::Right)
21384 }));
21385 });
21386 self.selection_history.mode = SelectionHistoryMode::Normal;
21387 };
21388 }
21389
21390 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21391 }
21392
21393 fn update_lsp_data(
21394 &mut self,
21395 ignore_cache: bool,
21396 for_buffer: Option<BufferId>,
21397 window: &mut Window,
21398 cx: &mut Context<'_, Self>,
21399 ) {
21400 self.pull_diagnostics(for_buffer, window, cx);
21401 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21402 }
21403}
21404
21405fn vim_enabled(cx: &App) -> bool {
21406 cx.global::<SettingsStore>()
21407 .raw_user_settings()
21408 .get("vim_mode")
21409 == Some(&serde_json::Value::Bool(true))
21410}
21411
21412fn process_completion_for_edit(
21413 completion: &Completion,
21414 intent: CompletionIntent,
21415 buffer: &Entity<Buffer>,
21416 cursor_position: &text::Anchor,
21417 cx: &mut Context<Editor>,
21418) -> CompletionEdit {
21419 let buffer = buffer.read(cx);
21420 let buffer_snapshot = buffer.snapshot();
21421 let (snippet, new_text) = if completion.is_snippet() {
21422 // Workaround for typescript language server issues so that methods don't expand within
21423 // strings and functions with type expressions. The previous point is used because the query
21424 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21425 let mut snippet_source = completion.new_text.clone();
21426 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21427 previous_point.column = previous_point.column.saturating_sub(1);
21428 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21429 && scope.prefers_label_for_snippet_in_completion()
21430 && let Some(label) = completion.label()
21431 && matches!(
21432 completion.kind(),
21433 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21434 )
21435 {
21436 snippet_source = label;
21437 }
21438 match Snippet::parse(&snippet_source).log_err() {
21439 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21440 None => (None, completion.new_text.clone()),
21441 }
21442 } else {
21443 (None, completion.new_text.clone())
21444 };
21445
21446 let mut range_to_replace = {
21447 let replace_range = &completion.replace_range;
21448 if let CompletionSource::Lsp {
21449 insert_range: Some(insert_range),
21450 ..
21451 } = &completion.source
21452 {
21453 debug_assert_eq!(
21454 insert_range.start, replace_range.start,
21455 "insert_range and replace_range should start at the same position"
21456 );
21457 debug_assert!(
21458 insert_range
21459 .start
21460 .cmp(cursor_position, &buffer_snapshot)
21461 .is_le(),
21462 "insert_range should start before or at cursor position"
21463 );
21464 debug_assert!(
21465 replace_range
21466 .start
21467 .cmp(cursor_position, &buffer_snapshot)
21468 .is_le(),
21469 "replace_range should start before or at cursor position"
21470 );
21471
21472 let should_replace = match intent {
21473 CompletionIntent::CompleteWithInsert => false,
21474 CompletionIntent::CompleteWithReplace => true,
21475 CompletionIntent::Complete | CompletionIntent::Compose => {
21476 let insert_mode =
21477 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21478 .completions
21479 .lsp_insert_mode;
21480 match insert_mode {
21481 LspInsertMode::Insert => false,
21482 LspInsertMode::Replace => true,
21483 LspInsertMode::ReplaceSubsequence => {
21484 let mut text_to_replace = buffer.chars_for_range(
21485 buffer.anchor_before(replace_range.start)
21486 ..buffer.anchor_after(replace_range.end),
21487 );
21488 let mut current_needle = text_to_replace.next();
21489 for haystack_ch in completion.label.text.chars() {
21490 if let Some(needle_ch) = current_needle
21491 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21492 {
21493 current_needle = text_to_replace.next();
21494 }
21495 }
21496 current_needle.is_none()
21497 }
21498 LspInsertMode::ReplaceSuffix => {
21499 if replace_range
21500 .end
21501 .cmp(cursor_position, &buffer_snapshot)
21502 .is_gt()
21503 {
21504 let range_after_cursor = *cursor_position..replace_range.end;
21505 let text_after_cursor = buffer
21506 .text_for_range(
21507 buffer.anchor_before(range_after_cursor.start)
21508 ..buffer.anchor_after(range_after_cursor.end),
21509 )
21510 .collect::<String>()
21511 .to_ascii_lowercase();
21512 completion
21513 .label
21514 .text
21515 .to_ascii_lowercase()
21516 .ends_with(&text_after_cursor)
21517 } else {
21518 true
21519 }
21520 }
21521 }
21522 }
21523 };
21524
21525 if should_replace {
21526 replace_range.clone()
21527 } else {
21528 insert_range.clone()
21529 }
21530 } else {
21531 replace_range.clone()
21532 }
21533 };
21534
21535 if range_to_replace
21536 .end
21537 .cmp(cursor_position, &buffer_snapshot)
21538 .is_lt()
21539 {
21540 range_to_replace.end = *cursor_position;
21541 }
21542
21543 CompletionEdit {
21544 new_text,
21545 replace_range: range_to_replace.to_offset(buffer),
21546 snippet,
21547 }
21548}
21549
21550struct CompletionEdit {
21551 new_text: String,
21552 replace_range: Range<usize>,
21553 snippet: Option<Snippet>,
21554}
21555
21556fn insert_extra_newline_brackets(
21557 buffer: &MultiBufferSnapshot,
21558 range: Range<usize>,
21559 language: &language::LanguageScope,
21560) -> bool {
21561 let leading_whitespace_len = buffer
21562 .reversed_chars_at(range.start)
21563 .take_while(|c| c.is_whitespace() && *c != '\n')
21564 .map(|c| c.len_utf8())
21565 .sum::<usize>();
21566 let trailing_whitespace_len = buffer
21567 .chars_at(range.end)
21568 .take_while(|c| c.is_whitespace() && *c != '\n')
21569 .map(|c| c.len_utf8())
21570 .sum::<usize>();
21571 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21572
21573 language.brackets().any(|(pair, enabled)| {
21574 let pair_start = pair.start.trim_end();
21575 let pair_end = pair.end.trim_start();
21576
21577 enabled
21578 && pair.newline
21579 && buffer.contains_str_at(range.end, pair_end)
21580 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21581 })
21582}
21583
21584fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21585 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21586 [(buffer, range, _)] => (*buffer, range.clone()),
21587 _ => return false,
21588 };
21589 let pair = {
21590 let mut result: Option<BracketMatch> = None;
21591
21592 for pair in buffer
21593 .all_bracket_ranges(range.clone())
21594 .filter(move |pair| {
21595 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21596 })
21597 {
21598 let len = pair.close_range.end - pair.open_range.start;
21599
21600 if let Some(existing) = &result {
21601 let existing_len = existing.close_range.end - existing.open_range.start;
21602 if len > existing_len {
21603 continue;
21604 }
21605 }
21606
21607 result = Some(pair);
21608 }
21609
21610 result
21611 };
21612 let Some(pair) = pair else {
21613 return false;
21614 };
21615 pair.newline_only
21616 && buffer
21617 .chars_for_range(pair.open_range.end..range.start)
21618 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21619 .all(|c| c.is_whitespace() && c != '\n')
21620}
21621
21622fn update_uncommitted_diff_for_buffer(
21623 editor: Entity<Editor>,
21624 project: &Entity<Project>,
21625 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21626 buffer: Entity<MultiBuffer>,
21627 cx: &mut App,
21628) -> Task<()> {
21629 let mut tasks = Vec::new();
21630 project.update(cx, |project, cx| {
21631 for buffer in buffers {
21632 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21633 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21634 }
21635 }
21636 });
21637 cx.spawn(async move |cx| {
21638 let diffs = future::join_all(tasks).await;
21639 if editor
21640 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21641 .unwrap_or(false)
21642 {
21643 return;
21644 }
21645
21646 buffer
21647 .update(cx, |buffer, cx| {
21648 for diff in diffs.into_iter().flatten() {
21649 buffer.add_diff(diff, cx);
21650 }
21651 })
21652 .ok();
21653 })
21654}
21655
21656fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21657 let tab_size = tab_size.get() as usize;
21658 let mut width = offset;
21659
21660 for ch in text.chars() {
21661 width += if ch == '\t' {
21662 tab_size - (width % tab_size)
21663 } else {
21664 1
21665 };
21666 }
21667
21668 width - offset
21669}
21670
21671#[cfg(test)]
21672mod tests {
21673 use super::*;
21674
21675 #[test]
21676 fn test_string_size_with_expanded_tabs() {
21677 let nz = |val| NonZeroU32::new(val).unwrap();
21678 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21679 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21680 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21681 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21682 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21683 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21684 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21685 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21686 }
21687}
21688
21689/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21690struct WordBreakingTokenizer<'a> {
21691 input: &'a str,
21692}
21693
21694impl<'a> WordBreakingTokenizer<'a> {
21695 fn new(input: &'a str) -> Self {
21696 Self { input }
21697 }
21698}
21699
21700fn is_char_ideographic(ch: char) -> bool {
21701 use unicode_script::Script::*;
21702 use unicode_script::UnicodeScript;
21703 matches!(ch.script(), Han | Tangut | Yi)
21704}
21705
21706fn is_grapheme_ideographic(text: &str) -> bool {
21707 text.chars().any(is_char_ideographic)
21708}
21709
21710fn is_grapheme_whitespace(text: &str) -> bool {
21711 text.chars().any(|x| x.is_whitespace())
21712}
21713
21714fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21715 text.chars()
21716 .next()
21717 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21718}
21719
21720#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21721enum WordBreakToken<'a> {
21722 Word { token: &'a str, grapheme_len: usize },
21723 InlineWhitespace { token: &'a str, grapheme_len: usize },
21724 Newline,
21725}
21726
21727impl<'a> Iterator for WordBreakingTokenizer<'a> {
21728 /// Yields a span, the count of graphemes in the token, and whether it was
21729 /// whitespace. Note that it also breaks at word boundaries.
21730 type Item = WordBreakToken<'a>;
21731
21732 fn next(&mut self) -> Option<Self::Item> {
21733 use unicode_segmentation::UnicodeSegmentation;
21734 if self.input.is_empty() {
21735 return None;
21736 }
21737
21738 let mut iter = self.input.graphemes(true).peekable();
21739 let mut offset = 0;
21740 let mut grapheme_len = 0;
21741 if let Some(first_grapheme) = iter.next() {
21742 let is_newline = first_grapheme == "\n";
21743 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21744 offset += first_grapheme.len();
21745 grapheme_len += 1;
21746 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21747 if let Some(grapheme) = iter.peek().copied()
21748 && should_stay_with_preceding_ideograph(grapheme)
21749 {
21750 offset += grapheme.len();
21751 grapheme_len += 1;
21752 }
21753 } else {
21754 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21755 let mut next_word_bound = words.peek().copied();
21756 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21757 next_word_bound = words.next();
21758 }
21759 while let Some(grapheme) = iter.peek().copied() {
21760 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21761 break;
21762 };
21763 if is_grapheme_whitespace(grapheme) != is_whitespace
21764 || (grapheme == "\n") != is_newline
21765 {
21766 break;
21767 };
21768 offset += grapheme.len();
21769 grapheme_len += 1;
21770 iter.next();
21771 }
21772 }
21773 let token = &self.input[..offset];
21774 self.input = &self.input[offset..];
21775 if token == "\n" {
21776 Some(WordBreakToken::Newline)
21777 } else if is_whitespace {
21778 Some(WordBreakToken::InlineWhitespace {
21779 token,
21780 grapheme_len,
21781 })
21782 } else {
21783 Some(WordBreakToken::Word {
21784 token,
21785 grapheme_len,
21786 })
21787 }
21788 } else {
21789 None
21790 }
21791 }
21792}
21793
21794#[test]
21795fn test_word_breaking_tokenizer() {
21796 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21797 ("", &[]),
21798 (" ", &[whitespace(" ", 2)]),
21799 ("Ʒ", &[word("Ʒ", 1)]),
21800 ("Ǽ", &[word("Ǽ", 1)]),
21801 ("⋑", &[word("⋑", 1)]),
21802 ("⋑⋑", &[word("⋑⋑", 2)]),
21803 (
21804 "原理,进而",
21805 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21806 ),
21807 (
21808 "hello world",
21809 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21810 ),
21811 (
21812 "hello, world",
21813 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21814 ),
21815 (
21816 " hello world",
21817 &[
21818 whitespace(" ", 2),
21819 word("hello", 5),
21820 whitespace(" ", 1),
21821 word("world", 5),
21822 ],
21823 ),
21824 (
21825 "这是什么 \n 钢笔",
21826 &[
21827 word("这", 1),
21828 word("是", 1),
21829 word("什", 1),
21830 word("么", 1),
21831 whitespace(" ", 1),
21832 newline(),
21833 whitespace(" ", 1),
21834 word("钢", 1),
21835 word("笔", 1),
21836 ],
21837 ),
21838 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21839 ];
21840
21841 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21842 WordBreakToken::Word {
21843 token,
21844 grapheme_len,
21845 }
21846 }
21847
21848 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21849 WordBreakToken::InlineWhitespace {
21850 token,
21851 grapheme_len,
21852 }
21853 }
21854
21855 fn newline() -> WordBreakToken<'static> {
21856 WordBreakToken::Newline
21857 }
21858
21859 for (input, result) in tests {
21860 assert_eq!(
21861 WordBreakingTokenizer::new(input)
21862 .collect::<Vec<_>>()
21863 .as_slice(),
21864 *result,
21865 );
21866 }
21867}
21868
21869fn wrap_with_prefix(
21870 first_line_prefix: String,
21871 subsequent_lines_prefix: String,
21872 unwrapped_text: String,
21873 wrap_column: usize,
21874 tab_size: NonZeroU32,
21875 preserve_existing_whitespace: bool,
21876) -> String {
21877 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21878 let subsequent_lines_prefix_len =
21879 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21880 let mut wrapped_text = String::new();
21881 let mut current_line = first_line_prefix;
21882 let mut is_first_line = true;
21883
21884 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21885 let mut current_line_len = first_line_prefix_len;
21886 let mut in_whitespace = false;
21887 for token in tokenizer {
21888 let have_preceding_whitespace = in_whitespace;
21889 match token {
21890 WordBreakToken::Word {
21891 token,
21892 grapheme_len,
21893 } => {
21894 in_whitespace = false;
21895 let current_prefix_len = if is_first_line {
21896 first_line_prefix_len
21897 } else {
21898 subsequent_lines_prefix_len
21899 };
21900 if current_line_len + grapheme_len > wrap_column
21901 && current_line_len != current_prefix_len
21902 {
21903 wrapped_text.push_str(current_line.trim_end());
21904 wrapped_text.push('\n');
21905 is_first_line = false;
21906 current_line = subsequent_lines_prefix.clone();
21907 current_line_len = subsequent_lines_prefix_len;
21908 }
21909 current_line.push_str(token);
21910 current_line_len += grapheme_len;
21911 }
21912 WordBreakToken::InlineWhitespace {
21913 mut token,
21914 mut grapheme_len,
21915 } => {
21916 in_whitespace = true;
21917 if have_preceding_whitespace && !preserve_existing_whitespace {
21918 continue;
21919 }
21920 if !preserve_existing_whitespace {
21921 token = " ";
21922 grapheme_len = 1;
21923 }
21924 let current_prefix_len = if is_first_line {
21925 first_line_prefix_len
21926 } else {
21927 subsequent_lines_prefix_len
21928 };
21929 if current_line_len + grapheme_len > wrap_column {
21930 wrapped_text.push_str(current_line.trim_end());
21931 wrapped_text.push('\n');
21932 is_first_line = false;
21933 current_line = subsequent_lines_prefix.clone();
21934 current_line_len = subsequent_lines_prefix_len;
21935 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21936 current_line.push_str(token);
21937 current_line_len += grapheme_len;
21938 }
21939 }
21940 WordBreakToken::Newline => {
21941 in_whitespace = true;
21942 let current_prefix_len = if is_first_line {
21943 first_line_prefix_len
21944 } else {
21945 subsequent_lines_prefix_len
21946 };
21947 if preserve_existing_whitespace {
21948 wrapped_text.push_str(current_line.trim_end());
21949 wrapped_text.push('\n');
21950 is_first_line = false;
21951 current_line = subsequent_lines_prefix.clone();
21952 current_line_len = subsequent_lines_prefix_len;
21953 } else if have_preceding_whitespace {
21954 continue;
21955 } else if current_line_len + 1 > wrap_column
21956 && current_line_len != current_prefix_len
21957 {
21958 wrapped_text.push_str(current_line.trim_end());
21959 wrapped_text.push('\n');
21960 is_first_line = false;
21961 current_line = subsequent_lines_prefix.clone();
21962 current_line_len = subsequent_lines_prefix_len;
21963 } else if current_line_len != current_prefix_len {
21964 current_line.push(' ');
21965 current_line_len += 1;
21966 }
21967 }
21968 }
21969 }
21970
21971 if !current_line.is_empty() {
21972 wrapped_text.push_str(¤t_line);
21973 }
21974 wrapped_text
21975}
21976
21977#[test]
21978fn test_wrap_with_prefix() {
21979 assert_eq!(
21980 wrap_with_prefix(
21981 "# ".to_string(),
21982 "# ".to_string(),
21983 "abcdefg".to_string(),
21984 4,
21985 NonZeroU32::new(4).unwrap(),
21986 false,
21987 ),
21988 "# abcdefg"
21989 );
21990 assert_eq!(
21991 wrap_with_prefix(
21992 "".to_string(),
21993 "".to_string(),
21994 "\thello world".to_string(),
21995 8,
21996 NonZeroU32::new(4).unwrap(),
21997 false,
21998 ),
21999 "hello\nworld"
22000 );
22001 assert_eq!(
22002 wrap_with_prefix(
22003 "// ".to_string(),
22004 "// ".to_string(),
22005 "xx \nyy zz aa bb cc".to_string(),
22006 12,
22007 NonZeroU32::new(4).unwrap(),
22008 false,
22009 ),
22010 "// xx yy zz\n// aa bb cc"
22011 );
22012 assert_eq!(
22013 wrap_with_prefix(
22014 String::new(),
22015 String::new(),
22016 "这是什么 \n 钢笔".to_string(),
22017 3,
22018 NonZeroU32::new(4).unwrap(),
22019 false,
22020 ),
22021 "这是什\n么 钢\n笔"
22022 );
22023}
22024
22025pub trait CollaborationHub {
22026 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22027 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22028 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22029}
22030
22031impl CollaborationHub for Entity<Project> {
22032 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22033 self.read(cx).collaborators()
22034 }
22035
22036 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22037 self.read(cx).user_store().read(cx).participant_indices()
22038 }
22039
22040 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22041 let this = self.read(cx);
22042 let user_ids = this.collaborators().values().map(|c| c.user_id);
22043 this.user_store().read(cx).participant_names(user_ids, cx)
22044 }
22045}
22046
22047pub trait SemanticsProvider {
22048 fn hover(
22049 &self,
22050 buffer: &Entity<Buffer>,
22051 position: text::Anchor,
22052 cx: &mut App,
22053 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22054
22055 fn inline_values(
22056 &self,
22057 buffer_handle: Entity<Buffer>,
22058 range: Range<text::Anchor>,
22059 cx: &mut App,
22060 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22061
22062 fn inlay_hints(
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 resolve_inlay_hint(
22070 &self,
22071 hint: InlayHint,
22072 buffer_handle: Entity<Buffer>,
22073 server_id: LanguageServerId,
22074 cx: &mut App,
22075 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22076
22077 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22078
22079 fn document_highlights(
22080 &self,
22081 buffer: &Entity<Buffer>,
22082 position: text::Anchor,
22083 cx: &mut App,
22084 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22085
22086 fn definitions(
22087 &self,
22088 buffer: &Entity<Buffer>,
22089 position: text::Anchor,
22090 kind: GotoDefinitionKind,
22091 cx: &mut App,
22092 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22093
22094 fn range_for_rename(
22095 &self,
22096 buffer: &Entity<Buffer>,
22097 position: text::Anchor,
22098 cx: &mut App,
22099 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22100
22101 fn perform_rename(
22102 &self,
22103 buffer: &Entity<Buffer>,
22104 position: text::Anchor,
22105 new_name: String,
22106 cx: &mut App,
22107 ) -> Option<Task<Result<ProjectTransaction>>>;
22108}
22109
22110pub trait CompletionProvider {
22111 fn completions(
22112 &self,
22113 excerpt_id: ExcerptId,
22114 buffer: &Entity<Buffer>,
22115 buffer_position: text::Anchor,
22116 trigger: CompletionContext,
22117 window: &mut Window,
22118 cx: &mut Context<Editor>,
22119 ) -> Task<Result<Vec<CompletionResponse>>>;
22120
22121 fn resolve_completions(
22122 &self,
22123 _buffer: Entity<Buffer>,
22124 _completion_indices: Vec<usize>,
22125 _completions: Rc<RefCell<Box<[Completion]>>>,
22126 _cx: &mut Context<Editor>,
22127 ) -> Task<Result<bool>> {
22128 Task::ready(Ok(false))
22129 }
22130
22131 fn apply_additional_edits_for_completion(
22132 &self,
22133 _buffer: Entity<Buffer>,
22134 _completions: Rc<RefCell<Box<[Completion]>>>,
22135 _completion_index: usize,
22136 _push_to_history: bool,
22137 _cx: &mut Context<Editor>,
22138 ) -> Task<Result<Option<language::Transaction>>> {
22139 Task::ready(Ok(None))
22140 }
22141
22142 fn is_completion_trigger(
22143 &self,
22144 buffer: &Entity<Buffer>,
22145 position: language::Anchor,
22146 text: &str,
22147 trigger_in_words: bool,
22148 menu_is_open: bool,
22149 cx: &mut Context<Editor>,
22150 ) -> bool;
22151
22152 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22153
22154 fn sort_completions(&self) -> bool {
22155 true
22156 }
22157
22158 fn filter_completions(&self) -> bool {
22159 true
22160 }
22161}
22162
22163pub trait CodeActionProvider {
22164 fn id(&self) -> Arc<str>;
22165
22166 fn code_actions(
22167 &self,
22168 buffer: &Entity<Buffer>,
22169 range: Range<text::Anchor>,
22170 window: &mut Window,
22171 cx: &mut App,
22172 ) -> Task<Result<Vec<CodeAction>>>;
22173
22174 fn apply_code_action(
22175 &self,
22176 buffer_handle: Entity<Buffer>,
22177 action: CodeAction,
22178 excerpt_id: ExcerptId,
22179 push_to_history: bool,
22180 window: &mut Window,
22181 cx: &mut App,
22182 ) -> Task<Result<ProjectTransaction>>;
22183}
22184
22185impl CodeActionProvider for Entity<Project> {
22186 fn id(&self) -> Arc<str> {
22187 "project".into()
22188 }
22189
22190 fn code_actions(
22191 &self,
22192 buffer: &Entity<Buffer>,
22193 range: Range<text::Anchor>,
22194 _window: &mut Window,
22195 cx: &mut App,
22196 ) -> Task<Result<Vec<CodeAction>>> {
22197 self.update(cx, |project, cx| {
22198 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22199 let code_actions = project.code_actions(buffer, range, None, cx);
22200 cx.background_spawn(async move {
22201 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22202 Ok(code_lens_actions
22203 .context("code lens fetch")?
22204 .into_iter()
22205 .flatten()
22206 .chain(
22207 code_actions
22208 .context("code action fetch")?
22209 .into_iter()
22210 .flatten(),
22211 )
22212 .collect())
22213 })
22214 })
22215 }
22216
22217 fn apply_code_action(
22218 &self,
22219 buffer_handle: Entity<Buffer>,
22220 action: CodeAction,
22221 _excerpt_id: ExcerptId,
22222 push_to_history: bool,
22223 _window: &mut Window,
22224 cx: &mut App,
22225 ) -> Task<Result<ProjectTransaction>> {
22226 self.update(cx, |project, cx| {
22227 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22228 })
22229 }
22230}
22231
22232fn snippet_completions(
22233 project: &Project,
22234 buffer: &Entity<Buffer>,
22235 buffer_position: text::Anchor,
22236 cx: &mut App,
22237) -> Task<Result<CompletionResponse>> {
22238 let languages = buffer.read(cx).languages_at(buffer_position);
22239 let snippet_store = project.snippets().read(cx);
22240
22241 let scopes: Vec<_> = languages
22242 .iter()
22243 .filter_map(|language| {
22244 let language_name = language.lsp_id();
22245 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22246
22247 if snippets.is_empty() {
22248 None
22249 } else {
22250 Some((language.default_scope(), snippets))
22251 }
22252 })
22253 .collect();
22254
22255 if scopes.is_empty() {
22256 return Task::ready(Ok(CompletionResponse {
22257 completions: vec![],
22258 is_incomplete: false,
22259 }));
22260 }
22261
22262 let snapshot = buffer.read(cx).text_snapshot();
22263 let chars: String = snapshot
22264 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22265 .collect();
22266 let executor = cx.background_executor().clone();
22267
22268 cx.background_spawn(async move {
22269 let mut is_incomplete = false;
22270 let mut completions: Vec<Completion> = Vec::new();
22271 for (scope, snippets) in scopes.into_iter() {
22272 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22273 let mut last_word = chars
22274 .chars()
22275 .take_while(|c| classifier.is_word(*c))
22276 .collect::<String>();
22277 last_word = last_word.chars().rev().collect();
22278
22279 if last_word.is_empty() {
22280 return Ok(CompletionResponse {
22281 completions: vec![],
22282 is_incomplete: true,
22283 });
22284 }
22285
22286 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22287 let to_lsp = |point: &text::Anchor| {
22288 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22289 point_to_lsp(end)
22290 };
22291 let lsp_end = to_lsp(&buffer_position);
22292
22293 let candidates = snippets
22294 .iter()
22295 .enumerate()
22296 .flat_map(|(ix, snippet)| {
22297 snippet
22298 .prefix
22299 .iter()
22300 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22301 })
22302 .collect::<Vec<StringMatchCandidate>>();
22303
22304 const MAX_RESULTS: usize = 100;
22305 let mut matches = fuzzy::match_strings(
22306 &candidates,
22307 &last_word,
22308 last_word.chars().any(|c| c.is_uppercase()),
22309 true,
22310 MAX_RESULTS,
22311 &Default::default(),
22312 executor.clone(),
22313 )
22314 .await;
22315
22316 if matches.len() >= MAX_RESULTS {
22317 is_incomplete = true;
22318 }
22319
22320 // Remove all candidates where the query's start does not match the start of any word in the candidate
22321 if let Some(query_start) = last_word.chars().next() {
22322 matches.retain(|string_match| {
22323 split_words(&string_match.string).any(|word| {
22324 // Check that the first codepoint of the word as lowercase matches the first
22325 // codepoint of the query as lowercase
22326 word.chars()
22327 .flat_map(|codepoint| codepoint.to_lowercase())
22328 .zip(query_start.to_lowercase())
22329 .all(|(word_cp, query_cp)| word_cp == query_cp)
22330 })
22331 });
22332 }
22333
22334 let matched_strings = matches
22335 .into_iter()
22336 .map(|m| m.string)
22337 .collect::<HashSet<_>>();
22338
22339 completions.extend(snippets.iter().filter_map(|snippet| {
22340 let matching_prefix = snippet
22341 .prefix
22342 .iter()
22343 .find(|prefix| matched_strings.contains(*prefix))?;
22344 let start = as_offset - last_word.len();
22345 let start = snapshot.anchor_before(start);
22346 let range = start..buffer_position;
22347 let lsp_start = to_lsp(&start);
22348 let lsp_range = lsp::Range {
22349 start: lsp_start,
22350 end: lsp_end,
22351 };
22352 Some(Completion {
22353 replace_range: range,
22354 new_text: snippet.body.clone(),
22355 source: CompletionSource::Lsp {
22356 insert_range: None,
22357 server_id: LanguageServerId(usize::MAX),
22358 resolved: true,
22359 lsp_completion: Box::new(lsp::CompletionItem {
22360 label: snippet.prefix.first().unwrap().clone(),
22361 kind: Some(CompletionItemKind::SNIPPET),
22362 label_details: snippet.description.as_ref().map(|description| {
22363 lsp::CompletionItemLabelDetails {
22364 detail: Some(description.clone()),
22365 description: None,
22366 }
22367 }),
22368 insert_text_format: Some(InsertTextFormat::SNIPPET),
22369 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22370 lsp::InsertReplaceEdit {
22371 new_text: snippet.body.clone(),
22372 insert: lsp_range,
22373 replace: lsp_range,
22374 },
22375 )),
22376 filter_text: Some(snippet.body.clone()),
22377 sort_text: Some(char::MAX.to_string()),
22378 ..lsp::CompletionItem::default()
22379 }),
22380 lsp_defaults: None,
22381 },
22382 label: CodeLabel {
22383 text: matching_prefix.clone(),
22384 runs: Vec::new(),
22385 filter_range: 0..matching_prefix.len(),
22386 },
22387 icon_path: None,
22388 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22389 single_line: snippet.name.clone().into(),
22390 plain_text: snippet
22391 .description
22392 .clone()
22393 .map(|description| description.into()),
22394 }),
22395 insert_text_mode: None,
22396 confirm: None,
22397 })
22398 }))
22399 }
22400
22401 Ok(CompletionResponse {
22402 completions,
22403 is_incomplete,
22404 })
22405 })
22406}
22407
22408impl CompletionProvider for Entity<Project> {
22409 fn completions(
22410 &self,
22411 _excerpt_id: ExcerptId,
22412 buffer: &Entity<Buffer>,
22413 buffer_position: text::Anchor,
22414 options: CompletionContext,
22415 _window: &mut Window,
22416 cx: &mut Context<Editor>,
22417 ) -> Task<Result<Vec<CompletionResponse>>> {
22418 self.update(cx, |project, cx| {
22419 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22420 let project_completions = project.completions(buffer, buffer_position, options, cx);
22421 cx.background_spawn(async move {
22422 let mut responses = project_completions.await?;
22423 let snippets = snippets.await?;
22424 if !snippets.completions.is_empty() {
22425 responses.push(snippets);
22426 }
22427 Ok(responses)
22428 })
22429 })
22430 }
22431
22432 fn resolve_completions(
22433 &self,
22434 buffer: Entity<Buffer>,
22435 completion_indices: Vec<usize>,
22436 completions: Rc<RefCell<Box<[Completion]>>>,
22437 cx: &mut Context<Editor>,
22438 ) -> Task<Result<bool>> {
22439 self.update(cx, |project, cx| {
22440 project.lsp_store().update(cx, |lsp_store, cx| {
22441 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22442 })
22443 })
22444 }
22445
22446 fn apply_additional_edits_for_completion(
22447 &self,
22448 buffer: Entity<Buffer>,
22449 completions: Rc<RefCell<Box<[Completion]>>>,
22450 completion_index: usize,
22451 push_to_history: bool,
22452 cx: &mut Context<Editor>,
22453 ) -> Task<Result<Option<language::Transaction>>> {
22454 self.update(cx, |project, cx| {
22455 project.lsp_store().update(cx, |lsp_store, cx| {
22456 lsp_store.apply_additional_edits_for_completion(
22457 buffer,
22458 completions,
22459 completion_index,
22460 push_to_history,
22461 cx,
22462 )
22463 })
22464 })
22465 }
22466
22467 fn is_completion_trigger(
22468 &self,
22469 buffer: &Entity<Buffer>,
22470 position: language::Anchor,
22471 text: &str,
22472 trigger_in_words: bool,
22473 menu_is_open: bool,
22474 cx: &mut Context<Editor>,
22475 ) -> bool {
22476 let mut chars = text.chars();
22477 let char = if let Some(char) = chars.next() {
22478 char
22479 } else {
22480 return false;
22481 };
22482 if chars.next().is_some() {
22483 return false;
22484 }
22485
22486 let buffer = buffer.read(cx);
22487 let snapshot = buffer.snapshot();
22488 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22489 return false;
22490 }
22491 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22492 if trigger_in_words && classifier.is_word(char) {
22493 return true;
22494 }
22495
22496 buffer.completion_triggers().contains(text)
22497 }
22498}
22499
22500impl SemanticsProvider for Entity<Project> {
22501 fn hover(
22502 &self,
22503 buffer: &Entity<Buffer>,
22504 position: text::Anchor,
22505 cx: &mut App,
22506 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22507 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22508 }
22509
22510 fn document_highlights(
22511 &self,
22512 buffer: &Entity<Buffer>,
22513 position: text::Anchor,
22514 cx: &mut App,
22515 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22516 Some(self.update(cx, |project, cx| {
22517 project.document_highlights(buffer, position, cx)
22518 }))
22519 }
22520
22521 fn definitions(
22522 &self,
22523 buffer: &Entity<Buffer>,
22524 position: text::Anchor,
22525 kind: GotoDefinitionKind,
22526 cx: &mut App,
22527 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22528 Some(self.update(cx, |project, cx| match kind {
22529 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22530 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22531 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22532 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22533 }))
22534 }
22535
22536 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22537 self.update(cx, |project, cx| {
22538 if project
22539 .active_debug_session(cx)
22540 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22541 {
22542 return true;
22543 }
22544
22545 buffer.update(cx, |buffer, cx| {
22546 project.any_language_server_supports_inlay_hints(buffer, cx)
22547 })
22548 })
22549 }
22550
22551 fn inline_values(
22552 &self,
22553 buffer_handle: Entity<Buffer>,
22554 range: Range<text::Anchor>,
22555 cx: &mut App,
22556 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22557 self.update(cx, |project, cx| {
22558 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22559
22560 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22561 })
22562 }
22563
22564 fn inlay_hints(
22565 &self,
22566 buffer_handle: Entity<Buffer>,
22567 range: Range<text::Anchor>,
22568 cx: &mut App,
22569 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22570 Some(self.update(cx, |project, cx| {
22571 project.inlay_hints(buffer_handle, range, cx)
22572 }))
22573 }
22574
22575 fn resolve_inlay_hint(
22576 &self,
22577 hint: InlayHint,
22578 buffer_handle: Entity<Buffer>,
22579 server_id: LanguageServerId,
22580 cx: &mut App,
22581 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22582 Some(self.update(cx, |project, cx| {
22583 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22584 }))
22585 }
22586
22587 fn range_for_rename(
22588 &self,
22589 buffer: &Entity<Buffer>,
22590 position: text::Anchor,
22591 cx: &mut App,
22592 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22593 Some(self.update(cx, |project, cx| {
22594 let buffer = buffer.clone();
22595 let task = project.prepare_rename(buffer.clone(), position, cx);
22596 cx.spawn(async move |_, cx| {
22597 Ok(match task.await? {
22598 PrepareRenameResponse::Success(range) => Some(range),
22599 PrepareRenameResponse::InvalidPosition => None,
22600 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22601 // Fallback on using TreeSitter info to determine identifier range
22602 buffer.read_with(cx, |buffer, _| {
22603 let snapshot = buffer.snapshot();
22604 let (range, kind) = snapshot.surrounding_word(position, false);
22605 if kind != Some(CharKind::Word) {
22606 return None;
22607 }
22608 Some(
22609 snapshot.anchor_before(range.start)
22610 ..snapshot.anchor_after(range.end),
22611 )
22612 })?
22613 }
22614 })
22615 })
22616 }))
22617 }
22618
22619 fn perform_rename(
22620 &self,
22621 buffer: &Entity<Buffer>,
22622 position: text::Anchor,
22623 new_name: String,
22624 cx: &mut App,
22625 ) -> Option<Task<Result<ProjectTransaction>>> {
22626 Some(self.update(cx, |project, cx| {
22627 project.perform_rename(buffer.clone(), position, new_name, cx)
22628 }))
22629 }
22630}
22631
22632fn inlay_hint_settings(
22633 location: Anchor,
22634 snapshot: &MultiBufferSnapshot,
22635 cx: &mut Context<Editor>,
22636) -> InlayHintSettings {
22637 let file = snapshot.file_at(location);
22638 let language = snapshot.language_at(location).map(|l| l.name());
22639 language_settings(language, file, cx).inlay_hints
22640}
22641
22642fn consume_contiguous_rows(
22643 contiguous_row_selections: &mut Vec<Selection<Point>>,
22644 selection: &Selection<Point>,
22645 display_map: &DisplaySnapshot,
22646 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22647) -> (MultiBufferRow, MultiBufferRow) {
22648 contiguous_row_selections.push(selection.clone());
22649 let start_row = starting_row(selection, display_map);
22650 let mut end_row = ending_row(selection, display_map);
22651
22652 while let Some(next_selection) = selections.peek() {
22653 if next_selection.start.row <= end_row.0 {
22654 end_row = ending_row(next_selection, display_map);
22655 contiguous_row_selections.push(selections.next().unwrap().clone());
22656 } else {
22657 break;
22658 }
22659 }
22660 (start_row, end_row)
22661}
22662
22663fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22664 if selection.start.column > 0 {
22665 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22666 } else {
22667 MultiBufferRow(selection.start.row)
22668 }
22669}
22670
22671fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22672 if next_selection.end.column > 0 || next_selection.is_empty() {
22673 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22674 } else {
22675 MultiBufferRow(next_selection.end.row)
22676 }
22677}
22678
22679impl EditorSnapshot {
22680 pub fn remote_selections_in_range<'a>(
22681 &'a self,
22682 range: &'a Range<Anchor>,
22683 collaboration_hub: &dyn CollaborationHub,
22684 cx: &'a App,
22685 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22686 let participant_names = collaboration_hub.user_names(cx);
22687 let participant_indices = collaboration_hub.user_participant_indices(cx);
22688 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22689 let collaborators_by_replica_id = collaborators_by_peer_id
22690 .values()
22691 .map(|collaborator| (collaborator.replica_id, collaborator))
22692 .collect::<HashMap<_, _>>();
22693 self.buffer_snapshot
22694 .selections_in_range(range, false)
22695 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22696 if replica_id == AGENT_REPLICA_ID {
22697 Some(RemoteSelection {
22698 replica_id,
22699 selection,
22700 cursor_shape,
22701 line_mode,
22702 collaborator_id: CollaboratorId::Agent,
22703 user_name: Some("Agent".into()),
22704 color: cx.theme().players().agent(),
22705 })
22706 } else {
22707 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22708 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22709 let user_name = participant_names.get(&collaborator.user_id).cloned();
22710 Some(RemoteSelection {
22711 replica_id,
22712 selection,
22713 cursor_shape,
22714 line_mode,
22715 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22716 user_name,
22717 color: if let Some(index) = participant_index {
22718 cx.theme().players().color_for_participant(index.0)
22719 } else {
22720 cx.theme().players().absent()
22721 },
22722 })
22723 }
22724 })
22725 }
22726
22727 pub fn hunks_for_ranges(
22728 &self,
22729 ranges: impl IntoIterator<Item = Range<Point>>,
22730 ) -> Vec<MultiBufferDiffHunk> {
22731 let mut hunks = Vec::new();
22732 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22733 HashMap::default();
22734 for query_range in ranges {
22735 let query_rows =
22736 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22737 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22738 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22739 ) {
22740 // Include deleted hunks that are adjacent to the query range, because
22741 // otherwise they would be missed.
22742 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22743 if hunk.status().is_deleted() {
22744 intersects_range |= hunk.row_range.start == query_rows.end;
22745 intersects_range |= hunk.row_range.end == query_rows.start;
22746 }
22747 if intersects_range {
22748 if !processed_buffer_rows
22749 .entry(hunk.buffer_id)
22750 .or_default()
22751 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22752 {
22753 continue;
22754 }
22755 hunks.push(hunk);
22756 }
22757 }
22758 }
22759
22760 hunks
22761 }
22762
22763 fn display_diff_hunks_for_rows<'a>(
22764 &'a self,
22765 display_rows: Range<DisplayRow>,
22766 folded_buffers: &'a HashSet<BufferId>,
22767 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22768 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22769 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22770
22771 self.buffer_snapshot
22772 .diff_hunks_in_range(buffer_start..buffer_end)
22773 .filter_map(|hunk| {
22774 if folded_buffers.contains(&hunk.buffer_id) {
22775 return None;
22776 }
22777
22778 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22779 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22780
22781 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22782 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22783
22784 let display_hunk = if hunk_display_start.column() != 0 {
22785 DisplayDiffHunk::Folded {
22786 display_row: hunk_display_start.row(),
22787 }
22788 } else {
22789 let mut end_row = hunk_display_end.row();
22790 if hunk_display_end.column() > 0 {
22791 end_row.0 += 1;
22792 }
22793 let is_created_file = hunk.is_created_file();
22794 DisplayDiffHunk::Unfolded {
22795 status: hunk.status(),
22796 diff_base_byte_range: hunk.diff_base_byte_range,
22797 display_row_range: hunk_display_start.row()..end_row,
22798 multi_buffer_range: Anchor::range_in_buffer(
22799 hunk.excerpt_id,
22800 hunk.buffer_id,
22801 hunk.buffer_range,
22802 ),
22803 is_created_file,
22804 }
22805 };
22806
22807 Some(display_hunk)
22808 })
22809 }
22810
22811 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22812 self.display_snapshot.buffer_snapshot.language_at(position)
22813 }
22814
22815 pub fn is_focused(&self) -> bool {
22816 self.is_focused
22817 }
22818
22819 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22820 self.placeholder_text.as_ref()
22821 }
22822
22823 pub fn scroll_position(&self) -> gpui::Point<f32> {
22824 self.scroll_anchor.scroll_position(&self.display_snapshot)
22825 }
22826
22827 fn gutter_dimensions(
22828 &self,
22829 font_id: FontId,
22830 font_size: Pixels,
22831 max_line_number_width: Pixels,
22832 cx: &App,
22833 ) -> Option<GutterDimensions> {
22834 if !self.show_gutter {
22835 return None;
22836 }
22837
22838 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22839 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22840
22841 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22842 matches!(
22843 ProjectSettings::get_global(cx).git.git_gutter,
22844 Some(GitGutterSetting::TrackedFiles)
22845 )
22846 });
22847 let gutter_settings = EditorSettings::get_global(cx).gutter;
22848 let show_line_numbers = self
22849 .show_line_numbers
22850 .unwrap_or(gutter_settings.line_numbers);
22851 let line_gutter_width = if show_line_numbers {
22852 // Avoid flicker-like gutter resizes when the line number gains another digit by
22853 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22854 let min_width_for_number_on_gutter =
22855 ch_advance * gutter_settings.min_line_number_digits as f32;
22856 max_line_number_width.max(min_width_for_number_on_gutter)
22857 } else {
22858 0.0.into()
22859 };
22860
22861 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22862 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22863
22864 let git_blame_entries_width =
22865 self.git_blame_gutter_max_author_length
22866 .map(|max_author_length| {
22867 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22868 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22869
22870 /// The number of characters to dedicate to gaps and margins.
22871 const SPACING_WIDTH: usize = 4;
22872
22873 let max_char_count = max_author_length.min(renderer.max_author_length())
22874 + ::git::SHORT_SHA_LENGTH
22875 + MAX_RELATIVE_TIMESTAMP.len()
22876 + SPACING_WIDTH;
22877
22878 ch_advance * max_char_count
22879 });
22880
22881 let is_singleton = self.buffer_snapshot.is_singleton();
22882
22883 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22884 left_padding += if !is_singleton {
22885 ch_width * 4.0
22886 } else if show_runnables || show_breakpoints {
22887 ch_width * 3.0
22888 } else if show_git_gutter && show_line_numbers {
22889 ch_width * 2.0
22890 } else if show_git_gutter || show_line_numbers {
22891 ch_width
22892 } else {
22893 px(0.)
22894 };
22895
22896 let shows_folds = is_singleton && gutter_settings.folds;
22897
22898 let right_padding = if shows_folds && show_line_numbers {
22899 ch_width * 4.0
22900 } else if shows_folds || (!is_singleton && show_line_numbers) {
22901 ch_width * 3.0
22902 } else if show_line_numbers {
22903 ch_width
22904 } else {
22905 px(0.)
22906 };
22907
22908 Some(GutterDimensions {
22909 left_padding,
22910 right_padding,
22911 width: line_gutter_width + left_padding + right_padding,
22912 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22913 git_blame_entries_width,
22914 })
22915 }
22916
22917 pub fn render_crease_toggle(
22918 &self,
22919 buffer_row: MultiBufferRow,
22920 row_contains_cursor: bool,
22921 editor: Entity<Editor>,
22922 window: &mut Window,
22923 cx: &mut App,
22924 ) -> Option<AnyElement> {
22925 let folded = self.is_line_folded(buffer_row);
22926 let mut is_foldable = false;
22927
22928 if let Some(crease) = self
22929 .crease_snapshot
22930 .query_row(buffer_row, &self.buffer_snapshot)
22931 {
22932 is_foldable = true;
22933 match crease {
22934 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22935 if let Some(render_toggle) = render_toggle {
22936 let toggle_callback =
22937 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22938 if folded {
22939 editor.update(cx, |editor, cx| {
22940 editor.fold_at(buffer_row, window, cx)
22941 });
22942 } else {
22943 editor.update(cx, |editor, cx| {
22944 editor.unfold_at(buffer_row, window, cx)
22945 });
22946 }
22947 });
22948 return Some((render_toggle)(
22949 buffer_row,
22950 folded,
22951 toggle_callback,
22952 window,
22953 cx,
22954 ));
22955 }
22956 }
22957 }
22958 }
22959
22960 is_foldable |= self.starts_indent(buffer_row);
22961
22962 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22963 Some(
22964 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22965 .toggle_state(folded)
22966 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22967 if folded {
22968 this.unfold_at(buffer_row, window, cx);
22969 } else {
22970 this.fold_at(buffer_row, window, cx);
22971 }
22972 }))
22973 .into_any_element(),
22974 )
22975 } else {
22976 None
22977 }
22978 }
22979
22980 pub fn render_crease_trailer(
22981 &self,
22982 buffer_row: MultiBufferRow,
22983 window: &mut Window,
22984 cx: &mut App,
22985 ) -> Option<AnyElement> {
22986 let folded = self.is_line_folded(buffer_row);
22987 if let Crease::Inline { render_trailer, .. } = self
22988 .crease_snapshot
22989 .query_row(buffer_row, &self.buffer_snapshot)?
22990 {
22991 let render_trailer = render_trailer.as_ref()?;
22992 Some(render_trailer(buffer_row, folded, window, cx))
22993 } else {
22994 None
22995 }
22996 }
22997}
22998
22999impl Deref for EditorSnapshot {
23000 type Target = DisplaySnapshot;
23001
23002 fn deref(&self) -> &Self::Target {
23003 &self.display_snapshot
23004 }
23005}
23006
23007#[derive(Clone, Debug, PartialEq, Eq)]
23008pub enum EditorEvent {
23009 InputIgnored {
23010 text: Arc<str>,
23011 },
23012 InputHandled {
23013 utf16_range_to_replace: Option<Range<isize>>,
23014 text: Arc<str>,
23015 },
23016 ExcerptsAdded {
23017 buffer: Entity<Buffer>,
23018 predecessor: ExcerptId,
23019 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23020 },
23021 ExcerptsRemoved {
23022 ids: Vec<ExcerptId>,
23023 removed_buffer_ids: Vec<BufferId>,
23024 },
23025 BufferFoldToggled {
23026 ids: Vec<ExcerptId>,
23027 folded: bool,
23028 },
23029 ExcerptsEdited {
23030 ids: Vec<ExcerptId>,
23031 },
23032 ExcerptsExpanded {
23033 ids: Vec<ExcerptId>,
23034 },
23035 BufferEdited,
23036 Edited {
23037 transaction_id: clock::Lamport,
23038 },
23039 Reparsed(BufferId),
23040 Focused,
23041 FocusedIn,
23042 Blurred,
23043 DirtyChanged,
23044 Saved,
23045 TitleChanged,
23046 DiffBaseChanged,
23047 SelectionsChanged {
23048 local: bool,
23049 },
23050 ScrollPositionChanged {
23051 local: bool,
23052 autoscroll: bool,
23053 },
23054 Closed,
23055 TransactionUndone {
23056 transaction_id: clock::Lamport,
23057 },
23058 TransactionBegun {
23059 transaction_id: clock::Lamport,
23060 },
23061 Reloaded,
23062 CursorShapeChanged,
23063 BreadcrumbsChanged,
23064 PushedToNavHistory {
23065 anchor: Anchor,
23066 is_deactivate: bool,
23067 },
23068}
23069
23070impl EventEmitter<EditorEvent> for Editor {}
23071
23072impl Focusable for Editor {
23073 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23074 self.focus_handle.clone()
23075 }
23076}
23077
23078impl Render for Editor {
23079 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23080 let settings = ThemeSettings::get_global(cx);
23081
23082 let mut text_style = match self.mode {
23083 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23084 color: cx.theme().colors().editor_foreground,
23085 font_family: settings.ui_font.family.clone(),
23086 font_features: settings.ui_font.features.clone(),
23087 font_fallbacks: settings.ui_font.fallbacks.clone(),
23088 font_size: rems(0.875).into(),
23089 font_weight: settings.ui_font.weight,
23090 line_height: relative(settings.buffer_line_height.value()),
23091 ..Default::default()
23092 },
23093 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23094 color: cx.theme().colors().editor_foreground,
23095 font_family: settings.buffer_font.family.clone(),
23096 font_features: settings.buffer_font.features.clone(),
23097 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23098 font_size: settings.buffer_font_size(cx).into(),
23099 font_weight: settings.buffer_font.weight,
23100 line_height: relative(settings.buffer_line_height.value()),
23101 ..Default::default()
23102 },
23103 };
23104 if let Some(text_style_refinement) = &self.text_style_refinement {
23105 text_style.refine(text_style_refinement)
23106 }
23107
23108 let background = match self.mode {
23109 EditorMode::SingleLine => cx.theme().system().transparent,
23110 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23111 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23112 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23113 };
23114
23115 EditorElement::new(
23116 &cx.entity(),
23117 EditorStyle {
23118 background,
23119 border: cx.theme().colors().border,
23120 local_player: cx.theme().players().local(),
23121 text: text_style,
23122 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23123 syntax: cx.theme().syntax().clone(),
23124 status: cx.theme().status().clone(),
23125 inlay_hints_style: make_inlay_hints_style(cx),
23126 edit_prediction_styles: make_suggestion_styles(cx),
23127 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23128 show_underlines: self.diagnostics_enabled(),
23129 },
23130 )
23131 }
23132}
23133
23134impl EntityInputHandler for Editor {
23135 fn text_for_range(
23136 &mut self,
23137 range_utf16: Range<usize>,
23138 adjusted_range: &mut Option<Range<usize>>,
23139 _: &mut Window,
23140 cx: &mut Context<Self>,
23141 ) -> Option<String> {
23142 let snapshot = self.buffer.read(cx).read(cx);
23143 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23144 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23145 if (start.0..end.0) != range_utf16 {
23146 adjusted_range.replace(start.0..end.0);
23147 }
23148 Some(snapshot.text_for_range(start..end).collect())
23149 }
23150
23151 fn selected_text_range(
23152 &mut self,
23153 ignore_disabled_input: bool,
23154 _: &mut Window,
23155 cx: &mut Context<Self>,
23156 ) -> Option<UTF16Selection> {
23157 // Prevent the IME menu from appearing when holding down an alphabetic key
23158 // while input is disabled.
23159 if !ignore_disabled_input && !self.input_enabled {
23160 return None;
23161 }
23162
23163 let selection = self.selections.newest::<OffsetUtf16>(cx);
23164 let range = selection.range();
23165
23166 Some(UTF16Selection {
23167 range: range.start.0..range.end.0,
23168 reversed: selection.reversed,
23169 })
23170 }
23171
23172 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23173 let snapshot = self.buffer.read(cx).read(cx);
23174 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23175 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23176 }
23177
23178 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23179 self.clear_highlights::<InputComposition>(cx);
23180 self.ime_transaction.take();
23181 }
23182
23183 fn replace_text_in_range(
23184 &mut self,
23185 range_utf16: Option<Range<usize>>,
23186 text: &str,
23187 window: &mut Window,
23188 cx: &mut Context<Self>,
23189 ) {
23190 if !self.input_enabled {
23191 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23192 return;
23193 }
23194
23195 self.transact(window, cx, |this, window, cx| {
23196 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23197 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23198 Some(this.selection_replacement_ranges(range_utf16, cx))
23199 } else {
23200 this.marked_text_ranges(cx)
23201 };
23202
23203 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23204 let newest_selection_id = this.selections.newest_anchor().id;
23205 this.selections
23206 .all::<OffsetUtf16>(cx)
23207 .iter()
23208 .zip(ranges_to_replace.iter())
23209 .find_map(|(selection, range)| {
23210 if selection.id == newest_selection_id {
23211 Some(
23212 (range.start.0 as isize - selection.head().0 as isize)
23213 ..(range.end.0 as isize - selection.head().0 as isize),
23214 )
23215 } else {
23216 None
23217 }
23218 })
23219 });
23220
23221 cx.emit(EditorEvent::InputHandled {
23222 utf16_range_to_replace: range_to_replace,
23223 text: text.into(),
23224 });
23225
23226 if let Some(new_selected_ranges) = new_selected_ranges {
23227 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23228 selections.select_ranges(new_selected_ranges)
23229 });
23230 this.backspace(&Default::default(), window, cx);
23231 }
23232
23233 this.handle_input(text, window, cx);
23234 });
23235
23236 if let Some(transaction) = self.ime_transaction {
23237 self.buffer.update(cx, |buffer, cx| {
23238 buffer.group_until_transaction(transaction, cx);
23239 });
23240 }
23241
23242 self.unmark_text(window, cx);
23243 }
23244
23245 fn replace_and_mark_text_in_range(
23246 &mut self,
23247 range_utf16: Option<Range<usize>>,
23248 text: &str,
23249 new_selected_range_utf16: Option<Range<usize>>,
23250 window: &mut Window,
23251 cx: &mut Context<Self>,
23252 ) {
23253 if !self.input_enabled {
23254 return;
23255 }
23256
23257 let transaction = self.transact(window, cx, |this, window, cx| {
23258 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23259 let snapshot = this.buffer.read(cx).read(cx);
23260 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23261 for marked_range in &mut marked_ranges {
23262 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23263 marked_range.start.0 += relative_range_utf16.start;
23264 marked_range.start =
23265 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23266 marked_range.end =
23267 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23268 }
23269 }
23270 Some(marked_ranges)
23271 } else if let Some(range_utf16) = range_utf16 {
23272 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23273 Some(this.selection_replacement_ranges(range_utf16, cx))
23274 } else {
23275 None
23276 };
23277
23278 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23279 let newest_selection_id = this.selections.newest_anchor().id;
23280 this.selections
23281 .all::<OffsetUtf16>(cx)
23282 .iter()
23283 .zip(ranges_to_replace.iter())
23284 .find_map(|(selection, range)| {
23285 if selection.id == newest_selection_id {
23286 Some(
23287 (range.start.0 as isize - selection.head().0 as isize)
23288 ..(range.end.0 as isize - selection.head().0 as isize),
23289 )
23290 } else {
23291 None
23292 }
23293 })
23294 });
23295
23296 cx.emit(EditorEvent::InputHandled {
23297 utf16_range_to_replace: range_to_replace,
23298 text: text.into(),
23299 });
23300
23301 if let Some(ranges) = ranges_to_replace {
23302 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23303 s.select_ranges(ranges)
23304 });
23305 }
23306
23307 let marked_ranges = {
23308 let snapshot = this.buffer.read(cx).read(cx);
23309 this.selections
23310 .disjoint_anchors()
23311 .iter()
23312 .map(|selection| {
23313 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23314 })
23315 .collect::<Vec<_>>()
23316 };
23317
23318 if text.is_empty() {
23319 this.unmark_text(window, cx);
23320 } else {
23321 this.highlight_text::<InputComposition>(
23322 marked_ranges.clone(),
23323 HighlightStyle {
23324 underline: Some(UnderlineStyle {
23325 thickness: px(1.),
23326 color: None,
23327 wavy: false,
23328 }),
23329 ..Default::default()
23330 },
23331 cx,
23332 );
23333 }
23334
23335 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23336 let use_autoclose = this.use_autoclose;
23337 let use_auto_surround = this.use_auto_surround;
23338 this.set_use_autoclose(false);
23339 this.set_use_auto_surround(false);
23340 this.handle_input(text, window, cx);
23341 this.set_use_autoclose(use_autoclose);
23342 this.set_use_auto_surround(use_auto_surround);
23343
23344 if let Some(new_selected_range) = new_selected_range_utf16 {
23345 let snapshot = this.buffer.read(cx).read(cx);
23346 let new_selected_ranges = marked_ranges
23347 .into_iter()
23348 .map(|marked_range| {
23349 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23350 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23351 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23352 snapshot.clip_offset_utf16(new_start, Bias::Left)
23353 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23354 })
23355 .collect::<Vec<_>>();
23356
23357 drop(snapshot);
23358 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23359 selections.select_ranges(new_selected_ranges)
23360 });
23361 }
23362 });
23363
23364 self.ime_transaction = self.ime_transaction.or(transaction);
23365 if let Some(transaction) = self.ime_transaction {
23366 self.buffer.update(cx, |buffer, cx| {
23367 buffer.group_until_transaction(transaction, cx);
23368 });
23369 }
23370
23371 if self.text_highlights::<InputComposition>(cx).is_none() {
23372 self.ime_transaction.take();
23373 }
23374 }
23375
23376 fn bounds_for_range(
23377 &mut self,
23378 range_utf16: Range<usize>,
23379 element_bounds: gpui::Bounds<Pixels>,
23380 window: &mut Window,
23381 cx: &mut Context<Self>,
23382 ) -> Option<gpui::Bounds<Pixels>> {
23383 let text_layout_details = self.text_layout_details(window);
23384 let CharacterDimensions {
23385 em_width,
23386 em_advance,
23387 line_height,
23388 } = self.character_dimensions(window);
23389
23390 let snapshot = self.snapshot(window, cx);
23391 let scroll_position = snapshot.scroll_position();
23392 let scroll_left = scroll_position.x * em_advance;
23393
23394 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23395 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23396 + self.gutter_dimensions.full_width();
23397 let y = line_height * (start.row().as_f32() - scroll_position.y);
23398
23399 Some(Bounds {
23400 origin: element_bounds.origin + point(x, y),
23401 size: size(em_width, line_height),
23402 })
23403 }
23404
23405 fn character_index_for_point(
23406 &mut self,
23407 point: gpui::Point<Pixels>,
23408 _window: &mut Window,
23409 _cx: &mut Context<Self>,
23410 ) -> Option<usize> {
23411 let position_map = self.last_position_map.as_ref()?;
23412 if !position_map.text_hitbox.contains(&point) {
23413 return None;
23414 }
23415 let display_point = position_map.point_for_position(point).previous_valid;
23416 let anchor = position_map
23417 .snapshot
23418 .display_point_to_anchor(display_point, Bias::Left);
23419 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23420 Some(utf16_offset.0)
23421 }
23422}
23423
23424trait SelectionExt {
23425 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23426 fn spanned_rows(
23427 &self,
23428 include_end_if_at_line_start: bool,
23429 map: &DisplaySnapshot,
23430 ) -> Range<MultiBufferRow>;
23431}
23432
23433impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23434 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23435 let start = self
23436 .start
23437 .to_point(&map.buffer_snapshot)
23438 .to_display_point(map);
23439 let end = self
23440 .end
23441 .to_point(&map.buffer_snapshot)
23442 .to_display_point(map);
23443 if self.reversed {
23444 end..start
23445 } else {
23446 start..end
23447 }
23448 }
23449
23450 fn spanned_rows(
23451 &self,
23452 include_end_if_at_line_start: bool,
23453 map: &DisplaySnapshot,
23454 ) -> Range<MultiBufferRow> {
23455 let start = self.start.to_point(&map.buffer_snapshot);
23456 let mut end = self.end.to_point(&map.buffer_snapshot);
23457 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23458 end.row -= 1;
23459 }
23460
23461 let buffer_start = map.prev_line_boundary(start).0;
23462 let buffer_end = map.next_line_boundary(end).0;
23463 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23464 }
23465}
23466
23467impl<T: InvalidationRegion> InvalidationStack<T> {
23468 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23469 where
23470 S: Clone + ToOffset,
23471 {
23472 while let Some(region) = self.last() {
23473 let all_selections_inside_invalidation_ranges =
23474 if selections.len() == region.ranges().len() {
23475 selections
23476 .iter()
23477 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23478 .all(|(selection, invalidation_range)| {
23479 let head = selection.head().to_offset(buffer);
23480 invalidation_range.start <= head && invalidation_range.end >= head
23481 })
23482 } else {
23483 false
23484 };
23485
23486 if all_selections_inside_invalidation_ranges {
23487 break;
23488 } else {
23489 self.pop();
23490 }
23491 }
23492 }
23493}
23494
23495impl<T> Default for InvalidationStack<T> {
23496 fn default() -> Self {
23497 Self(Default::default())
23498 }
23499}
23500
23501impl<T> Deref for InvalidationStack<T> {
23502 type Target = Vec<T>;
23503
23504 fn deref(&self) -> &Self::Target {
23505 &self.0
23506 }
23507}
23508
23509impl<T> DerefMut for InvalidationStack<T> {
23510 fn deref_mut(&mut self) -> &mut Self::Target {
23511 &mut self.0
23512 }
23513}
23514
23515impl InvalidationRegion for SnippetState {
23516 fn ranges(&self) -> &[Range<Anchor>] {
23517 &self.ranges[self.active_index]
23518 }
23519}
23520
23521fn edit_prediction_edit_text(
23522 current_snapshot: &BufferSnapshot,
23523 edits: &[(Range<Anchor>, String)],
23524 edit_preview: &EditPreview,
23525 include_deletions: bool,
23526 cx: &App,
23527) -> HighlightedText {
23528 let edits = edits
23529 .iter()
23530 .map(|(anchor, text)| {
23531 (
23532 anchor.start.text_anchor..anchor.end.text_anchor,
23533 text.clone(),
23534 )
23535 })
23536 .collect::<Vec<_>>();
23537
23538 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23539}
23540
23541fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23542 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23543 // Just show the raw edit text with basic styling
23544 let mut text = String::new();
23545 let mut highlights = Vec::new();
23546
23547 let insertion_highlight_style = HighlightStyle {
23548 color: Some(cx.theme().colors().text),
23549 ..Default::default()
23550 };
23551
23552 for (_, edit_text) in edits {
23553 let start_offset = text.len();
23554 text.push_str(edit_text);
23555 let end_offset = text.len();
23556
23557 if start_offset < end_offset {
23558 highlights.push((start_offset..end_offset, insertion_highlight_style));
23559 }
23560 }
23561
23562 HighlightedText {
23563 text: text.into(),
23564 highlights,
23565 }
23566}
23567
23568pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23569 match severity {
23570 lsp::DiagnosticSeverity::ERROR => colors.error,
23571 lsp::DiagnosticSeverity::WARNING => colors.warning,
23572 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23573 lsp::DiagnosticSeverity::HINT => colors.info,
23574 _ => colors.ignored,
23575 }
23576}
23577
23578pub fn styled_runs_for_code_label<'a>(
23579 label: &'a CodeLabel,
23580 syntax_theme: &'a theme::SyntaxTheme,
23581) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23582 let fade_out = HighlightStyle {
23583 fade_out: Some(0.35),
23584 ..Default::default()
23585 };
23586
23587 let mut prev_end = label.filter_range.end;
23588 label
23589 .runs
23590 .iter()
23591 .enumerate()
23592 .flat_map(move |(ix, (range, highlight_id))| {
23593 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23594 style
23595 } else {
23596 return Default::default();
23597 };
23598 let mut muted_style = style;
23599 muted_style.highlight(fade_out);
23600
23601 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23602 if range.start >= label.filter_range.end {
23603 if range.start > prev_end {
23604 runs.push((prev_end..range.start, fade_out));
23605 }
23606 runs.push((range.clone(), muted_style));
23607 } else if range.end <= label.filter_range.end {
23608 runs.push((range.clone(), style));
23609 } else {
23610 runs.push((range.start..label.filter_range.end, style));
23611 runs.push((label.filter_range.end..range.end, muted_style));
23612 }
23613 prev_end = cmp::max(prev_end, range.end);
23614
23615 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23616 runs.push((prev_end..label.text.len(), fade_out));
23617 }
23618
23619 runs
23620 })
23621}
23622
23623pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23624 let mut prev_index = 0;
23625 let mut prev_codepoint: Option<char> = None;
23626 text.char_indices()
23627 .chain([(text.len(), '\0')])
23628 .filter_map(move |(index, codepoint)| {
23629 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23630 let is_boundary = index == text.len()
23631 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23632 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23633 if is_boundary {
23634 let chunk = &text[prev_index..index];
23635 prev_index = index;
23636 Some(chunk)
23637 } else {
23638 None
23639 }
23640 })
23641}
23642
23643pub trait RangeToAnchorExt: Sized {
23644 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23645
23646 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23647 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23648 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23649 }
23650}
23651
23652impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23653 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23654 let start_offset = self.start.to_offset(snapshot);
23655 let end_offset = self.end.to_offset(snapshot);
23656 if start_offset == end_offset {
23657 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23658 } else {
23659 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23660 }
23661 }
23662}
23663
23664pub trait RowExt {
23665 fn as_f32(&self) -> f32;
23666
23667 fn next_row(&self) -> Self;
23668
23669 fn previous_row(&self) -> Self;
23670
23671 fn minus(&self, other: Self) -> u32;
23672}
23673
23674impl RowExt for DisplayRow {
23675 fn as_f32(&self) -> f32 {
23676 self.0 as f32
23677 }
23678
23679 fn next_row(&self) -> Self {
23680 Self(self.0 + 1)
23681 }
23682
23683 fn previous_row(&self) -> Self {
23684 Self(self.0.saturating_sub(1))
23685 }
23686
23687 fn minus(&self, other: Self) -> u32 {
23688 self.0 - other.0
23689 }
23690}
23691
23692impl RowExt for MultiBufferRow {
23693 fn as_f32(&self) -> f32 {
23694 self.0 as f32
23695 }
23696
23697 fn next_row(&self) -> Self {
23698 Self(self.0 + 1)
23699 }
23700
23701 fn previous_row(&self) -> Self {
23702 Self(self.0.saturating_sub(1))
23703 }
23704
23705 fn minus(&self, other: Self) -> u32 {
23706 self.0 - other.0
23707 }
23708}
23709
23710trait RowRangeExt {
23711 type Row;
23712
23713 fn len(&self) -> usize;
23714
23715 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23716}
23717
23718impl RowRangeExt for Range<MultiBufferRow> {
23719 type Row = MultiBufferRow;
23720
23721 fn len(&self) -> usize {
23722 (self.end.0 - self.start.0) as usize
23723 }
23724
23725 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23726 (self.start.0..self.end.0).map(MultiBufferRow)
23727 }
23728}
23729
23730impl RowRangeExt for Range<DisplayRow> {
23731 type Row = DisplayRow;
23732
23733 fn len(&self) -> usize {
23734 (self.end.0 - self.start.0) as usize
23735 }
23736
23737 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23738 (self.start.0..self.end.0).map(DisplayRow)
23739 }
23740}
23741
23742/// If select range has more than one line, we
23743/// just point the cursor to range.start.
23744fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23745 if range.start.row == range.end.row {
23746 range
23747 } else {
23748 range.start..range.start
23749 }
23750}
23751pub struct KillRing(ClipboardItem);
23752impl Global for KillRing {}
23753
23754const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23755
23756enum BreakpointPromptEditAction {
23757 Log,
23758 Condition,
23759 HitCondition,
23760}
23761
23762struct BreakpointPromptEditor {
23763 pub(crate) prompt: Entity<Editor>,
23764 editor: WeakEntity<Editor>,
23765 breakpoint_anchor: Anchor,
23766 breakpoint: Breakpoint,
23767 edit_action: BreakpointPromptEditAction,
23768 block_ids: HashSet<CustomBlockId>,
23769 editor_margins: Arc<Mutex<EditorMargins>>,
23770 _subscriptions: Vec<Subscription>,
23771}
23772
23773impl BreakpointPromptEditor {
23774 const MAX_LINES: u8 = 4;
23775
23776 fn new(
23777 editor: WeakEntity<Editor>,
23778 breakpoint_anchor: Anchor,
23779 breakpoint: Breakpoint,
23780 edit_action: BreakpointPromptEditAction,
23781 window: &mut Window,
23782 cx: &mut Context<Self>,
23783 ) -> Self {
23784 let base_text = match edit_action {
23785 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23786 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23787 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23788 }
23789 .map(|msg| msg.to_string())
23790 .unwrap_or_default();
23791
23792 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23793 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23794
23795 let prompt = cx.new(|cx| {
23796 let mut prompt = Editor::new(
23797 EditorMode::AutoHeight {
23798 min_lines: 1,
23799 max_lines: Some(Self::MAX_LINES as usize),
23800 },
23801 buffer,
23802 None,
23803 window,
23804 cx,
23805 );
23806 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23807 prompt.set_show_cursor_when_unfocused(false, cx);
23808 prompt.set_placeholder_text(
23809 match edit_action {
23810 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23811 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23812 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23813 },
23814 cx,
23815 );
23816
23817 prompt
23818 });
23819
23820 Self {
23821 prompt,
23822 editor,
23823 breakpoint_anchor,
23824 breakpoint,
23825 edit_action,
23826 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23827 block_ids: Default::default(),
23828 _subscriptions: vec![],
23829 }
23830 }
23831
23832 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23833 self.block_ids.extend(block_ids)
23834 }
23835
23836 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23837 if let Some(editor) = self.editor.upgrade() {
23838 let message = self
23839 .prompt
23840 .read(cx)
23841 .buffer
23842 .read(cx)
23843 .as_singleton()
23844 .expect("A multi buffer in breakpoint prompt isn't possible")
23845 .read(cx)
23846 .as_rope()
23847 .to_string();
23848
23849 editor.update(cx, |editor, cx| {
23850 editor.edit_breakpoint_at_anchor(
23851 self.breakpoint_anchor,
23852 self.breakpoint.clone(),
23853 match self.edit_action {
23854 BreakpointPromptEditAction::Log => {
23855 BreakpointEditAction::EditLogMessage(message.into())
23856 }
23857 BreakpointPromptEditAction::Condition => {
23858 BreakpointEditAction::EditCondition(message.into())
23859 }
23860 BreakpointPromptEditAction::HitCondition => {
23861 BreakpointEditAction::EditHitCondition(message.into())
23862 }
23863 },
23864 cx,
23865 );
23866
23867 editor.remove_blocks(self.block_ids.clone(), None, cx);
23868 cx.focus_self(window);
23869 });
23870 }
23871 }
23872
23873 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23874 self.editor
23875 .update(cx, |editor, cx| {
23876 editor.remove_blocks(self.block_ids.clone(), None, cx);
23877 window.focus(&editor.focus_handle);
23878 })
23879 .log_err();
23880 }
23881
23882 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23883 let settings = ThemeSettings::get_global(cx);
23884 let text_style = TextStyle {
23885 color: if self.prompt.read(cx).read_only(cx) {
23886 cx.theme().colors().text_disabled
23887 } else {
23888 cx.theme().colors().text
23889 },
23890 font_family: settings.buffer_font.family.clone(),
23891 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23892 font_size: settings.buffer_font_size(cx).into(),
23893 font_weight: settings.buffer_font.weight,
23894 line_height: relative(settings.buffer_line_height.value()),
23895 ..Default::default()
23896 };
23897 EditorElement::new(
23898 &self.prompt,
23899 EditorStyle {
23900 background: cx.theme().colors().editor_background,
23901 local_player: cx.theme().players().local(),
23902 text: text_style,
23903 ..Default::default()
23904 },
23905 )
23906 }
23907}
23908
23909impl Render for BreakpointPromptEditor {
23910 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23911 let editor_margins = *self.editor_margins.lock();
23912 let gutter_dimensions = editor_margins.gutter;
23913 h_flex()
23914 .key_context("Editor")
23915 .bg(cx.theme().colors().editor_background)
23916 .border_y_1()
23917 .border_color(cx.theme().status().info_border)
23918 .size_full()
23919 .py(window.line_height() / 2.5)
23920 .on_action(cx.listener(Self::confirm))
23921 .on_action(cx.listener(Self::cancel))
23922 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23923 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23924 }
23925}
23926
23927impl Focusable for BreakpointPromptEditor {
23928 fn focus_handle(&self, cx: &App) -> FocusHandle {
23929 self.prompt.focus_handle(cx)
23930 }
23931}
23932
23933fn all_edits_insertions_or_deletions(
23934 edits: &Vec<(Range<Anchor>, String)>,
23935 snapshot: &MultiBufferSnapshot,
23936) -> bool {
23937 let mut all_insertions = true;
23938 let mut all_deletions = true;
23939
23940 for (range, new_text) in edits.iter() {
23941 let range_is_empty = range.to_offset(snapshot).is_empty();
23942 let text_is_empty = new_text.is_empty();
23943
23944 if range_is_empty != text_is_empty {
23945 if range_is_empty {
23946 all_deletions = false;
23947 } else {
23948 all_insertions = false;
23949 }
23950 } else {
23951 return false;
23952 }
23953
23954 if !all_insertions && !all_deletions {
23955 return false;
23956 }
23957 }
23958 all_insertions || all_deletions
23959}
23960
23961struct MissingEditPredictionKeybindingTooltip;
23962
23963impl Render for MissingEditPredictionKeybindingTooltip {
23964 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23965 ui::tooltip_container(window, cx, |container, _, cx| {
23966 container
23967 .flex_shrink_0()
23968 .max_w_80()
23969 .min_h(rems_from_px(124.))
23970 .justify_between()
23971 .child(
23972 v_flex()
23973 .flex_1()
23974 .text_ui_sm(cx)
23975 .child(Label::new("Conflict with Accept Keybinding"))
23976 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23977 )
23978 .child(
23979 h_flex()
23980 .pb_1()
23981 .gap_1()
23982 .items_end()
23983 .w_full()
23984 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23985 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23986 }))
23987 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23988 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23989 })),
23990 )
23991 })
23992 }
23993}
23994
23995#[derive(Debug, Clone, Copy, PartialEq)]
23996pub struct LineHighlight {
23997 pub background: Background,
23998 pub border: Option<gpui::Hsla>,
23999 pub include_gutter: bool,
24000 pub type_id: Option<TypeId>,
24001}
24002
24003struct LineManipulationResult {
24004 pub new_text: String,
24005 pub line_count_before: usize,
24006 pub line_count_after: usize,
24007}
24008
24009fn render_diff_hunk_controls(
24010 row: u32,
24011 status: &DiffHunkStatus,
24012 hunk_range: Range<Anchor>,
24013 is_created_file: bool,
24014 line_height: Pixels,
24015 editor: &Entity<Editor>,
24016 _window: &mut Window,
24017 cx: &mut App,
24018) -> AnyElement {
24019 h_flex()
24020 .h(line_height)
24021 .mr_1()
24022 .gap_1()
24023 .px_0p5()
24024 .pb_1()
24025 .border_x_1()
24026 .border_b_1()
24027 .border_color(cx.theme().colors().border_variant)
24028 .rounded_b_lg()
24029 .bg(cx.theme().colors().editor_background)
24030 .gap_1()
24031 .block_mouse_except_scroll()
24032 .shadow_md()
24033 .child(if status.has_secondary_hunk() {
24034 Button::new(("stage", row as u64), "Stage")
24035 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24036 .tooltip({
24037 let focus_handle = editor.focus_handle(cx);
24038 move |window, cx| {
24039 Tooltip::for_action_in(
24040 "Stage Hunk",
24041 &::git::ToggleStaged,
24042 &focus_handle,
24043 window,
24044 cx,
24045 )
24046 }
24047 })
24048 .on_click({
24049 let editor = editor.clone();
24050 move |_event, _window, cx| {
24051 editor.update(cx, |editor, cx| {
24052 editor.stage_or_unstage_diff_hunks(
24053 true,
24054 vec![hunk_range.start..hunk_range.start],
24055 cx,
24056 );
24057 });
24058 }
24059 })
24060 } else {
24061 Button::new(("unstage", row as u64), "Unstage")
24062 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24063 .tooltip({
24064 let focus_handle = editor.focus_handle(cx);
24065 move |window, cx| {
24066 Tooltip::for_action_in(
24067 "Unstage Hunk",
24068 &::git::ToggleStaged,
24069 &focus_handle,
24070 window,
24071 cx,
24072 )
24073 }
24074 })
24075 .on_click({
24076 let editor = editor.clone();
24077 move |_event, _window, cx| {
24078 editor.update(cx, |editor, cx| {
24079 editor.stage_or_unstage_diff_hunks(
24080 false,
24081 vec![hunk_range.start..hunk_range.start],
24082 cx,
24083 );
24084 });
24085 }
24086 })
24087 })
24088 .child(
24089 Button::new(("restore", row as u64), "Restore")
24090 .tooltip({
24091 let focus_handle = editor.focus_handle(cx);
24092 move |window, cx| {
24093 Tooltip::for_action_in(
24094 "Restore Hunk",
24095 &::git::Restore,
24096 &focus_handle,
24097 window,
24098 cx,
24099 )
24100 }
24101 })
24102 .on_click({
24103 let editor = editor.clone();
24104 move |_event, window, cx| {
24105 editor.update(cx, |editor, cx| {
24106 let snapshot = editor.snapshot(window, cx);
24107 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24108 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24109 });
24110 }
24111 })
24112 .disabled(is_created_file),
24113 )
24114 .when(
24115 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24116 |el| {
24117 el.child(
24118 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24119 .shape(IconButtonShape::Square)
24120 .icon_size(IconSize::Small)
24121 // .disabled(!has_multiple_hunks)
24122 .tooltip({
24123 let focus_handle = editor.focus_handle(cx);
24124 move |window, cx| {
24125 Tooltip::for_action_in(
24126 "Next Hunk",
24127 &GoToHunk,
24128 &focus_handle,
24129 window,
24130 cx,
24131 )
24132 }
24133 })
24134 .on_click({
24135 let editor = editor.clone();
24136 move |_event, window, cx| {
24137 editor.update(cx, |editor, cx| {
24138 let snapshot = editor.snapshot(window, cx);
24139 let position =
24140 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24141 editor.go_to_hunk_before_or_after_position(
24142 &snapshot,
24143 position,
24144 Direction::Next,
24145 window,
24146 cx,
24147 );
24148 editor.expand_selected_diff_hunks(cx);
24149 });
24150 }
24151 }),
24152 )
24153 .child(
24154 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24155 .shape(IconButtonShape::Square)
24156 .icon_size(IconSize::Small)
24157 // .disabled(!has_multiple_hunks)
24158 .tooltip({
24159 let focus_handle = editor.focus_handle(cx);
24160 move |window, cx| {
24161 Tooltip::for_action_in(
24162 "Previous Hunk",
24163 &GoToPreviousHunk,
24164 &focus_handle,
24165 window,
24166 cx,
24167 )
24168 }
24169 })
24170 .on_click({
24171 let editor = editor.clone();
24172 move |_event, window, cx| {
24173 editor.update(cx, |editor, cx| {
24174 let snapshot = editor.snapshot(window, cx);
24175 let point =
24176 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24177 editor.go_to_hunk_before_or_after_position(
24178 &snapshot,
24179 point,
24180 Direction::Prev,
24181 window,
24182 cx,
24183 );
24184 editor.expand_selected_diff_hunks(cx);
24185 });
24186 }
24187 }),
24188 )
24189 },
24190 )
24191 .into_any_element()
24192}
24193
24194pub fn multibuffer_context_lines(cx: &App) -> u32 {
24195 EditorSettings::try_get(cx)
24196 .map(|settings| settings.excerpt_context_lines)
24197 .unwrap_or(2)
24198 .clamp(1, 32)
24199}