1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{
164 DiagnosticSeverity, GitGutterSetting, GoToDiagnosticSeverityFilter, ProjectSettings,
165 },
166};
167use rand::seq::SliceRandom;
168use rpc::{ErrorCode, ErrorExt, proto::PeerId};
169use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
170use selections_collection::{
171 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
172};
173use serde::{Deserialize, Serialize};
174use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
175use smallvec::{SmallVec, smallvec};
176use snippet::Snippet;
177use std::{
178 any::TypeId,
179 borrow::Cow,
180 cell::OnceCell,
181 cell::RefCell,
182 cmp::{self, Ordering, Reverse},
183 iter::Peekable,
184 mem,
185 num::NonZeroU32,
186 ops::Not,
187 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
188 path::{Path, PathBuf},
189 rc::Rc,
190 sync::Arc,
191 time::{Duration, Instant},
192};
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
223const MAX_LINE_LEN: usize = 1024;
224const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
225const MAX_SELECTION_HISTORY_LEN: usize = 1024;
226pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
227#[doc(hidden)]
228pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
229pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
230
231pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268struct InlineValueCache {
269 enabled: bool,
270 inlays: Vec<InlayId>,
271 refresh_task: Task<Option<()>>,
272}
273
274impl InlineValueCache {
275 fn new(enabled: bool) -> Self {
276 Self {
277 enabled,
278 inlays: Vec::new(),
279 refresh_task: Task::ready(None),
280 }
281 }
282}
283
284#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
285pub enum InlayId {
286 EditPrediction(usize),
287 DebuggerValue(usize),
288 // LSP
289 Hint(usize),
290 Color(usize),
291}
292
293impl InlayId {
294 fn id(&self) -> usize {
295 match self {
296 Self::EditPrediction(id) => *id,
297 Self::DebuggerValue(id) => *id,
298 Self::Hint(id) => *id,
299 Self::Color(id) => *id,
300 }
301 }
302}
303
304pub enum ActiveDebugLine {}
305pub enum DebugStackFrameLine {}
306enum DocumentHighlightRead {}
307enum DocumentHighlightWrite {}
308enum InputComposition {}
309pub enum PendingInput {}
310enum SelectedTextHighlight {}
311
312pub enum ConflictsOuter {}
313pub enum ConflictsOurs {}
314pub enum ConflictsTheirs {}
315pub enum ConflictsOursMarker {}
316pub enum ConflictsTheirsMarker {}
317
318#[derive(Debug, Copy, Clone, PartialEq, Eq)]
319pub enum Navigated {
320 Yes,
321 No,
322}
323
324impl Navigated {
325 pub fn from_bool(yes: bool) -> Navigated {
326 if yes { Navigated::Yes } else { Navigated::No }
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331enum DisplayDiffHunk {
332 Folded {
333 display_row: DisplayRow,
334 },
335 Unfolded {
336 is_created_file: bool,
337 diff_base_byte_range: Range<usize>,
338 display_row_range: Range<DisplayRow>,
339 multi_buffer_range: Range<Anchor>,
340 status: DiffHunkStatus,
341 },
342}
343
344pub enum HideMouseCursorOrigin {
345 TypingAction,
346 MovementAction,
347}
348
349pub fn init_settings(cx: &mut App) {
350 EditorSettings::register(cx);
351}
352
353pub fn init(cx: &mut App) {
354 init_settings(cx);
355
356 cx.set_global(GlobalBlameRenderer(Arc::new(())));
357
358 workspace::register_project_item::<Editor>(cx);
359 workspace::FollowableViewRegistry::register::<Editor>(cx);
360 workspace::register_serializable_item::<Editor>(cx);
361
362 cx.observe_new(
363 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
364 workspace.register_action(Editor::new_file);
365 workspace.register_action(Editor::new_file_vertical);
366 workspace.register_action(Editor::new_file_horizontal);
367 workspace.register_action(Editor::cancel_language_server_work);
368 workspace.register_action(Editor::toggle_focus);
369 },
370 )
371 .detach();
372
373 cx.on_action(move |_: &workspace::NewFile, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387 cx.on_action(move |_: &workspace::NewWindow, cx| {
388 let app_state = workspace::AppState::global(cx);
389 if let Some(app_state) = app_state.upgrade() {
390 workspace::open_new(
391 Default::default(),
392 app_state,
393 cx,
394 |workspace, window, cx| {
395 cx.activate(true);
396 Editor::new_file(workspace, &Default::default(), window, cx)
397 },
398 )
399 .detach();
400 }
401 });
402}
403
404pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
405 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
406}
407
408pub trait DiagnosticRenderer {
409 fn render_group(
410 &self,
411 diagnostic_group: Vec<DiagnosticEntry<Point>>,
412 buffer_id: BufferId,
413 snapshot: EditorSnapshot,
414 editor: WeakEntity<Editor>,
415 cx: &mut App,
416 ) -> Vec<BlockProperties<Anchor>>;
417
418 fn render_hover(
419 &self,
420 diagnostic_group: Vec<DiagnosticEntry<Point>>,
421 range: Range<Point>,
422 buffer_id: BufferId,
423 cx: &mut App,
424 ) -> Option<Entity<markdown::Markdown>>;
425
426 fn open_link(
427 &self,
428 editor: &mut Editor,
429 link: SharedString,
430 window: &mut Window,
431 cx: &mut Context<Editor>,
432 );
433}
434
435pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
436
437impl GlobalDiagnosticRenderer {
438 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
439 cx.try_global::<Self>().map(|g| g.0.clone())
440 }
441}
442
443impl gpui::Global for GlobalDiagnosticRenderer {}
444pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
445 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
446}
447
448pub struct SearchWithinRange;
449
450trait InvalidationRegion {
451 fn ranges(&self) -> &[Range<Anchor>];
452}
453
454#[derive(Clone, Debug, PartialEq)]
455pub enum SelectPhase {
456 Begin {
457 position: DisplayPoint,
458 add: bool,
459 click_count: usize,
460 },
461 BeginColumnar {
462 position: DisplayPoint,
463 reset: bool,
464 mode: ColumnarMode,
465 goal_column: u32,
466 },
467 Extend {
468 position: DisplayPoint,
469 click_count: usize,
470 },
471 Update {
472 position: DisplayPoint,
473 goal_column: u32,
474 scroll_delta: gpui::Point<f32>,
475 },
476 End,
477}
478
479#[derive(Clone, Debug, PartialEq)]
480pub enum ColumnarMode {
481 FromMouse,
482 FromSelection,
483}
484
485#[derive(Clone, Debug)]
486pub enum SelectMode {
487 Character,
488 Word(Range<Anchor>),
489 Line(Range<Anchor>),
490 All,
491}
492
493#[derive(Clone, PartialEq, Eq, Debug)]
494pub enum EditorMode {
495 SingleLine,
496 AutoHeight {
497 min_lines: usize,
498 max_lines: Option<usize>,
499 },
500 Full {
501 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
502 scale_ui_elements_with_buffer_font_size: bool,
503 /// When set to `true`, the editor will render a background for the active line.
504 show_active_line_background: bool,
505 /// When set to `true`, the editor's height will be determined by its content.
506 sized_by_content: bool,
507 },
508 Minimap {
509 parent: WeakEntity<Editor>,
510 },
511}
512
513impl EditorMode {
514 pub fn full() -> Self {
515 Self::Full {
516 scale_ui_elements_with_buffer_font_size: true,
517 show_active_line_background: true,
518 sized_by_content: false,
519 }
520 }
521
522 #[inline]
523 pub fn is_full(&self) -> bool {
524 matches!(self, Self::Full { .. })
525 }
526
527 #[inline]
528 pub fn is_single_line(&self) -> bool {
529 matches!(self, Self::SingleLine { .. })
530 }
531
532 #[inline]
533 fn is_minimap(&self) -> bool {
534 matches!(self, Self::Minimap { .. })
535 }
536}
537
538#[derive(Copy, Clone, Debug)]
539pub enum SoftWrap {
540 /// Prefer not to wrap at all.
541 ///
542 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
543 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
544 GitDiff,
545 /// Prefer a single line generally, unless an overly long line is encountered.
546 None,
547 /// Soft wrap lines that exceed the editor width.
548 EditorWidth,
549 /// Soft wrap lines at the preferred line length.
550 Column(u32),
551 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
552 Bounded(u32),
553}
554
555#[derive(Clone)]
556pub struct EditorStyle {
557 pub background: Hsla,
558 pub border: Hsla,
559 pub local_player: PlayerColor,
560 pub text: TextStyle,
561 pub scrollbar_width: Pixels,
562 pub syntax: Arc<SyntaxTheme>,
563 pub status: StatusColors,
564 pub inlay_hints_style: HighlightStyle,
565 pub edit_prediction_styles: EditPredictionStyles,
566 pub unnecessary_code_fade: f32,
567 pub show_underlines: bool,
568}
569
570impl Default for EditorStyle {
571 fn default() -> Self {
572 Self {
573 background: Hsla::default(),
574 border: Hsla::default(),
575 local_player: PlayerColor::default(),
576 text: TextStyle::default(),
577 scrollbar_width: Pixels::default(),
578 syntax: Default::default(),
579 // HACK: Status colors don't have a real default.
580 // We should look into removing the status colors from the editor
581 // style and retrieve them directly from the theme.
582 status: StatusColors::dark(),
583 inlay_hints_style: HighlightStyle::default(),
584 edit_prediction_styles: EditPredictionStyles {
585 insertion: HighlightStyle::default(),
586 whitespace: HighlightStyle::default(),
587 },
588 unnecessary_code_fade: Default::default(),
589 show_underlines: true,
590 }
591 }
592}
593
594pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
595 let show_background = language_settings::language_settings(None, None, cx)
596 .inlay_hints
597 .show_background;
598
599 HighlightStyle {
600 color: Some(cx.theme().status().hint),
601 background_color: show_background.then(|| cx.theme().status().hint_background),
602 ..HighlightStyle::default()
603 }
604}
605
606pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
607 EditPredictionStyles {
608 insertion: HighlightStyle {
609 color: Some(cx.theme().status().predictive),
610 ..HighlightStyle::default()
611 },
612 whitespace: HighlightStyle {
613 background_color: Some(cx.theme().status().created_background),
614 ..HighlightStyle::default()
615 },
616 }
617}
618
619type CompletionId = usize;
620
621pub(crate) enum EditDisplayMode {
622 TabAccept,
623 DiffPopover,
624 Inline,
625}
626
627enum EditPrediction {
628 Edit {
629 edits: Vec<(Range<Anchor>, String)>,
630 edit_preview: Option<EditPreview>,
631 display_mode: EditDisplayMode,
632 snapshot: BufferSnapshot,
633 },
634 Move {
635 target: Anchor,
636 snapshot: BufferSnapshot,
637 },
638}
639
640struct EditPredictionState {
641 inlay_ids: Vec<InlayId>,
642 completion: EditPrediction,
643 completion_id: Option<SharedString>,
644 invalidation_range: Range<Anchor>,
645}
646
647enum EditPredictionSettings {
648 Disabled,
649 Enabled {
650 show_in_menu: bool,
651 preview_requires_modifier: bool,
652 },
653}
654
655enum EditPredictionHighlight {}
656
657#[derive(Debug, Clone)]
658struct InlineDiagnostic {
659 message: SharedString,
660 group_id: usize,
661 is_primary: bool,
662 start: Point,
663 severity: lsp::DiagnosticSeverity,
664}
665
666pub enum MenuEditPredictionsPolicy {
667 Never,
668 ByProvider,
669}
670
671pub enum EditPredictionPreview {
672 /// Modifier is not pressed
673 Inactive { released_too_fast: bool },
674 /// Modifier pressed
675 Active {
676 since: Instant,
677 previous_scroll_position: Option<ScrollAnchor>,
678 },
679}
680
681impl EditPredictionPreview {
682 pub fn released_too_fast(&self) -> bool {
683 match self {
684 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
685 EditPredictionPreview::Active { .. } => false,
686 }
687 }
688
689 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
690 if let EditPredictionPreview::Active {
691 previous_scroll_position,
692 ..
693 } = self
694 {
695 *previous_scroll_position = scroll_position;
696 }
697 }
698}
699
700pub struct ContextMenuOptions {
701 pub min_entries_visible: usize,
702 pub max_entries_visible: usize,
703 pub placement: Option<ContextMenuPlacement>,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq)]
707pub enum ContextMenuPlacement {
708 Above,
709 Below,
710}
711
712#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
713struct EditorActionId(usize);
714
715impl EditorActionId {
716 pub fn post_inc(&mut self) -> Self {
717 let answer = self.0;
718
719 *self = Self(answer + 1);
720
721 Self(answer)
722 }
723}
724
725// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
726// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
727
728type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
729type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
730
731#[derive(Default)]
732struct ScrollbarMarkerState {
733 scrollbar_size: Size<Pixels>,
734 dirty: bool,
735 markers: Arc<[PaintQuad]>,
736 pending_refresh: Option<Task<Result<()>>>,
737}
738
739impl ScrollbarMarkerState {
740 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
741 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
742 }
743}
744
745#[derive(Clone, Copy, PartialEq, Eq)]
746pub enum MinimapVisibility {
747 Disabled,
748 Enabled {
749 /// The configuration currently present in the users settings.
750 setting_configuration: bool,
751 /// Whether to override the currently set visibility from the users setting.
752 toggle_override: bool,
753 },
754}
755
756impl MinimapVisibility {
757 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
758 if mode.is_full() {
759 Self::Enabled {
760 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
761 toggle_override: false,
762 }
763 } else {
764 Self::Disabled
765 }
766 }
767
768 fn hidden(&self) -> Self {
769 match *self {
770 Self::Enabled {
771 setting_configuration,
772 ..
773 } => Self::Enabled {
774 setting_configuration,
775 toggle_override: setting_configuration,
776 },
777 Self::Disabled => Self::Disabled,
778 }
779 }
780
781 fn disabled(&self) -> bool {
782 matches!(*self, Self::Disabled)
783 }
784
785 fn settings_visibility(&self) -> bool {
786 match *self {
787 Self::Enabled {
788 setting_configuration,
789 ..
790 } => setting_configuration,
791 _ => false,
792 }
793 }
794
795 fn visible(&self) -> bool {
796 match *self {
797 Self::Enabled {
798 setting_configuration,
799 toggle_override,
800 } => setting_configuration ^ toggle_override,
801 _ => false,
802 }
803 }
804
805 fn toggle_visibility(&self) -> Self {
806 match *self {
807 Self::Enabled {
808 toggle_override,
809 setting_configuration,
810 } => Self::Enabled {
811 setting_configuration,
812 toggle_override: !toggle_override,
813 },
814 Self::Disabled => Self::Disabled,
815 }
816 }
817}
818
819#[derive(Clone, Debug)]
820struct RunnableTasks {
821 templates: Vec<(TaskSourceKind, TaskTemplate)>,
822 offset: multi_buffer::Anchor,
823 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
824 column: u32,
825 // Values of all named captures, including those starting with '_'
826 extra_variables: HashMap<String, String>,
827 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
828 context_range: Range<BufferOffset>,
829}
830
831impl RunnableTasks {
832 fn resolve<'a>(
833 &'a self,
834 cx: &'a task::TaskContext,
835 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
836 self.templates.iter().filter_map(|(kind, template)| {
837 template
838 .resolve_task(&kind.to_id_base(), cx)
839 .map(|task| (kind.clone(), task))
840 })
841 }
842}
843
844#[derive(Clone)]
845pub struct ResolvedTasks {
846 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
847 position: Anchor,
848}
849
850#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
851struct BufferOffset(usize);
852
853// Addons allow storing per-editor state in other crates (e.g. Vim)
854pub trait Addon: 'static {
855 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
856
857 fn render_buffer_header_controls(
858 &self,
859 _: &ExcerptInfo,
860 _: &Window,
861 _: &App,
862 ) -> Option<AnyElement> {
863 None
864 }
865
866 fn to_any(&self) -> &dyn std::any::Any;
867
868 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
869 None
870 }
871}
872
873struct ChangeLocation {
874 current: Option<Vec<Anchor>>,
875 original: Vec<Anchor>,
876}
877impl ChangeLocation {
878 fn locations(&self) -> &[Anchor] {
879 self.current.as_ref().unwrap_or(&self.original)
880 }
881}
882
883/// A set of caret positions, registered when the editor was edited.
884pub struct ChangeList {
885 changes: Vec<ChangeLocation>,
886 /// Currently "selected" change.
887 position: Option<usize>,
888}
889
890impl ChangeList {
891 pub fn new() -> Self {
892 Self {
893 changes: Vec::new(),
894 position: None,
895 }
896 }
897
898 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
899 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
900 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
901 if self.changes.is_empty() {
902 return None;
903 }
904
905 let prev = self.position.unwrap_or(self.changes.len());
906 let next = if direction == Direction::Prev {
907 prev.saturating_sub(count)
908 } else {
909 (prev + count).min(self.changes.len() - 1)
910 };
911 self.position = Some(next);
912 self.changes.get(next).map(|change| change.locations())
913 }
914
915 /// Adds a new change to the list, resetting the change list position.
916 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
917 self.position.take();
918 if let Some(last) = self.changes.last_mut()
919 && group
920 {
921 last.current = Some(new_positions)
922 } else {
923 self.changes.push(ChangeLocation {
924 original: new_positions,
925 current: None,
926 });
927 }
928 }
929
930 pub fn last(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.locations())
932 }
933
934 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
935 self.changes.last().map(|change| change.original.as_slice())
936 }
937
938 pub fn invert_last_group(&mut self) {
939 if let Some(last) = self.changes.last_mut()
940 && let Some(current) = last.current.as_mut()
941 {
942 mem::swap(&mut last.original, current);
943 }
944 }
945}
946
947#[derive(Clone)]
948struct InlineBlamePopoverState {
949 scroll_handle: ScrollHandle,
950 commit_message: Option<ParsedCommitMessage>,
951 markdown: Entity<Markdown>,
952}
953
954struct InlineBlamePopover {
955 position: gpui::Point<Pixels>,
956 hide_task: Option<Task<()>>,
957 popover_bounds: Option<Bounds<Pixels>>,
958 popover_state: InlineBlamePopoverState,
959 keyboard_grace: bool,
960}
961
962enum SelectionDragState {
963 /// State when no drag related activity is detected.
964 None,
965 /// State when the mouse is down on a selection that is about to be dragged.
966 ReadyToDrag {
967 selection: Selection<Anchor>,
968 click_position: gpui::Point<Pixels>,
969 mouse_down_time: Instant,
970 },
971 /// State when the mouse is dragging the selection in the editor.
972 Dragging {
973 selection: Selection<Anchor>,
974 drop_cursor: Selection<Anchor>,
975 hide_drop_cursor: bool,
976 },
977}
978
979enum ColumnarSelectionState {
980 FromMouse {
981 selection_tail: Anchor,
982 display_point: Option<DisplayPoint>,
983 },
984 FromSelection {
985 selection_tail: Anchor,
986 },
987}
988
989/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
990/// a breakpoint on them.
991#[derive(Clone, Copy, Debug, PartialEq, Eq)]
992struct PhantomBreakpointIndicator {
993 display_row: DisplayRow,
994 /// There's a small debounce between hovering over the line and showing the indicator.
995 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
996 is_active: bool,
997 collides_with_existing_breakpoint: bool,
998}
999
1000/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1001///
1002/// See the [module level documentation](self) for more information.
1003pub struct Editor {
1004 focus_handle: FocusHandle,
1005 last_focused_descendant: Option<WeakFocusHandle>,
1006 /// The text buffer being edited
1007 buffer: Entity<MultiBuffer>,
1008 /// Map of how text in the buffer should be displayed.
1009 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1010 pub display_map: Entity<DisplayMap>,
1011 pub selections: SelectionsCollection,
1012 pub scroll_manager: ScrollManager,
1013 /// When inline assist editors are linked, they all render cursors because
1014 /// typing enters text into each of them, even the ones that aren't focused.
1015 pub(crate) show_cursor_when_unfocused: bool,
1016 columnar_selection_state: Option<ColumnarSelectionState>,
1017 add_selections_state: Option<AddSelectionsState>,
1018 select_next_state: Option<SelectNextState>,
1019 select_prev_state: Option<SelectNextState>,
1020 selection_history: SelectionHistory,
1021 defer_selection_effects: bool,
1022 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1023 autoclose_regions: Vec<AutocloseRegion>,
1024 snippet_stack: InvalidationStack<SnippetState>,
1025 select_syntax_node_history: SelectSyntaxNodeHistory,
1026 ime_transaction: Option<TransactionId>,
1027 pub diagnostics_max_severity: DiagnosticSeverity,
1028 active_diagnostics: ActiveDiagnostic,
1029 show_inline_diagnostics: bool,
1030 inline_diagnostics_update: Task<()>,
1031 inline_diagnostics_enabled: bool,
1032 diagnostics_enabled: bool,
1033 word_completions_enabled: bool,
1034 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1035 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1036 hard_wrap: Option<usize>,
1037 project: Option<Entity<Project>>,
1038 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1039 completion_provider: Option<Rc<dyn CompletionProvider>>,
1040 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1041 blink_manager: Entity<BlinkManager>,
1042 show_cursor_names: bool,
1043 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1044 pub show_local_selections: bool,
1045 mode: EditorMode,
1046 show_breadcrumbs: bool,
1047 show_gutter: bool,
1048 show_scrollbars: ScrollbarAxes,
1049 minimap_visibility: MinimapVisibility,
1050 offset_content: bool,
1051 disable_expand_excerpt_buttons: bool,
1052 show_line_numbers: Option<bool>,
1053 use_relative_line_numbers: Option<bool>,
1054 show_git_diff_gutter: Option<bool>,
1055 show_code_actions: Option<bool>,
1056 show_runnables: Option<bool>,
1057 show_breakpoints: Option<bool>,
1058 show_wrap_guides: Option<bool>,
1059 show_indent_guides: Option<bool>,
1060 placeholder_text: Option<Arc<str>>,
1061 highlight_order: usize,
1062 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1063 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1064 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1065 scrollbar_marker_state: ScrollbarMarkerState,
1066 active_indent_guides_state: ActiveIndentGuidesState,
1067 nav_history: Option<ItemNavHistory>,
1068 context_menu: RefCell<Option<CodeContextMenu>>,
1069 context_menu_options: Option<ContextMenuOptions>,
1070 mouse_context_menu: Option<MouseContextMenu>,
1071 completion_tasks: Vec<(CompletionId, Task<()>)>,
1072 inline_blame_popover: Option<InlineBlamePopover>,
1073 inline_blame_popover_show_task: Option<Task<()>>,
1074 signature_help_state: SignatureHelpState,
1075 auto_signature_help: Option<bool>,
1076 find_all_references_task_sources: Vec<Anchor>,
1077 next_completion_id: CompletionId,
1078 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1079 code_actions_task: Option<Task<Result<()>>>,
1080 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1082 document_highlights_task: Option<Task<()>>,
1083 linked_editing_range_task: Option<Task<Option<()>>>,
1084 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1085 pending_rename: Option<RenameState>,
1086 searchable: bool,
1087 cursor_shape: CursorShape,
1088 current_line_highlight: Option<CurrentLineHighlight>,
1089 collapse_matches: bool,
1090 autoindent_mode: Option<AutoindentMode>,
1091 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1092 input_enabled: bool,
1093 use_modal_editing: bool,
1094 read_only: bool,
1095 leader_id: Option<CollaboratorId>,
1096 remote_id: Option<ViewId>,
1097 pub hover_state: HoverState,
1098 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1099 gutter_hovered: bool,
1100 hovered_link_state: Option<HoveredLinkState>,
1101 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1102 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1103 active_edit_prediction: Option<EditPredictionState>,
1104 /// Used to prevent flickering as the user types while the menu is open
1105 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1106 edit_prediction_settings: EditPredictionSettings,
1107 edit_predictions_hidden_for_vim_mode: bool,
1108 show_edit_predictions_override: Option<bool>,
1109 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1110 edit_prediction_preview: EditPredictionPreview,
1111 edit_prediction_indent_conflict: bool,
1112 edit_prediction_requires_modifier_in_indent_conflict: bool,
1113 inlay_hint_cache: InlayHintCache,
1114 next_inlay_id: usize,
1115 _subscriptions: Vec<Subscription>,
1116 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1117 gutter_dimensions: GutterDimensions,
1118 style: Option<EditorStyle>,
1119 text_style_refinement: Option<TextStyleRefinement>,
1120 next_editor_action_id: EditorActionId,
1121 editor_actions: Rc<
1122 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1123 >,
1124 use_autoclose: bool,
1125 use_auto_surround: bool,
1126 auto_replace_emoji_shortcode: bool,
1127 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1128 show_git_blame_gutter: bool,
1129 show_git_blame_inline: bool,
1130 show_git_blame_inline_delay_task: Option<Task<()>>,
1131 git_blame_inline_enabled: bool,
1132 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1133 serialize_dirty_buffers: bool,
1134 show_selection_menu: Option<bool>,
1135 blame: Option<Entity<GitBlame>>,
1136 blame_subscription: Option<Subscription>,
1137 custom_context_menu: Option<
1138 Box<
1139 dyn 'static
1140 + Fn(
1141 &mut Self,
1142 DisplayPoint,
1143 &mut Window,
1144 &mut Context<Self>,
1145 ) -> Option<Entity<ui::ContextMenu>>,
1146 >,
1147 >,
1148 last_bounds: Option<Bounds<Pixels>>,
1149 last_position_map: Option<Rc<PositionMap>>,
1150 expect_bounds_change: Option<Bounds<Pixels>>,
1151 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1152 tasks_update_task: Option<Task<()>>,
1153 breakpoint_store: Option<Entity<BreakpointStore>>,
1154 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1155 hovered_diff_hunk_row: Option<DisplayRow>,
1156 pull_diagnostics_task: Task<()>,
1157 in_project_search: bool,
1158 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1159 breadcrumb_header: Option<String>,
1160 focused_block: Option<FocusedBlock>,
1161 next_scroll_position: NextScrollCursorCenterTopBottom,
1162 addons: HashMap<TypeId, Box<dyn Addon>>,
1163 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1164 load_diff_task: Option<Shared<Task<()>>>,
1165 /// Whether we are temporarily displaying a diff other than git's
1166 temporary_diff_override: bool,
1167 selection_mark_mode: bool,
1168 toggle_fold_multiple_buffers: Task<()>,
1169 _scroll_cursor_center_top_bottom_task: Task<()>,
1170 serialize_selections: Task<()>,
1171 serialize_folds: Task<()>,
1172 mouse_cursor_hidden: bool,
1173 minimap: Option<Entity<Self>>,
1174 hide_mouse_mode: HideMouseMode,
1175 pub change_list: ChangeList,
1176 inline_value_cache: InlineValueCache,
1177 selection_drag_state: SelectionDragState,
1178 next_color_inlay_id: usize,
1179 colors: Option<LspColorData>,
1180 folding_newlines: Task<()>,
1181}
1182
1183#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1184enum NextScrollCursorCenterTopBottom {
1185 #[default]
1186 Center,
1187 Top,
1188 Bottom,
1189}
1190
1191impl NextScrollCursorCenterTopBottom {
1192 fn next(&self) -> Self {
1193 match self {
1194 Self::Center => Self::Top,
1195 Self::Top => Self::Bottom,
1196 Self::Bottom => Self::Center,
1197 }
1198 }
1199}
1200
1201#[derive(Clone)]
1202pub struct EditorSnapshot {
1203 pub mode: EditorMode,
1204 show_gutter: bool,
1205 show_line_numbers: Option<bool>,
1206 show_git_diff_gutter: Option<bool>,
1207 show_code_actions: Option<bool>,
1208 show_runnables: Option<bool>,
1209 show_breakpoints: Option<bool>,
1210 git_blame_gutter_max_author_length: Option<usize>,
1211 pub display_snapshot: DisplaySnapshot,
1212 pub placeholder_text: Option<Arc<str>>,
1213 is_focused: bool,
1214 scroll_anchor: ScrollAnchor,
1215 ongoing_scroll: OngoingScroll,
1216 current_line_highlight: CurrentLineHighlight,
1217 gutter_hovered: bool,
1218}
1219
1220#[derive(Default, Debug, Clone, Copy)]
1221pub struct GutterDimensions {
1222 pub left_padding: Pixels,
1223 pub right_padding: Pixels,
1224 pub width: Pixels,
1225 pub margin: Pixels,
1226 pub git_blame_entries_width: Option<Pixels>,
1227}
1228
1229impl GutterDimensions {
1230 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1231 Self {
1232 margin: Self::default_gutter_margin(font_id, font_size, cx),
1233 ..Default::default()
1234 }
1235 }
1236
1237 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1238 -cx.text_system().descent(font_id, font_size)
1239 }
1240 /// The full width of the space taken up by the gutter.
1241 pub fn full_width(&self) -> Pixels {
1242 self.margin + self.width
1243 }
1244
1245 /// The width of the space reserved for the fold indicators,
1246 /// use alongside 'justify_end' and `gutter_width` to
1247 /// right align content with the line numbers
1248 pub fn fold_area_width(&self) -> Pixels {
1249 self.margin + self.right_padding
1250 }
1251}
1252
1253struct CharacterDimensions {
1254 em_width: Pixels,
1255 em_advance: Pixels,
1256 line_height: Pixels,
1257}
1258
1259#[derive(Debug)]
1260pub struct RemoteSelection {
1261 pub replica_id: ReplicaId,
1262 pub selection: Selection<Anchor>,
1263 pub cursor_shape: CursorShape,
1264 pub collaborator_id: CollaboratorId,
1265 pub line_mode: bool,
1266 pub user_name: Option<SharedString>,
1267 pub color: PlayerColor,
1268}
1269
1270#[derive(Clone, Debug)]
1271struct SelectionHistoryEntry {
1272 selections: Arc<[Selection<Anchor>]>,
1273 select_next_state: Option<SelectNextState>,
1274 select_prev_state: Option<SelectNextState>,
1275 add_selections_state: Option<AddSelectionsState>,
1276}
1277
1278#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1279enum SelectionHistoryMode {
1280 Normal,
1281 Undoing,
1282 Redoing,
1283 Skipping,
1284}
1285
1286#[derive(Clone, PartialEq, Eq, Hash)]
1287struct HoveredCursor {
1288 replica_id: u16,
1289 selection_id: usize,
1290}
1291
1292impl Default for SelectionHistoryMode {
1293 fn default() -> Self {
1294 Self::Normal
1295 }
1296}
1297
1298#[derive(Debug)]
1299/// SelectionEffects controls the side-effects of updating the selection.
1300///
1301/// The default behaviour does "what you mostly want":
1302/// - it pushes to the nav history if the cursor moved by >10 lines
1303/// - it re-triggers completion requests
1304/// - it scrolls to fit
1305///
1306/// You might want to modify these behaviours. For example when doing a "jump"
1307/// like go to definition, we always want to add to nav history; but when scrolling
1308/// in vim mode we never do.
1309///
1310/// Similarly, you might want to disable scrolling if you don't want the viewport to
1311/// move.
1312#[derive(Clone)]
1313pub struct SelectionEffects {
1314 nav_history: Option<bool>,
1315 completions: bool,
1316 scroll: Option<Autoscroll>,
1317}
1318
1319impl Default for SelectionEffects {
1320 fn default() -> Self {
1321 Self {
1322 nav_history: None,
1323 completions: true,
1324 scroll: Some(Autoscroll::fit()),
1325 }
1326 }
1327}
1328impl SelectionEffects {
1329 pub fn scroll(scroll: Autoscroll) -> Self {
1330 Self {
1331 scroll: Some(scroll),
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn no_scroll() -> Self {
1337 Self {
1338 scroll: None,
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn completions(self, completions: bool) -> Self {
1344 Self {
1345 completions,
1346 ..self
1347 }
1348 }
1349
1350 pub fn nav_history(self, nav_history: bool) -> Self {
1351 Self {
1352 nav_history: Some(nav_history),
1353 ..self
1354 }
1355 }
1356}
1357
1358struct DeferredSelectionEffectsState {
1359 changed: bool,
1360 effects: SelectionEffects,
1361 old_cursor_position: Anchor,
1362 history_entry: SelectionHistoryEntry,
1363}
1364
1365#[derive(Default)]
1366struct SelectionHistory {
1367 #[allow(clippy::type_complexity)]
1368 selections_by_transaction:
1369 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1370 mode: SelectionHistoryMode,
1371 undo_stack: VecDeque<SelectionHistoryEntry>,
1372 redo_stack: VecDeque<SelectionHistoryEntry>,
1373}
1374
1375impl SelectionHistory {
1376 #[track_caller]
1377 fn insert_transaction(
1378 &mut self,
1379 transaction_id: TransactionId,
1380 selections: Arc<[Selection<Anchor>]>,
1381 ) {
1382 if selections.is_empty() {
1383 log::error!(
1384 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1385 std::panic::Location::caller()
1386 );
1387 return;
1388 }
1389 self.selections_by_transaction
1390 .insert(transaction_id, (selections, None));
1391 }
1392
1393 #[allow(clippy::type_complexity)]
1394 fn transaction(
1395 &self,
1396 transaction_id: TransactionId,
1397 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1398 self.selections_by_transaction.get(&transaction_id)
1399 }
1400
1401 #[allow(clippy::type_complexity)]
1402 fn transaction_mut(
1403 &mut self,
1404 transaction_id: TransactionId,
1405 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1406 self.selections_by_transaction.get_mut(&transaction_id)
1407 }
1408
1409 fn push(&mut self, entry: SelectionHistoryEntry) {
1410 if !entry.selections.is_empty() {
1411 match self.mode {
1412 SelectionHistoryMode::Normal => {
1413 self.push_undo(entry);
1414 self.redo_stack.clear();
1415 }
1416 SelectionHistoryMode::Undoing => self.push_redo(entry),
1417 SelectionHistoryMode::Redoing => self.push_undo(entry),
1418 SelectionHistoryMode::Skipping => {}
1419 }
1420 }
1421 }
1422
1423 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1424 if self
1425 .undo_stack
1426 .back()
1427 .is_none_or(|e| e.selections != entry.selections)
1428 {
1429 self.undo_stack.push_back(entry);
1430 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1431 self.undo_stack.pop_front();
1432 }
1433 }
1434 }
1435
1436 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1437 if self
1438 .redo_stack
1439 .back()
1440 .is_none_or(|e| e.selections != entry.selections)
1441 {
1442 self.redo_stack.push_back(entry);
1443 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1444 self.redo_stack.pop_front();
1445 }
1446 }
1447 }
1448}
1449
1450#[derive(Clone, Copy)]
1451pub struct RowHighlightOptions {
1452 pub autoscroll: bool,
1453 pub include_gutter: bool,
1454}
1455
1456impl Default for RowHighlightOptions {
1457 fn default() -> Self {
1458 Self {
1459 autoscroll: Default::default(),
1460 include_gutter: true,
1461 }
1462 }
1463}
1464
1465struct RowHighlight {
1466 index: usize,
1467 range: Range<Anchor>,
1468 color: Hsla,
1469 options: RowHighlightOptions,
1470 type_id: TypeId,
1471}
1472
1473#[derive(Clone, Debug)]
1474struct AddSelectionsState {
1475 groups: Vec<AddSelectionsGroup>,
1476}
1477
1478#[derive(Clone, Debug)]
1479struct AddSelectionsGroup {
1480 above: bool,
1481 stack: Vec<usize>,
1482}
1483
1484#[derive(Clone)]
1485struct SelectNextState {
1486 query: AhoCorasick,
1487 wordwise: bool,
1488 done: bool,
1489}
1490
1491impl std::fmt::Debug for SelectNextState {
1492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1493 f.debug_struct(std::any::type_name::<Self>())
1494 .field("wordwise", &self.wordwise)
1495 .field("done", &self.done)
1496 .finish()
1497 }
1498}
1499
1500#[derive(Debug)]
1501struct AutocloseRegion {
1502 selection_id: usize,
1503 range: Range<Anchor>,
1504 pair: BracketPair,
1505}
1506
1507#[derive(Debug)]
1508struct SnippetState {
1509 ranges: Vec<Vec<Range<Anchor>>>,
1510 active_index: usize,
1511 choices: Vec<Option<Vec<String>>>,
1512}
1513
1514#[doc(hidden)]
1515pub struct RenameState {
1516 pub range: Range<Anchor>,
1517 pub old_name: Arc<str>,
1518 pub editor: Entity<Editor>,
1519 block_id: CustomBlockId,
1520}
1521
1522struct InvalidationStack<T>(Vec<T>);
1523
1524struct RegisteredEditPredictionProvider {
1525 provider: Arc<dyn EditPredictionProviderHandle>,
1526 _subscription: Subscription,
1527}
1528
1529#[derive(Debug, PartialEq, Eq)]
1530pub struct ActiveDiagnosticGroup {
1531 pub active_range: Range<Anchor>,
1532 pub active_message: String,
1533 pub group_id: usize,
1534 pub blocks: HashSet<CustomBlockId>,
1535}
1536
1537#[derive(Debug, PartialEq, Eq)]
1538
1539pub(crate) enum ActiveDiagnostic {
1540 None,
1541 All,
1542 Group(ActiveDiagnosticGroup),
1543}
1544
1545#[derive(Serialize, Deserialize, Clone, Debug)]
1546pub struct ClipboardSelection {
1547 /// The number of bytes in this selection.
1548 pub len: usize,
1549 /// Whether this was a full-line selection.
1550 pub is_entire_line: bool,
1551 /// The indentation of the first line when this content was originally copied.
1552 pub first_line_indent: u32,
1553}
1554
1555// selections, scroll behavior, was newest selection reversed
1556type SelectSyntaxNodeHistoryState = (
1557 Box<[Selection<usize>]>,
1558 SelectSyntaxNodeScrollBehavior,
1559 bool,
1560);
1561
1562#[derive(Default)]
1563struct SelectSyntaxNodeHistory {
1564 stack: Vec<SelectSyntaxNodeHistoryState>,
1565 // disable temporarily to allow changing selections without losing the stack
1566 pub disable_clearing: bool,
1567}
1568
1569impl SelectSyntaxNodeHistory {
1570 pub fn try_clear(&mut self) {
1571 if !self.disable_clearing {
1572 self.stack.clear();
1573 }
1574 }
1575
1576 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1577 self.stack.push(selection);
1578 }
1579
1580 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1581 self.stack.pop()
1582 }
1583}
1584
1585enum SelectSyntaxNodeScrollBehavior {
1586 CursorTop,
1587 FitSelection,
1588 CursorBottom,
1589}
1590
1591#[derive(Debug)]
1592pub(crate) struct NavigationData {
1593 cursor_anchor: Anchor,
1594 cursor_position: Point,
1595 scroll_anchor: ScrollAnchor,
1596 scroll_top_row: u32,
1597}
1598
1599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1600pub enum GotoDefinitionKind {
1601 Symbol,
1602 Declaration,
1603 Type,
1604 Implementation,
1605}
1606
1607#[derive(Debug, Clone)]
1608enum InlayHintRefreshReason {
1609 ModifiersChanged(bool),
1610 Toggle(bool),
1611 SettingsChange(InlayHintSettings),
1612 NewLinesShown,
1613 BufferEdited(HashSet<Arc<Language>>),
1614 RefreshRequested,
1615 ExcerptsRemoved(Vec<ExcerptId>),
1616}
1617
1618impl InlayHintRefreshReason {
1619 fn description(&self) -> &'static str {
1620 match self {
1621 Self::ModifiersChanged(_) => "modifiers changed",
1622 Self::Toggle(_) => "toggle",
1623 Self::SettingsChange(_) => "settings change",
1624 Self::NewLinesShown => "new lines shown",
1625 Self::BufferEdited(_) => "buffer edited",
1626 Self::RefreshRequested => "refresh requested",
1627 Self::ExcerptsRemoved(_) => "excerpts removed",
1628 }
1629 }
1630}
1631
1632pub enum FormatTarget {
1633 Buffers(HashSet<Entity<Buffer>>),
1634 Ranges(Vec<Range<MultiBufferPoint>>),
1635}
1636
1637pub(crate) struct FocusedBlock {
1638 id: BlockId,
1639 focus_handle: WeakFocusHandle,
1640}
1641
1642#[derive(Clone)]
1643enum JumpData {
1644 MultiBufferRow {
1645 row: MultiBufferRow,
1646 line_offset_from_top: u32,
1647 },
1648 MultiBufferPoint {
1649 excerpt_id: ExcerptId,
1650 position: Point,
1651 anchor: text::Anchor,
1652 line_offset_from_top: u32,
1653 },
1654}
1655
1656pub enum MultibufferSelectionMode {
1657 First,
1658 All,
1659}
1660
1661#[derive(Clone, Copy, Debug, Default)]
1662pub struct RewrapOptions {
1663 pub override_language_settings: bool,
1664 pub preserve_existing_whitespace: bool,
1665}
1666
1667impl Editor {
1668 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1669 let buffer = cx.new(|cx| Buffer::local("", cx));
1670 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1671 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1672 }
1673
1674 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(EditorMode::full(), buffer, None, window, cx)
1678 }
1679
1680 pub fn auto_height(
1681 min_lines: usize,
1682 max_lines: usize,
1683 window: &mut Window,
1684 cx: &mut Context<Self>,
1685 ) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(
1689 EditorMode::AutoHeight {
1690 min_lines,
1691 max_lines: Some(max_lines),
1692 },
1693 buffer,
1694 None,
1695 window,
1696 cx,
1697 )
1698 }
1699
1700 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1701 /// The editor grows as tall as needed to fit its content.
1702 pub fn auto_height_unbounded(
1703 min_lines: usize,
1704 window: &mut Window,
1705 cx: &mut Context<Self>,
1706 ) -> Self {
1707 let buffer = cx.new(|cx| Buffer::local("", cx));
1708 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1709 Self::new(
1710 EditorMode::AutoHeight {
1711 min_lines,
1712 max_lines: None,
1713 },
1714 buffer,
1715 None,
1716 window,
1717 cx,
1718 )
1719 }
1720
1721 pub fn for_buffer(
1722 buffer: Entity<Buffer>,
1723 project: Option<Entity<Project>>,
1724 window: &mut Window,
1725 cx: &mut Context<Self>,
1726 ) -> Self {
1727 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1728 Self::new(EditorMode::full(), buffer, project, window, cx)
1729 }
1730
1731 pub fn for_multibuffer(
1732 buffer: Entity<MultiBuffer>,
1733 project: Option<Entity<Project>>,
1734 window: &mut Window,
1735 cx: &mut Context<Self>,
1736 ) -> Self {
1737 Self::new(EditorMode::full(), buffer, project, window, cx)
1738 }
1739
1740 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1741 let mut clone = Self::new(
1742 self.mode.clone(),
1743 self.buffer.clone(),
1744 self.project.clone(),
1745 window,
1746 cx,
1747 );
1748 self.display_map.update(cx, |display_map, cx| {
1749 let snapshot = display_map.snapshot(cx);
1750 clone.display_map.update(cx, |display_map, cx| {
1751 display_map.set_state(&snapshot, cx);
1752 });
1753 });
1754 clone.folds_did_change(cx);
1755 clone.selections.clone_state(&self.selections);
1756 clone.scroll_manager.clone_state(&self.scroll_manager);
1757 clone.searchable = self.searchable;
1758 clone.read_only = self.read_only;
1759 clone
1760 }
1761
1762 pub fn new(
1763 mode: EditorMode,
1764 buffer: Entity<MultiBuffer>,
1765 project: Option<Entity<Project>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 Editor::new_internal(mode, buffer, project, None, window, cx)
1770 }
1771
1772 fn new_internal(
1773 mode: EditorMode,
1774 buffer: Entity<MultiBuffer>,
1775 project: Option<Entity<Project>>,
1776 display_map: Option<Entity<DisplayMap>>,
1777 window: &mut Window,
1778 cx: &mut Context<Self>,
1779 ) -> Self {
1780 debug_assert!(
1781 display_map.is_none() || mode.is_minimap(),
1782 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1783 );
1784
1785 let full_mode = mode.is_full();
1786 let is_minimap = mode.is_minimap();
1787 let diagnostics_max_severity = if full_mode {
1788 EditorSettings::get_global(cx)
1789 .diagnostics_max_severity
1790 .unwrap_or(DiagnosticSeverity::Hint)
1791 } else {
1792 DiagnosticSeverity::Off
1793 };
1794 let style = window.text_style();
1795 let font_size = style.font_size.to_pixels(window.rem_size());
1796 let editor = cx.entity().downgrade();
1797 let fold_placeholder = FoldPlaceholder {
1798 constrain_width: false,
1799 render: Arc::new(move |fold_id, fold_range, cx| {
1800 let editor = editor.clone();
1801 div()
1802 .id(fold_id)
1803 .bg(cx.theme().colors().ghost_element_background)
1804 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1805 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1806 .rounded_xs()
1807 .size_full()
1808 .cursor_pointer()
1809 .child("⋯")
1810 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1811 .on_click(move |_, _window, cx| {
1812 editor
1813 .update(cx, |editor, cx| {
1814 editor.unfold_ranges(
1815 &[fold_range.start..fold_range.end],
1816 true,
1817 false,
1818 cx,
1819 );
1820 cx.stop_propagation();
1821 })
1822 .ok();
1823 })
1824 .into_any()
1825 }),
1826 merge_adjacent: true,
1827 ..FoldPlaceholder::default()
1828 };
1829 let display_map = display_map.unwrap_or_else(|| {
1830 cx.new(|cx| {
1831 DisplayMap::new(
1832 buffer.clone(),
1833 style.font(),
1834 font_size,
1835 None,
1836 FILE_HEADER_HEIGHT,
1837 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1838 fold_placeholder,
1839 diagnostics_max_severity,
1840 cx,
1841 )
1842 })
1843 });
1844
1845 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1846
1847 let blink_manager = cx.new(|cx| {
1848 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1849 if is_minimap {
1850 blink_manager.disable(cx);
1851 }
1852 blink_manager
1853 });
1854
1855 let soft_wrap_mode_override =
1856 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1857
1858 let mut project_subscriptions = Vec::new();
1859 if full_mode && let Some(project) = project.as_ref() {
1860 project_subscriptions.push(cx.subscribe_in(
1861 project,
1862 window,
1863 |editor, _, event, window, cx| match event {
1864 project::Event::RefreshCodeLens => {
1865 // we always query lens with actions, without storing them, always refreshing them
1866 }
1867 project::Event::RefreshInlayHints => {
1868 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1869 }
1870 project::Event::LanguageServerAdded(..)
1871 | project::Event::LanguageServerRemoved(..) => {
1872 if editor.tasks_update_task.is_none() {
1873 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1874 }
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1897 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1898 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1899 }
1900 }
1901
1902 project::Event::EntryRenamed(transaction) => {
1903 let Some(workspace) = editor.workspace() else {
1904 return;
1905 };
1906 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1907 else {
1908 return;
1909 };
1910 if active_editor.entity_id() == cx.entity_id() {
1911 let edited_buffers_already_open = {
1912 let other_editors: Vec<Entity<Editor>> = workspace
1913 .read(cx)
1914 .panes()
1915 .iter()
1916 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1917 .filter(|editor| editor.entity_id() != cx.entity_id())
1918 .collect();
1919
1920 transaction.0.keys().all(|buffer| {
1921 other_editors.iter().any(|editor| {
1922 let multi_buffer = editor.read(cx).buffer();
1923 multi_buffer.read(cx).is_singleton()
1924 && multi_buffer.read(cx).as_singleton().map_or(
1925 false,
1926 |singleton| {
1927 singleton.entity_id() == buffer.entity_id()
1928 },
1929 )
1930 })
1931 })
1932 };
1933
1934 if !edited_buffers_already_open {
1935 let workspace = workspace.downgrade();
1936 let transaction = transaction.clone();
1937 cx.defer_in(window, move |_, window, cx| {
1938 cx.spawn_in(window, async move |editor, cx| {
1939 Self::open_project_transaction(
1940 &editor,
1941 workspace,
1942 transaction,
1943 "Rename".to_string(),
1944 cx,
1945 )
1946 .await
1947 .ok()
1948 })
1949 .detach();
1950 });
1951 }
1952 }
1953 }
1954
1955 _ => {}
1956 },
1957 ));
1958 if let Some(task_inventory) = project
1959 .read(cx)
1960 .task_store()
1961 .read(cx)
1962 .task_inventory()
1963 .cloned()
1964 {
1965 project_subscriptions.push(cx.observe_in(
1966 &task_inventory,
1967 window,
1968 |editor, _, window, cx| {
1969 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1970 },
1971 ));
1972 };
1973
1974 project_subscriptions.push(cx.subscribe_in(
1975 &project.read(cx).breakpoint_store(),
1976 window,
1977 |editor, _, event, window, cx| match event {
1978 BreakpointStoreEvent::ClearDebugLines => {
1979 editor.clear_row_highlights::<ActiveDebugLine>();
1980 editor.refresh_inline_values(cx);
1981 }
1982 BreakpointStoreEvent::SetDebugLine => {
1983 if editor.go_to_active_debug_line(window, cx) {
1984 cx.stop_propagation();
1985 }
1986
1987 editor.refresh_inline_values(cx);
1988 }
1989 _ => {}
1990 },
1991 ));
1992 let git_store = project.read(cx).git_store().clone();
1993 let project = project.clone();
1994 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1995 if let GitStoreEvent::RepositoryUpdated(
1996 _,
1997 RepositoryEvent::Updated {
1998 new_instance: true, ..
1999 },
2000 _,
2001 ) = event
2002 {
2003 this.load_diff_task = Some(
2004 update_uncommitted_diff_for_buffer(
2005 cx.entity(),
2006 &project,
2007 this.buffer.read(cx).all_buffers(),
2008 this.buffer.clone(),
2009 cx,
2010 )
2011 .shared(),
2012 );
2013 }
2014 }));
2015 }
2016
2017 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2018
2019 let inlay_hint_settings =
2020 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2021 let focus_handle = cx.focus_handle();
2022 if !is_minimap {
2023 cx.on_focus(&focus_handle, window, Self::handle_focus)
2024 .detach();
2025 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2026 .detach();
2027 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2028 .detach();
2029 cx.on_blur(&focus_handle, window, Self::handle_blur)
2030 .detach();
2031 cx.observe_pending_input(window, Self::observe_pending_input)
2032 .detach();
2033 }
2034
2035 let show_indent_guides =
2036 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2037 Some(false)
2038 } else {
2039 None
2040 };
2041
2042 let breakpoint_store = match (&mode, project.as_ref()) {
2043 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2044 _ => None,
2045 };
2046
2047 let mut code_action_providers = Vec::new();
2048 let mut load_uncommitted_diff = None;
2049 if let Some(project) = project.clone() {
2050 load_uncommitted_diff = Some(
2051 update_uncommitted_diff_for_buffer(
2052 cx.entity(),
2053 &project,
2054 buffer.read(cx).all_buffers(),
2055 buffer.clone(),
2056 cx,
2057 )
2058 .shared(),
2059 );
2060 code_action_providers.push(Rc::new(project) as Rc<_>);
2061 }
2062
2063 let mut editor = Self {
2064 focus_handle,
2065 show_cursor_when_unfocused: false,
2066 last_focused_descendant: None,
2067 buffer: buffer.clone(),
2068 display_map: display_map.clone(),
2069 selections,
2070 scroll_manager: ScrollManager::new(cx),
2071 columnar_selection_state: None,
2072 add_selections_state: None,
2073 select_next_state: None,
2074 select_prev_state: None,
2075 selection_history: SelectionHistory::default(),
2076 defer_selection_effects: false,
2077 deferred_selection_effects_state: None,
2078 autoclose_regions: Vec::new(),
2079 snippet_stack: InvalidationStack::default(),
2080 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2081 ime_transaction: None,
2082 active_diagnostics: ActiveDiagnostic::None,
2083 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2084 inline_diagnostics_update: Task::ready(()),
2085 inline_diagnostics: Vec::new(),
2086 soft_wrap_mode_override,
2087 diagnostics_max_severity,
2088 hard_wrap: None,
2089 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2090 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2091 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2092 project,
2093 blink_manager: blink_manager.clone(),
2094 show_local_selections: true,
2095 show_scrollbars: ScrollbarAxes {
2096 horizontal: full_mode,
2097 vertical: full_mode,
2098 },
2099 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2100 offset_content: !matches!(mode, EditorMode::SingleLine),
2101 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2102 show_gutter: full_mode,
2103 show_line_numbers: (!full_mode).then_some(false),
2104 use_relative_line_numbers: None,
2105 disable_expand_excerpt_buttons: !full_mode,
2106 show_git_diff_gutter: None,
2107 show_code_actions: None,
2108 show_runnables: None,
2109 show_breakpoints: None,
2110 show_wrap_guides: None,
2111 show_indent_guides,
2112 placeholder_text: None,
2113 highlight_order: 0,
2114 highlighted_rows: HashMap::default(),
2115 background_highlights: HashMap::default(),
2116 gutter_highlights: HashMap::default(),
2117 scrollbar_marker_state: ScrollbarMarkerState::default(),
2118 active_indent_guides_state: ActiveIndentGuidesState::default(),
2119 nav_history: None,
2120 context_menu: RefCell::new(None),
2121 context_menu_options: None,
2122 mouse_context_menu: None,
2123 completion_tasks: Vec::new(),
2124 inline_blame_popover: None,
2125 inline_blame_popover_show_task: None,
2126 signature_help_state: SignatureHelpState::default(),
2127 auto_signature_help: None,
2128 find_all_references_task_sources: Vec::new(),
2129 next_completion_id: 0,
2130 next_inlay_id: 0,
2131 code_action_providers,
2132 available_code_actions: None,
2133 code_actions_task: None,
2134 quick_selection_highlight_task: None,
2135 debounced_selection_highlight_task: None,
2136 document_highlights_task: None,
2137 linked_editing_range_task: None,
2138 pending_rename: None,
2139 searchable: !is_minimap,
2140 cursor_shape: EditorSettings::get_global(cx)
2141 .cursor_shape
2142 .unwrap_or_default(),
2143 current_line_highlight: None,
2144 autoindent_mode: Some(AutoindentMode::EachLine),
2145 collapse_matches: false,
2146 workspace: None,
2147 input_enabled: !is_minimap,
2148 use_modal_editing: full_mode,
2149 read_only: is_minimap,
2150 use_autoclose: true,
2151 use_auto_surround: true,
2152 auto_replace_emoji_shortcode: false,
2153 jsx_tag_auto_close_enabled_in_any_buffer: false,
2154 leader_id: None,
2155 remote_id: None,
2156 hover_state: HoverState::default(),
2157 pending_mouse_down: None,
2158 hovered_link_state: None,
2159 edit_prediction_provider: None,
2160 active_edit_prediction: None,
2161 stale_edit_prediction_in_menu: None,
2162 edit_prediction_preview: EditPredictionPreview::Inactive {
2163 released_too_fast: false,
2164 },
2165 inline_diagnostics_enabled: full_mode,
2166 diagnostics_enabled: full_mode,
2167 word_completions_enabled: full_mode,
2168 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2169 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2170 gutter_hovered: false,
2171 pixel_position_of_newest_cursor: None,
2172 last_bounds: None,
2173 last_position_map: None,
2174 expect_bounds_change: None,
2175 gutter_dimensions: GutterDimensions::default(),
2176 style: None,
2177 show_cursor_names: false,
2178 hovered_cursors: HashMap::default(),
2179 next_editor_action_id: EditorActionId::default(),
2180 editor_actions: Rc::default(),
2181 edit_predictions_hidden_for_vim_mode: false,
2182 show_edit_predictions_override: None,
2183 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2184 edit_prediction_settings: EditPredictionSettings::Disabled,
2185 edit_prediction_indent_conflict: false,
2186 edit_prediction_requires_modifier_in_indent_conflict: true,
2187 custom_context_menu: None,
2188 show_git_blame_gutter: false,
2189 show_git_blame_inline: false,
2190 show_selection_menu: None,
2191 show_git_blame_inline_delay_task: None,
2192 git_blame_inline_enabled: full_mode
2193 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2194 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2195 serialize_dirty_buffers: !is_minimap
2196 && ProjectSettings::get_global(cx)
2197 .session
2198 .restore_unsaved_buffers,
2199 blame: None,
2200 blame_subscription: None,
2201 tasks: BTreeMap::default(),
2202
2203 breakpoint_store,
2204 gutter_breakpoint_indicator: (None, None),
2205 hovered_diff_hunk_row: None,
2206 _subscriptions: (!is_minimap)
2207 .then(|| {
2208 vec![
2209 cx.observe(&buffer, Self::on_buffer_changed),
2210 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2211 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2212 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2213 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2214 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2215 cx.observe_window_activation(window, |editor, window, cx| {
2216 let active = window.is_window_active();
2217 editor.blink_manager.update(cx, |blink_manager, cx| {
2218 if active {
2219 blink_manager.enable(cx);
2220 } else {
2221 blink_manager.disable(cx);
2222 }
2223 });
2224 if active {
2225 editor.show_mouse_cursor(cx);
2226 }
2227 }),
2228 ]
2229 })
2230 .unwrap_or_default(),
2231 tasks_update_task: None,
2232 pull_diagnostics_task: Task::ready(()),
2233 colors: None,
2234 next_color_inlay_id: 0,
2235 linked_edit_ranges: Default::default(),
2236 in_project_search: false,
2237 previous_search_ranges: None,
2238 breadcrumb_header: None,
2239 focused_block: None,
2240 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2241 addons: HashMap::default(),
2242 registered_buffers: HashMap::default(),
2243 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2244 selection_mark_mode: false,
2245 toggle_fold_multiple_buffers: Task::ready(()),
2246 serialize_selections: Task::ready(()),
2247 serialize_folds: Task::ready(()),
2248 text_style_refinement: None,
2249 load_diff_task: load_uncommitted_diff,
2250 temporary_diff_override: false,
2251 mouse_cursor_hidden: false,
2252 minimap: None,
2253 hide_mouse_mode: EditorSettings::get_global(cx)
2254 .hide_mouse
2255 .unwrap_or_default(),
2256 change_list: ChangeList::new(),
2257 mode,
2258 selection_drag_state: SelectionDragState::None,
2259 folding_newlines: Task::ready(()),
2260 };
2261
2262 if is_minimap {
2263 return editor;
2264 }
2265
2266 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2267 editor
2268 ._subscriptions
2269 .push(cx.observe(breakpoints, |_, _, cx| {
2270 cx.notify();
2271 }));
2272 }
2273 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2274 editor._subscriptions.extend(project_subscriptions);
2275
2276 editor._subscriptions.push(cx.subscribe_in(
2277 &cx.entity(),
2278 window,
2279 |editor, _, e: &EditorEvent, window, cx| match e {
2280 EditorEvent::ScrollPositionChanged { local, .. } => {
2281 if *local {
2282 let new_anchor = editor.scroll_manager.anchor();
2283 let snapshot = editor.snapshot(window, cx);
2284 editor.update_restoration_data(cx, move |data| {
2285 data.scroll_position = (
2286 new_anchor.top_row(&snapshot.buffer_snapshot),
2287 new_anchor.offset,
2288 );
2289 });
2290 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2291 editor.inline_blame_popover.take();
2292 }
2293 }
2294 EditorEvent::Edited { .. } => {
2295 if !vim_enabled(cx) {
2296 let (map, selections) = editor.selections.all_adjusted_display(cx);
2297 let pop_state = editor
2298 .change_list
2299 .last()
2300 .map(|previous| {
2301 previous.len() == selections.len()
2302 && previous.iter().enumerate().all(|(ix, p)| {
2303 p.to_display_point(&map).row()
2304 == selections[ix].head().row()
2305 })
2306 })
2307 .unwrap_or(false);
2308 let new_positions = selections
2309 .into_iter()
2310 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2311 .collect();
2312 editor
2313 .change_list
2314 .push_to_change_list(pop_state, new_positions);
2315 }
2316 }
2317 _ => (),
2318 },
2319 ));
2320
2321 if let Some(dap_store) = editor
2322 .project
2323 .as_ref()
2324 .map(|project| project.read(cx).dap_store())
2325 {
2326 let weak_editor = cx.weak_entity();
2327
2328 editor
2329 ._subscriptions
2330 .push(
2331 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2332 let session_entity = cx.entity();
2333 weak_editor
2334 .update(cx, |editor, cx| {
2335 editor._subscriptions.push(
2336 cx.subscribe(&session_entity, Self::on_debug_session_event),
2337 );
2338 })
2339 .ok();
2340 }),
2341 );
2342
2343 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2344 editor
2345 ._subscriptions
2346 .push(cx.subscribe(&session, Self::on_debug_session_event));
2347 }
2348 }
2349
2350 // skip adding the initial selection to selection history
2351 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2352 editor.end_selection(window, cx);
2353 editor.selection_history.mode = SelectionHistoryMode::Normal;
2354
2355 editor.scroll_manager.show_scrollbars(window, cx);
2356 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2357
2358 if full_mode {
2359 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2360 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2361
2362 if editor.git_blame_inline_enabled {
2363 editor.start_git_blame_inline(false, window, cx);
2364 }
2365
2366 editor.go_to_active_debug_line(window, cx);
2367
2368 if let Some(buffer) = buffer.read(cx).as_singleton()
2369 && let Some(project) = editor.project()
2370 {
2371 let handle = project.update(cx, |project, cx| {
2372 project.register_buffer_with_language_servers(&buffer, cx)
2373 });
2374 editor
2375 .registered_buffers
2376 .insert(buffer.read(cx).remote_id(), handle);
2377 }
2378
2379 editor.minimap =
2380 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2381 editor.colors = Some(LspColorData::new(cx));
2382 editor.update_lsp_data(false, None, window, cx);
2383 }
2384
2385 if editor.mode.is_full() {
2386 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2387 }
2388
2389 editor
2390 }
2391
2392 pub fn deploy_mouse_context_menu(
2393 &mut self,
2394 position: gpui::Point<Pixels>,
2395 context_menu: Entity<ContextMenu>,
2396 window: &mut Window,
2397 cx: &mut Context<Self>,
2398 ) {
2399 self.mouse_context_menu = Some(MouseContextMenu::new(
2400 self,
2401 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2402 context_menu,
2403 window,
2404 cx,
2405 ));
2406 }
2407
2408 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2409 self.mouse_context_menu
2410 .as_ref()
2411 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2412 }
2413
2414 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2415 if self
2416 .selections
2417 .pending
2418 .as_ref()
2419 .is_some_and(|pending_selection| {
2420 let snapshot = self.buffer().read(cx).snapshot(cx);
2421 pending_selection
2422 .selection
2423 .range()
2424 .includes(range, &snapshot)
2425 })
2426 {
2427 return true;
2428 }
2429
2430 self.selections
2431 .disjoint_in_range::<usize>(range.clone(), cx)
2432 .into_iter()
2433 .any(|selection| {
2434 // This is needed to cover a corner case, if we just check for an existing
2435 // selection in the fold range, having a cursor at the start of the fold
2436 // marks it as selected. Non-empty selections don't cause this.
2437 let length = selection.end - selection.start;
2438 length > 0
2439 })
2440 }
2441
2442 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2443 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2444 }
2445
2446 fn key_context_internal(
2447 &self,
2448 has_active_edit_prediction: bool,
2449 window: &Window,
2450 cx: &App,
2451 ) -> KeyContext {
2452 let mut key_context = KeyContext::new_with_defaults();
2453 key_context.add("Editor");
2454 let mode = match self.mode {
2455 EditorMode::SingleLine => "single_line",
2456 EditorMode::AutoHeight { .. } => "auto_height",
2457 EditorMode::Minimap { .. } => "minimap",
2458 EditorMode::Full { .. } => "full",
2459 };
2460
2461 if EditorSettings::jupyter_enabled(cx) {
2462 key_context.add("jupyter");
2463 }
2464
2465 key_context.set("mode", mode);
2466 if self.pending_rename.is_some() {
2467 key_context.add("renaming");
2468 }
2469
2470 match self.context_menu.borrow().as_ref() {
2471 Some(CodeContextMenu::Completions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_completions");
2475 }
2476 }
2477 Some(CodeContextMenu::CodeActions(menu)) => {
2478 if menu.visible() {
2479 key_context.add("menu");
2480 key_context.add("showing_code_actions")
2481 }
2482 }
2483 None => {}
2484 }
2485
2486 if self.signature_help_state.has_multiple_signatures() {
2487 key_context.add("showing_signature_help");
2488 }
2489
2490 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2491 if !self.focus_handle(cx).contains_focused(window, cx)
2492 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2493 {
2494 for addon in self.addons.values() {
2495 addon.extend_key_context(&mut key_context, cx)
2496 }
2497 }
2498
2499 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2500 if let Some(extension) = singleton_buffer
2501 .read(cx)
2502 .file()
2503 .and_then(|file| file.path().extension()?.to_str())
2504 {
2505 key_context.set("extension", extension.to_string());
2506 }
2507 } else {
2508 key_context.add("multibuffer");
2509 }
2510
2511 if has_active_edit_prediction {
2512 if self.edit_prediction_in_conflict() {
2513 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2514 } else {
2515 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2516 key_context.add("copilot_suggestion");
2517 }
2518 }
2519
2520 if self.selection_mark_mode {
2521 key_context.add("selection_mode");
2522 }
2523
2524 key_context
2525 }
2526
2527 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2528 if self.mouse_cursor_hidden {
2529 self.mouse_cursor_hidden = false;
2530 cx.notify();
2531 }
2532 }
2533
2534 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2535 let hide_mouse_cursor = match origin {
2536 HideMouseCursorOrigin::TypingAction => {
2537 matches!(
2538 self.hide_mouse_mode,
2539 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2540 )
2541 }
2542 HideMouseCursorOrigin::MovementAction => {
2543 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2544 }
2545 };
2546 if self.mouse_cursor_hidden != hide_mouse_cursor {
2547 self.mouse_cursor_hidden = hide_mouse_cursor;
2548 cx.notify();
2549 }
2550 }
2551
2552 pub fn edit_prediction_in_conflict(&self) -> bool {
2553 if !self.show_edit_predictions_in_menu() {
2554 return false;
2555 }
2556
2557 let showing_completions = self
2558 .context_menu
2559 .borrow()
2560 .as_ref()
2561 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2562
2563 showing_completions
2564 || self.edit_prediction_requires_modifier()
2565 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2566 // bindings to insert tab characters.
2567 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2568 }
2569
2570 pub fn accept_edit_prediction_keybind(
2571 &self,
2572 accept_partial: bool,
2573 window: &Window,
2574 cx: &App,
2575 ) -> AcceptEditPredictionBinding {
2576 let key_context = self.key_context_internal(true, window, cx);
2577 let in_conflict = self.edit_prediction_in_conflict();
2578
2579 let bindings = if accept_partial {
2580 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2581 } else {
2582 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2583 };
2584
2585 // TODO: if the binding contains multiple keystrokes, display all of them, not
2586 // just the first one.
2587 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2588 !in_conflict
2589 || binding
2590 .keystrokes()
2591 .first()
2592 .is_some_and(|keystroke| keystroke.modifiers().modified())
2593 }))
2594 }
2595
2596 pub fn new_file(
2597 workspace: &mut Workspace,
2598 _: &workspace::NewFile,
2599 window: &mut Window,
2600 cx: &mut Context<Workspace>,
2601 ) {
2602 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2603 "Failed to create buffer",
2604 window,
2605 cx,
2606 |e, _, _| match e.error_code() {
2607 ErrorCode::RemoteUpgradeRequired => Some(format!(
2608 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2609 e.error_tag("required").unwrap_or("the latest version")
2610 )),
2611 _ => None,
2612 },
2613 );
2614 }
2615
2616 pub fn new_in_workspace(
2617 workspace: &mut Workspace,
2618 window: &mut Window,
2619 cx: &mut Context<Workspace>,
2620 ) -> Task<Result<Entity<Editor>>> {
2621 let project = workspace.project().clone();
2622 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2623
2624 cx.spawn_in(window, async move |workspace, cx| {
2625 let buffer = create.await?;
2626 workspace.update_in(cx, |workspace, window, cx| {
2627 let editor =
2628 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2629 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2630 editor
2631 })
2632 })
2633 }
2634
2635 fn new_file_vertical(
2636 workspace: &mut Workspace,
2637 _: &workspace::NewFileSplitVertical,
2638 window: &mut Window,
2639 cx: &mut Context<Workspace>,
2640 ) {
2641 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2642 }
2643
2644 fn new_file_horizontal(
2645 workspace: &mut Workspace,
2646 _: &workspace::NewFileSplitHorizontal,
2647 window: &mut Window,
2648 cx: &mut Context<Workspace>,
2649 ) {
2650 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2651 }
2652
2653 fn new_file_in_direction(
2654 workspace: &mut Workspace,
2655 direction: SplitDirection,
2656 window: &mut Window,
2657 cx: &mut Context<Workspace>,
2658 ) {
2659 let project = workspace.project().clone();
2660 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2661
2662 cx.spawn_in(window, async move |workspace, cx| {
2663 let buffer = create.await?;
2664 workspace.update_in(cx, move |workspace, window, cx| {
2665 workspace.split_item(
2666 direction,
2667 Box::new(
2668 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2669 ),
2670 window,
2671 cx,
2672 )
2673 })?;
2674 anyhow::Ok(())
2675 })
2676 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2677 match e.error_code() {
2678 ErrorCode::RemoteUpgradeRequired => Some(format!(
2679 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2680 e.error_tag("required").unwrap_or("the latest version")
2681 )),
2682 _ => None,
2683 }
2684 });
2685 }
2686
2687 pub fn leader_id(&self) -> Option<CollaboratorId> {
2688 self.leader_id
2689 }
2690
2691 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2692 &self.buffer
2693 }
2694
2695 pub fn project(&self) -> Option<&Entity<Project>> {
2696 self.project.as_ref()
2697 }
2698
2699 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2700 self.workspace.as_ref()?.0.upgrade()
2701 }
2702
2703 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2704 self.buffer().read(cx).title(cx)
2705 }
2706
2707 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2708 let git_blame_gutter_max_author_length = self
2709 .render_git_blame_gutter(cx)
2710 .then(|| {
2711 if let Some(blame) = self.blame.as_ref() {
2712 let max_author_length =
2713 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2714 Some(max_author_length)
2715 } else {
2716 None
2717 }
2718 })
2719 .flatten();
2720
2721 EditorSnapshot {
2722 mode: self.mode.clone(),
2723 show_gutter: self.show_gutter,
2724 show_line_numbers: self.show_line_numbers,
2725 show_git_diff_gutter: self.show_git_diff_gutter,
2726 show_code_actions: self.show_code_actions,
2727 show_runnables: self.show_runnables,
2728 show_breakpoints: self.show_breakpoints,
2729 git_blame_gutter_max_author_length,
2730 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2731 scroll_anchor: self.scroll_manager.anchor(),
2732 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2733 placeholder_text: self.placeholder_text.clone(),
2734 is_focused: self.focus_handle.is_focused(window),
2735 current_line_highlight: self
2736 .current_line_highlight
2737 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2738 gutter_hovered: self.gutter_hovered,
2739 }
2740 }
2741
2742 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2743 self.buffer.read(cx).language_at(point, cx)
2744 }
2745
2746 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2747 self.buffer.read(cx).read(cx).file_at(point).cloned()
2748 }
2749
2750 pub fn active_excerpt(
2751 &self,
2752 cx: &App,
2753 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2754 self.buffer
2755 .read(cx)
2756 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2757 }
2758
2759 pub fn mode(&self) -> &EditorMode {
2760 &self.mode
2761 }
2762
2763 pub fn set_mode(&mut self, mode: EditorMode) {
2764 self.mode = mode;
2765 }
2766
2767 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2768 self.collaboration_hub.as_deref()
2769 }
2770
2771 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2772 self.collaboration_hub = Some(hub);
2773 }
2774
2775 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2776 self.in_project_search = in_project_search;
2777 }
2778
2779 pub fn set_custom_context_menu(
2780 &mut self,
2781 f: impl 'static
2782 + Fn(
2783 &mut Self,
2784 DisplayPoint,
2785 &mut Window,
2786 &mut Context<Self>,
2787 ) -> Option<Entity<ui::ContextMenu>>,
2788 ) {
2789 self.custom_context_menu = Some(Box::new(f))
2790 }
2791
2792 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2793 self.completion_provider = provider;
2794 }
2795
2796 #[cfg(any(test, feature = "test-support"))]
2797 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2798 self.completion_provider.clone()
2799 }
2800
2801 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2802 self.semantics_provider.clone()
2803 }
2804
2805 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2806 self.semantics_provider = provider;
2807 }
2808
2809 pub fn set_edit_prediction_provider<T>(
2810 &mut self,
2811 provider: Option<Entity<T>>,
2812 window: &mut Window,
2813 cx: &mut Context<Self>,
2814 ) where
2815 T: EditPredictionProvider,
2816 {
2817 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2818 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2819 if this.focus_handle.is_focused(window) {
2820 this.update_visible_edit_prediction(window, cx);
2821 }
2822 }),
2823 provider: Arc::new(provider),
2824 });
2825 self.update_edit_prediction_settings(cx);
2826 self.refresh_edit_prediction(false, false, window, cx);
2827 }
2828
2829 pub fn placeholder_text(&self) -> Option<&str> {
2830 self.placeholder_text.as_deref()
2831 }
2832
2833 pub fn set_placeholder_text(
2834 &mut self,
2835 placeholder_text: impl Into<Arc<str>>,
2836 cx: &mut Context<Self>,
2837 ) {
2838 let placeholder_text = Some(placeholder_text.into());
2839 if self.placeholder_text != placeholder_text {
2840 self.placeholder_text = placeholder_text;
2841 cx.notify();
2842 }
2843 }
2844
2845 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2846 self.cursor_shape = cursor_shape;
2847
2848 // Disrupt blink for immediate user feedback that the cursor shape has changed
2849 self.blink_manager.update(cx, BlinkManager::show_cursor);
2850
2851 cx.notify();
2852 }
2853
2854 pub fn set_current_line_highlight(
2855 &mut self,
2856 current_line_highlight: Option<CurrentLineHighlight>,
2857 ) {
2858 self.current_line_highlight = current_line_highlight;
2859 }
2860
2861 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2862 self.collapse_matches = collapse_matches;
2863 }
2864
2865 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2866 let buffers = self.buffer.read(cx).all_buffers();
2867 let Some(project) = self.project.as_ref() else {
2868 return;
2869 };
2870 project.update(cx, |project, cx| {
2871 for buffer in buffers {
2872 self.registered_buffers
2873 .entry(buffer.read(cx).remote_id())
2874 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2875 }
2876 })
2877 }
2878
2879 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2880 if self.collapse_matches {
2881 return range.start..range.start;
2882 }
2883 range.clone()
2884 }
2885
2886 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2887 if self.display_map.read(cx).clip_at_line_ends != clip {
2888 self.display_map
2889 .update(cx, |map, _| map.clip_at_line_ends = clip);
2890 }
2891 }
2892
2893 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2894 self.input_enabled = input_enabled;
2895 }
2896
2897 pub fn set_edit_predictions_hidden_for_vim_mode(
2898 &mut self,
2899 hidden: bool,
2900 window: &mut Window,
2901 cx: &mut Context<Self>,
2902 ) {
2903 if hidden != self.edit_predictions_hidden_for_vim_mode {
2904 self.edit_predictions_hidden_for_vim_mode = hidden;
2905 if hidden {
2906 self.update_visible_edit_prediction(window, cx);
2907 } else {
2908 self.refresh_edit_prediction(true, false, window, cx);
2909 }
2910 }
2911 }
2912
2913 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2914 self.menu_edit_predictions_policy = value;
2915 }
2916
2917 pub fn set_autoindent(&mut self, autoindent: bool) {
2918 if autoindent {
2919 self.autoindent_mode = Some(AutoindentMode::EachLine);
2920 } else {
2921 self.autoindent_mode = None;
2922 }
2923 }
2924
2925 pub fn read_only(&self, cx: &App) -> bool {
2926 self.read_only || self.buffer.read(cx).read_only()
2927 }
2928
2929 pub fn set_read_only(&mut self, read_only: bool) {
2930 self.read_only = read_only;
2931 }
2932
2933 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2934 self.use_autoclose = autoclose;
2935 }
2936
2937 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2938 self.use_auto_surround = auto_surround;
2939 }
2940
2941 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2942 self.auto_replace_emoji_shortcode = auto_replace;
2943 }
2944
2945 pub fn toggle_edit_predictions(
2946 &mut self,
2947 _: &ToggleEditPrediction,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) {
2951 if self.show_edit_predictions_override.is_some() {
2952 self.set_show_edit_predictions(None, window, cx);
2953 } else {
2954 let show_edit_predictions = !self.edit_predictions_enabled();
2955 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2956 }
2957 }
2958
2959 pub fn set_show_edit_predictions(
2960 &mut self,
2961 show_edit_predictions: Option<bool>,
2962 window: &mut Window,
2963 cx: &mut Context<Self>,
2964 ) {
2965 self.show_edit_predictions_override = show_edit_predictions;
2966 self.update_edit_prediction_settings(cx);
2967
2968 if let Some(false) = show_edit_predictions {
2969 self.discard_edit_prediction(false, cx);
2970 } else {
2971 self.refresh_edit_prediction(false, true, window, cx);
2972 }
2973 }
2974
2975 fn edit_predictions_disabled_in_scope(
2976 &self,
2977 buffer: &Entity<Buffer>,
2978 buffer_position: language::Anchor,
2979 cx: &App,
2980 ) -> bool {
2981 let snapshot = buffer.read(cx).snapshot();
2982 let settings = snapshot.settings_at(buffer_position, cx);
2983
2984 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2985 return false;
2986 };
2987
2988 scope.override_name().is_some_and(|scope_name| {
2989 settings
2990 .edit_predictions_disabled_in
2991 .iter()
2992 .any(|s| s == scope_name)
2993 })
2994 }
2995
2996 pub fn set_use_modal_editing(&mut self, to: bool) {
2997 self.use_modal_editing = to;
2998 }
2999
3000 pub fn use_modal_editing(&self) -> bool {
3001 self.use_modal_editing
3002 }
3003
3004 fn selections_did_change(
3005 &mut self,
3006 local: bool,
3007 old_cursor_position: &Anchor,
3008 effects: SelectionEffects,
3009 window: &mut Window,
3010 cx: &mut Context<Self>,
3011 ) {
3012 window.invalidate_character_coordinates();
3013
3014 // Copy selections to primary selection buffer
3015 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3016 if local {
3017 let selections = self.selections.all::<usize>(cx);
3018 let buffer_handle = self.buffer.read(cx).read(cx);
3019
3020 let mut text = String::new();
3021 for (index, selection) in selections.iter().enumerate() {
3022 let text_for_selection = buffer_handle
3023 .text_for_range(selection.start..selection.end)
3024 .collect::<String>();
3025
3026 text.push_str(&text_for_selection);
3027 if index != selections.len() - 1 {
3028 text.push('\n');
3029 }
3030 }
3031
3032 if !text.is_empty() {
3033 cx.write_to_primary(ClipboardItem::new_string(text));
3034 }
3035 }
3036
3037 let selection_anchors = self.selections.disjoint_anchors();
3038
3039 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3040 self.buffer.update(cx, |buffer, cx| {
3041 buffer.set_active_selections(
3042 &selection_anchors,
3043 self.selections.line_mode,
3044 self.cursor_shape,
3045 cx,
3046 )
3047 });
3048 }
3049 let display_map = self
3050 .display_map
3051 .update(cx, |display_map, cx| display_map.snapshot(cx));
3052 let buffer = &display_map.buffer_snapshot;
3053 if self.selections.count() == 1 {
3054 self.add_selections_state = None;
3055 }
3056 self.select_next_state = None;
3057 self.select_prev_state = None;
3058 self.select_syntax_node_history.try_clear();
3059 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3060 self.snippet_stack.invalidate(&selection_anchors, buffer);
3061 self.take_rename(false, window, cx);
3062
3063 let newest_selection = self.selections.newest_anchor();
3064 let new_cursor_position = newest_selection.head();
3065 let selection_start = newest_selection.start;
3066
3067 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3068 self.push_to_nav_history(
3069 *old_cursor_position,
3070 Some(new_cursor_position.to_point(buffer)),
3071 false,
3072 effects.nav_history == Some(true),
3073 cx,
3074 );
3075 }
3076
3077 if local {
3078 if let Some(buffer_id) = new_cursor_position.buffer_id
3079 && !self.registered_buffers.contains_key(&buffer_id)
3080 && let Some(project) = self.project.as_ref()
3081 {
3082 project.update(cx, |project, cx| {
3083 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3084 return;
3085 };
3086 self.registered_buffers.insert(
3087 buffer_id,
3088 project.register_buffer_with_language_servers(&buffer, cx),
3089 );
3090 })
3091 }
3092
3093 let mut context_menu = self.context_menu.borrow_mut();
3094 let completion_menu = match context_menu.as_ref() {
3095 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3096 Some(CodeContextMenu::CodeActions(_)) => {
3097 *context_menu = None;
3098 None
3099 }
3100 None => None,
3101 };
3102 let completion_position = completion_menu.map(|menu| menu.initial_position);
3103 drop(context_menu);
3104
3105 if effects.completions
3106 && let Some(completion_position) = completion_position
3107 {
3108 let start_offset = selection_start.to_offset(buffer);
3109 let position_matches = start_offset == completion_position.to_offset(buffer);
3110 let continue_showing = if position_matches {
3111 if self.snippet_stack.is_empty() {
3112 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3113 } else {
3114 // Snippet choices can be shown even when the cursor is in whitespace.
3115 // Dismissing the menu with actions like backspace is handled by
3116 // invalidation regions.
3117 true
3118 }
3119 } else {
3120 false
3121 };
3122
3123 if continue_showing {
3124 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3125 } else {
3126 self.hide_context_menu(window, cx);
3127 }
3128 }
3129
3130 hide_hover(self, cx);
3131
3132 if old_cursor_position.to_display_point(&display_map).row()
3133 != new_cursor_position.to_display_point(&display_map).row()
3134 {
3135 self.available_code_actions.take();
3136 }
3137 self.refresh_code_actions(window, cx);
3138 self.refresh_document_highlights(cx);
3139 self.refresh_selected_text_highlights(false, window, cx);
3140 refresh_matching_bracket_highlights(self, window, cx);
3141 self.update_visible_edit_prediction(window, cx);
3142 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3143 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3144 self.inline_blame_popover.take();
3145 if self.git_blame_inline_enabled {
3146 self.start_inline_blame_timer(window, cx);
3147 }
3148 }
3149
3150 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3151 cx.emit(EditorEvent::SelectionsChanged { local });
3152
3153 let selections = &self.selections.disjoint;
3154 if selections.len() == 1 {
3155 cx.emit(SearchEvent::ActiveMatchChanged)
3156 }
3157 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3158 let inmemory_selections = selections
3159 .iter()
3160 .map(|s| {
3161 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3162 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3163 })
3164 .collect();
3165 self.update_restoration_data(cx, |data| {
3166 data.selections = inmemory_selections;
3167 });
3168
3169 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3170 && let Some(workspace_id) =
3171 self.workspace.as_ref().and_then(|workspace| workspace.1)
3172 {
3173 let snapshot = self.buffer().read(cx).snapshot(cx);
3174 let selections = selections.clone();
3175 let background_executor = cx.background_executor().clone();
3176 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3177 self.serialize_selections = cx.background_spawn(async move {
3178 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3179 let db_selections = selections
3180 .iter()
3181 .map(|selection| {
3182 (
3183 selection.start.to_offset(&snapshot),
3184 selection.end.to_offset(&snapshot),
3185 )
3186 })
3187 .collect();
3188
3189 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3190 .await
3191 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3192 .log_err();
3193 });
3194 }
3195 }
3196
3197 cx.notify();
3198 }
3199
3200 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3201 use text::ToOffset as _;
3202 use text::ToPoint as _;
3203
3204 if self.mode.is_minimap()
3205 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3206 {
3207 return;
3208 }
3209
3210 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3211 return;
3212 };
3213
3214 let snapshot = singleton.read(cx).snapshot();
3215 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3216 let display_snapshot = display_map.snapshot(cx);
3217
3218 display_snapshot
3219 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3220 .map(|fold| {
3221 fold.range.start.text_anchor.to_point(&snapshot)
3222 ..fold.range.end.text_anchor.to_point(&snapshot)
3223 })
3224 .collect()
3225 });
3226 self.update_restoration_data(cx, |data| {
3227 data.folds = inmemory_folds;
3228 });
3229
3230 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3231 return;
3232 };
3233 let background_executor = cx.background_executor().clone();
3234 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3235 let db_folds = self.display_map.update(cx, |display_map, cx| {
3236 display_map
3237 .snapshot(cx)
3238 .folds_in_range(0..snapshot.len())
3239 .map(|fold| {
3240 (
3241 fold.range.start.text_anchor.to_offset(&snapshot),
3242 fold.range.end.text_anchor.to_offset(&snapshot),
3243 )
3244 })
3245 .collect()
3246 });
3247 self.serialize_folds = cx.background_spawn(async move {
3248 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3249 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3250 .await
3251 .with_context(|| {
3252 format!(
3253 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3254 )
3255 })
3256 .log_err();
3257 });
3258 }
3259
3260 pub fn sync_selections(
3261 &mut self,
3262 other: Entity<Editor>,
3263 cx: &mut Context<Self>,
3264 ) -> gpui::Subscription {
3265 let other_selections = other.read(cx).selections.disjoint.to_vec();
3266 self.selections.change_with(cx, |selections| {
3267 selections.select_anchors(other_selections);
3268 });
3269
3270 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3271 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3272 let other_selections = other.read(cx).selections.disjoint.to_vec();
3273 if other_selections.is_empty() {
3274 return;
3275 }
3276 this.selections.change_with(cx, |selections| {
3277 selections.select_anchors(other_selections);
3278 });
3279 }
3280 });
3281
3282 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3283 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3284 let these_selections = this.selections.disjoint.to_vec();
3285 if these_selections.is_empty() {
3286 return;
3287 }
3288 other.update(cx, |other_editor, cx| {
3289 other_editor.selections.change_with(cx, |selections| {
3290 selections.select_anchors(these_selections);
3291 })
3292 });
3293 }
3294 });
3295
3296 Subscription::join(other_subscription, this_subscription)
3297 }
3298
3299 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3300 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3301 /// effects of selection change occur at the end of the transaction.
3302 pub fn change_selections<R>(
3303 &mut self,
3304 effects: SelectionEffects,
3305 window: &mut Window,
3306 cx: &mut Context<Self>,
3307 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3308 ) -> R {
3309 if let Some(state) = &mut self.deferred_selection_effects_state {
3310 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3311 state.effects.completions = effects.completions;
3312 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3313 let (changed, result) = self.selections.change_with(cx, change);
3314 state.changed |= changed;
3315 return result;
3316 }
3317 let mut state = DeferredSelectionEffectsState {
3318 changed: false,
3319 effects,
3320 old_cursor_position: self.selections.newest_anchor().head(),
3321 history_entry: SelectionHistoryEntry {
3322 selections: self.selections.disjoint_anchors(),
3323 select_next_state: self.select_next_state.clone(),
3324 select_prev_state: self.select_prev_state.clone(),
3325 add_selections_state: self.add_selections_state.clone(),
3326 },
3327 };
3328 let (changed, result) = self.selections.change_with(cx, change);
3329 state.changed = state.changed || changed;
3330 if self.defer_selection_effects {
3331 self.deferred_selection_effects_state = Some(state);
3332 } else {
3333 self.apply_selection_effects(state, window, cx);
3334 }
3335 result
3336 }
3337
3338 /// Defers the effects of selection change, so that the effects of multiple calls to
3339 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3340 /// to selection history and the state of popovers based on selection position aren't
3341 /// erroneously updated.
3342 pub fn with_selection_effects_deferred<R>(
3343 &mut self,
3344 window: &mut Window,
3345 cx: &mut Context<Self>,
3346 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3347 ) -> R {
3348 let already_deferred = self.defer_selection_effects;
3349 self.defer_selection_effects = true;
3350 let result = update(self, window, cx);
3351 if !already_deferred {
3352 self.defer_selection_effects = false;
3353 if let Some(state) = self.deferred_selection_effects_state.take() {
3354 self.apply_selection_effects(state, window, cx);
3355 }
3356 }
3357 result
3358 }
3359
3360 fn apply_selection_effects(
3361 &mut self,
3362 state: DeferredSelectionEffectsState,
3363 window: &mut Window,
3364 cx: &mut Context<Self>,
3365 ) {
3366 if state.changed {
3367 self.selection_history.push(state.history_entry);
3368
3369 if let Some(autoscroll) = state.effects.scroll {
3370 self.request_autoscroll(autoscroll, cx);
3371 }
3372
3373 let old_cursor_position = &state.old_cursor_position;
3374
3375 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3376
3377 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3378 self.show_signature_help(&ShowSignatureHelp, window, cx);
3379 }
3380 }
3381 }
3382
3383 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3384 where
3385 I: IntoIterator<Item = (Range<S>, T)>,
3386 S: ToOffset,
3387 T: Into<Arc<str>>,
3388 {
3389 if self.read_only(cx) {
3390 return;
3391 }
3392
3393 self.buffer
3394 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3395 }
3396
3397 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3398 where
3399 I: IntoIterator<Item = (Range<S>, T)>,
3400 S: ToOffset,
3401 T: Into<Arc<str>>,
3402 {
3403 if self.read_only(cx) {
3404 return;
3405 }
3406
3407 self.buffer.update(cx, |buffer, cx| {
3408 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3409 });
3410 }
3411
3412 pub fn edit_with_block_indent<I, S, T>(
3413 &mut self,
3414 edits: I,
3415 original_indent_columns: Vec<Option<u32>>,
3416 cx: &mut Context<Self>,
3417 ) where
3418 I: IntoIterator<Item = (Range<S>, T)>,
3419 S: ToOffset,
3420 T: Into<Arc<str>>,
3421 {
3422 if self.read_only(cx) {
3423 return;
3424 }
3425
3426 self.buffer.update(cx, |buffer, cx| {
3427 buffer.edit(
3428 edits,
3429 Some(AutoindentMode::Block {
3430 original_indent_columns,
3431 }),
3432 cx,
3433 )
3434 });
3435 }
3436
3437 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3438 self.hide_context_menu(window, cx);
3439
3440 match phase {
3441 SelectPhase::Begin {
3442 position,
3443 add,
3444 click_count,
3445 } => self.begin_selection(position, add, click_count, window, cx),
3446 SelectPhase::BeginColumnar {
3447 position,
3448 goal_column,
3449 reset,
3450 mode,
3451 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3452 SelectPhase::Extend {
3453 position,
3454 click_count,
3455 } => self.extend_selection(position, click_count, window, cx),
3456 SelectPhase::Update {
3457 position,
3458 goal_column,
3459 scroll_delta,
3460 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3461 SelectPhase::End => self.end_selection(window, cx),
3462 }
3463 }
3464
3465 fn extend_selection(
3466 &mut self,
3467 position: DisplayPoint,
3468 click_count: usize,
3469 window: &mut Window,
3470 cx: &mut Context<Self>,
3471 ) {
3472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3473 let tail = self.selections.newest::<usize>(cx).tail();
3474 self.begin_selection(position, false, click_count, window, cx);
3475
3476 let position = position.to_offset(&display_map, Bias::Left);
3477 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3478
3479 let mut pending_selection = self
3480 .selections
3481 .pending_anchor()
3482 .expect("extend_selection not called with pending selection");
3483 if position >= tail {
3484 pending_selection.start = tail_anchor;
3485 } else {
3486 pending_selection.end = tail_anchor;
3487 pending_selection.reversed = true;
3488 }
3489
3490 let mut pending_mode = self.selections.pending_mode().unwrap();
3491 match &mut pending_mode {
3492 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3493 _ => {}
3494 }
3495
3496 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3497 SelectionEffects::scroll(Autoscroll::fit())
3498 } else {
3499 SelectionEffects::no_scroll()
3500 };
3501
3502 self.change_selections(effects, window, cx, |s| {
3503 s.set_pending(pending_selection, pending_mode)
3504 });
3505 }
3506
3507 fn begin_selection(
3508 &mut self,
3509 position: DisplayPoint,
3510 add: bool,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 if !self.focus_handle.is_focused(window) {
3516 self.last_focused_descendant = None;
3517 window.focus(&self.focus_handle);
3518 }
3519
3520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3521 let buffer = &display_map.buffer_snapshot;
3522 let position = display_map.clip_point(position, Bias::Left);
3523
3524 let start;
3525 let end;
3526 let mode;
3527 let mut auto_scroll;
3528 match click_count {
3529 1 => {
3530 start = buffer.anchor_before(position.to_point(&display_map));
3531 end = start;
3532 mode = SelectMode::Character;
3533 auto_scroll = true;
3534 }
3535 2 => {
3536 let position = display_map
3537 .clip_point(position, Bias::Left)
3538 .to_offset(&display_map, Bias::Left);
3539 let (range, _) = buffer.surrounding_word(position, false);
3540 start = buffer.anchor_before(range.start);
3541 end = buffer.anchor_before(range.end);
3542 mode = SelectMode::Word(start..end);
3543 auto_scroll = true;
3544 }
3545 3 => {
3546 let position = display_map
3547 .clip_point(position, Bias::Left)
3548 .to_point(&display_map);
3549 let line_start = display_map.prev_line_boundary(position).0;
3550 let next_line_start = buffer.clip_point(
3551 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3552 Bias::Left,
3553 );
3554 start = buffer.anchor_before(line_start);
3555 end = buffer.anchor_before(next_line_start);
3556 mode = SelectMode::Line(start..end);
3557 auto_scroll = true;
3558 }
3559 _ => {
3560 start = buffer.anchor_before(0);
3561 end = buffer.anchor_before(buffer.len());
3562 mode = SelectMode::All;
3563 auto_scroll = false;
3564 }
3565 }
3566 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3567
3568 let point_to_delete: Option<usize> = {
3569 let selected_points: Vec<Selection<Point>> =
3570 self.selections.disjoint_in_range(start..end, cx);
3571
3572 if !add || click_count > 1 {
3573 None
3574 } else if !selected_points.is_empty() {
3575 Some(selected_points[0].id)
3576 } else {
3577 let clicked_point_already_selected =
3578 self.selections.disjoint.iter().find(|selection| {
3579 selection.start.to_point(buffer) == start.to_point(buffer)
3580 || selection.end.to_point(buffer) == end.to_point(buffer)
3581 });
3582
3583 clicked_point_already_selected.map(|selection| selection.id)
3584 }
3585 };
3586
3587 let selections_count = self.selections.count();
3588 let effects = if auto_scroll {
3589 SelectionEffects::default()
3590 } else {
3591 SelectionEffects::no_scroll()
3592 };
3593
3594 self.change_selections(effects, window, cx, |s| {
3595 if let Some(point_to_delete) = point_to_delete {
3596 s.delete(point_to_delete);
3597
3598 if selections_count == 1 {
3599 s.set_pending_anchor_range(start..end, mode);
3600 }
3601 } else {
3602 if !add {
3603 s.clear_disjoint();
3604 }
3605
3606 s.set_pending_anchor_range(start..end, mode);
3607 }
3608 });
3609 }
3610
3611 fn begin_columnar_selection(
3612 &mut self,
3613 position: DisplayPoint,
3614 goal_column: u32,
3615 reset: bool,
3616 mode: ColumnarMode,
3617 window: &mut Window,
3618 cx: &mut Context<Self>,
3619 ) {
3620 if !self.focus_handle.is_focused(window) {
3621 self.last_focused_descendant = None;
3622 window.focus(&self.focus_handle);
3623 }
3624
3625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3626
3627 if reset {
3628 let pointer_position = display_map
3629 .buffer_snapshot
3630 .anchor_before(position.to_point(&display_map));
3631
3632 self.change_selections(
3633 SelectionEffects::scroll(Autoscroll::newest()),
3634 window,
3635 cx,
3636 |s| {
3637 s.clear_disjoint();
3638 s.set_pending_anchor_range(
3639 pointer_position..pointer_position,
3640 SelectMode::Character,
3641 );
3642 },
3643 );
3644 };
3645
3646 let tail = self.selections.newest::<Point>(cx).tail();
3647 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3648 self.columnar_selection_state = match mode {
3649 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3650 selection_tail: selection_anchor,
3651 display_point: if reset {
3652 if position.column() != goal_column {
3653 Some(DisplayPoint::new(position.row(), goal_column))
3654 } else {
3655 None
3656 }
3657 } else {
3658 None
3659 },
3660 }),
3661 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3662 selection_tail: selection_anchor,
3663 }),
3664 };
3665
3666 if !reset {
3667 self.select_columns(position, goal_column, &display_map, window, cx);
3668 }
3669 }
3670
3671 fn update_selection(
3672 &mut self,
3673 position: DisplayPoint,
3674 goal_column: u32,
3675 scroll_delta: gpui::Point<f32>,
3676 window: &mut Window,
3677 cx: &mut Context<Self>,
3678 ) {
3679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3680
3681 if self.columnar_selection_state.is_some() {
3682 self.select_columns(position, goal_column, &display_map, window, cx);
3683 } else if let Some(mut pending) = self.selections.pending_anchor() {
3684 let buffer = &display_map.buffer_snapshot;
3685 let head;
3686 let tail;
3687 let mode = self.selections.pending_mode().unwrap();
3688 match &mode {
3689 SelectMode::Character => {
3690 head = position.to_point(&display_map);
3691 tail = pending.tail().to_point(buffer);
3692 }
3693 SelectMode::Word(original_range) => {
3694 let offset = display_map
3695 .clip_point(position, Bias::Left)
3696 .to_offset(&display_map, Bias::Left);
3697 let original_range = original_range.to_offset(buffer);
3698
3699 let head_offset = if buffer.is_inside_word(offset, false)
3700 || original_range.contains(&offset)
3701 {
3702 let (word_range, _) = buffer.surrounding_word(offset, false);
3703 if word_range.start < original_range.start {
3704 word_range.start
3705 } else {
3706 word_range.end
3707 }
3708 } else {
3709 offset
3710 };
3711
3712 head = head_offset.to_point(buffer);
3713 if head_offset <= original_range.start {
3714 tail = original_range.end.to_point(buffer);
3715 } else {
3716 tail = original_range.start.to_point(buffer);
3717 }
3718 }
3719 SelectMode::Line(original_range) => {
3720 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3721
3722 let position = display_map
3723 .clip_point(position, Bias::Left)
3724 .to_point(&display_map);
3725 let line_start = display_map.prev_line_boundary(position).0;
3726 let next_line_start = buffer.clip_point(
3727 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3728 Bias::Left,
3729 );
3730
3731 if line_start < original_range.start {
3732 head = line_start
3733 } else {
3734 head = next_line_start
3735 }
3736
3737 if head <= original_range.start {
3738 tail = original_range.end;
3739 } else {
3740 tail = original_range.start;
3741 }
3742 }
3743 SelectMode::All => {
3744 return;
3745 }
3746 };
3747
3748 if head < tail {
3749 pending.start = buffer.anchor_before(head);
3750 pending.end = buffer.anchor_before(tail);
3751 pending.reversed = true;
3752 } else {
3753 pending.start = buffer.anchor_before(tail);
3754 pending.end = buffer.anchor_before(head);
3755 pending.reversed = false;
3756 }
3757
3758 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3759 s.set_pending(pending, mode);
3760 });
3761 } else {
3762 log::error!("update_selection dispatched with no pending selection");
3763 return;
3764 }
3765
3766 self.apply_scroll_delta(scroll_delta, window, cx);
3767 cx.notify();
3768 }
3769
3770 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3771 self.columnar_selection_state.take();
3772 if self.selections.pending_anchor().is_some() {
3773 let selections = self.selections.all::<usize>(cx);
3774 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3775 s.select(selections);
3776 s.clear_pending();
3777 });
3778 }
3779 }
3780
3781 fn select_columns(
3782 &mut self,
3783 head: DisplayPoint,
3784 goal_column: u32,
3785 display_map: &DisplaySnapshot,
3786 window: &mut Window,
3787 cx: &mut Context<Self>,
3788 ) {
3789 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3790 return;
3791 };
3792
3793 let tail = match columnar_state {
3794 ColumnarSelectionState::FromMouse {
3795 selection_tail,
3796 display_point,
3797 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3798 ColumnarSelectionState::FromSelection { selection_tail } => {
3799 selection_tail.to_display_point(display_map)
3800 }
3801 };
3802
3803 let start_row = cmp::min(tail.row(), head.row());
3804 let end_row = cmp::max(tail.row(), head.row());
3805 let start_column = cmp::min(tail.column(), goal_column);
3806 let end_column = cmp::max(tail.column(), goal_column);
3807 let reversed = start_column < tail.column();
3808
3809 let selection_ranges = (start_row.0..=end_row.0)
3810 .map(DisplayRow)
3811 .filter_map(|row| {
3812 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3813 || start_column <= display_map.line_len(row))
3814 && !display_map.is_block_line(row)
3815 {
3816 let start = display_map
3817 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3818 .to_point(display_map);
3819 let end = display_map
3820 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3821 .to_point(display_map);
3822 if reversed {
3823 Some(end..start)
3824 } else {
3825 Some(start..end)
3826 }
3827 } else {
3828 None
3829 }
3830 })
3831 .collect::<Vec<_>>();
3832
3833 let ranges = match columnar_state {
3834 ColumnarSelectionState::FromMouse { .. } => {
3835 let mut non_empty_ranges = selection_ranges
3836 .iter()
3837 .filter(|selection_range| selection_range.start != selection_range.end)
3838 .peekable();
3839 if non_empty_ranges.peek().is_some() {
3840 non_empty_ranges.cloned().collect()
3841 } else {
3842 selection_ranges
3843 }
3844 }
3845 _ => selection_ranges,
3846 };
3847
3848 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3849 s.select_ranges(ranges);
3850 });
3851 cx.notify();
3852 }
3853
3854 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3855 self.selections
3856 .all_adjusted(cx)
3857 .iter()
3858 .any(|selection| !selection.is_empty())
3859 }
3860
3861 pub fn has_pending_nonempty_selection(&self) -> bool {
3862 let pending_nonempty_selection = match self.selections.pending_anchor() {
3863 Some(Selection { start, end, .. }) => start != end,
3864 None => false,
3865 };
3866
3867 pending_nonempty_selection
3868 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3869 }
3870
3871 pub fn has_pending_selection(&self) -> bool {
3872 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3873 }
3874
3875 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3876 self.selection_mark_mode = false;
3877 self.selection_drag_state = SelectionDragState::None;
3878
3879 if self.clear_expanded_diff_hunks(cx) {
3880 cx.notify();
3881 return;
3882 }
3883 if self.dismiss_menus_and_popups(true, window, cx) {
3884 return;
3885 }
3886
3887 if self.mode.is_full()
3888 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3889 {
3890 return;
3891 }
3892
3893 cx.propagate();
3894 }
3895
3896 pub fn dismiss_menus_and_popups(
3897 &mut self,
3898 is_user_requested: bool,
3899 window: &mut Window,
3900 cx: &mut Context<Self>,
3901 ) -> bool {
3902 if self.take_rename(false, window, cx).is_some() {
3903 return true;
3904 }
3905
3906 if hide_hover(self, cx) {
3907 return true;
3908 }
3909
3910 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3911 return true;
3912 }
3913
3914 if self.hide_context_menu(window, cx).is_some() {
3915 return true;
3916 }
3917
3918 if self.mouse_context_menu.take().is_some() {
3919 return true;
3920 }
3921
3922 if is_user_requested && self.discard_edit_prediction(true, cx) {
3923 return true;
3924 }
3925
3926 if self.snippet_stack.pop().is_some() {
3927 return true;
3928 }
3929
3930 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3931 self.dismiss_diagnostics(cx);
3932 return true;
3933 }
3934
3935 false
3936 }
3937
3938 fn linked_editing_ranges_for(
3939 &self,
3940 selection: Range<text::Anchor>,
3941 cx: &App,
3942 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3943 if self.linked_edit_ranges.is_empty() {
3944 return None;
3945 }
3946 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3947 selection.end.buffer_id.and_then(|end_buffer_id| {
3948 if selection.start.buffer_id != Some(end_buffer_id) {
3949 return None;
3950 }
3951 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3952 let snapshot = buffer.read(cx).snapshot();
3953 self.linked_edit_ranges
3954 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3955 .map(|ranges| (ranges, snapshot, buffer))
3956 })?;
3957 use text::ToOffset as TO;
3958 // find offset from the start of current range to current cursor position
3959 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3960
3961 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3962 let start_difference = start_offset - start_byte_offset;
3963 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3964 let end_difference = end_offset - start_byte_offset;
3965 // Current range has associated linked ranges.
3966 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3967 for range in linked_ranges.iter() {
3968 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3969 let end_offset = start_offset + end_difference;
3970 let start_offset = start_offset + start_difference;
3971 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3972 continue;
3973 }
3974 if self.selections.disjoint_anchor_ranges().any(|s| {
3975 if s.start.buffer_id != selection.start.buffer_id
3976 || s.end.buffer_id != selection.end.buffer_id
3977 {
3978 return false;
3979 }
3980 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3981 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3982 }) {
3983 continue;
3984 }
3985 let start = buffer_snapshot.anchor_after(start_offset);
3986 let end = buffer_snapshot.anchor_after(end_offset);
3987 linked_edits
3988 .entry(buffer.clone())
3989 .or_default()
3990 .push(start..end);
3991 }
3992 Some(linked_edits)
3993 }
3994
3995 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3996 let text: Arc<str> = text.into();
3997
3998 if self.read_only(cx) {
3999 return;
4000 }
4001
4002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4003
4004 let selections = self.selections.all_adjusted(cx);
4005 let mut bracket_inserted = false;
4006 let mut edits = Vec::new();
4007 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4008 let mut new_selections = Vec::with_capacity(selections.len());
4009 let mut new_autoclose_regions = Vec::new();
4010 let snapshot = self.buffer.read(cx).read(cx);
4011 let mut clear_linked_edit_ranges = false;
4012
4013 for (selection, autoclose_region) in
4014 self.selections_with_autoclose_regions(selections, &snapshot)
4015 {
4016 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4017 // Determine if the inserted text matches the opening or closing
4018 // bracket of any of this language's bracket pairs.
4019 let mut bracket_pair = None;
4020 let mut is_bracket_pair_start = false;
4021 let mut is_bracket_pair_end = false;
4022 if !text.is_empty() {
4023 let mut bracket_pair_matching_end = None;
4024 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4025 // and they are removing the character that triggered IME popup.
4026 for (pair, enabled) in scope.brackets() {
4027 if !pair.close && !pair.surround {
4028 continue;
4029 }
4030
4031 if enabled && pair.start.ends_with(text.as_ref()) {
4032 let prefix_len = pair.start.len() - text.len();
4033 let preceding_text_matches_prefix = prefix_len == 0
4034 || (selection.start.column >= (prefix_len as u32)
4035 && snapshot.contains_str_at(
4036 Point::new(
4037 selection.start.row,
4038 selection.start.column - (prefix_len as u32),
4039 ),
4040 &pair.start[..prefix_len],
4041 ));
4042 if preceding_text_matches_prefix {
4043 bracket_pair = Some(pair.clone());
4044 is_bracket_pair_start = true;
4045 break;
4046 }
4047 }
4048 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4049 {
4050 // take first bracket pair matching end, but don't break in case a later bracket
4051 // pair matches start
4052 bracket_pair_matching_end = Some(pair.clone());
4053 }
4054 }
4055 if let Some(end) = bracket_pair_matching_end
4056 && bracket_pair.is_none()
4057 {
4058 bracket_pair = Some(end);
4059 is_bracket_pair_end = true;
4060 }
4061 }
4062
4063 if let Some(bracket_pair) = bracket_pair {
4064 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4065 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4066 let auto_surround =
4067 self.use_auto_surround && snapshot_settings.use_auto_surround;
4068 if selection.is_empty() {
4069 if is_bracket_pair_start {
4070 // If the inserted text is a suffix of an opening bracket and the
4071 // selection is preceded by the rest of the opening bracket, then
4072 // insert the closing bracket.
4073 let following_text_allows_autoclose = snapshot
4074 .chars_at(selection.start)
4075 .next()
4076 .is_none_or(|c| scope.should_autoclose_before(c));
4077
4078 let preceding_text_allows_autoclose = selection.start.column == 0
4079 || snapshot
4080 .reversed_chars_at(selection.start)
4081 .next()
4082 .is_none_or(|c| {
4083 bracket_pair.start != bracket_pair.end
4084 || !snapshot
4085 .char_classifier_at(selection.start)
4086 .is_word(c)
4087 });
4088
4089 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4090 && bracket_pair.start.len() == 1
4091 {
4092 let target = bracket_pair.start.chars().next().unwrap();
4093 let current_line_count = snapshot
4094 .reversed_chars_at(selection.start)
4095 .take_while(|&c| c != '\n')
4096 .filter(|&c| c == target)
4097 .count();
4098 current_line_count % 2 == 1
4099 } else {
4100 false
4101 };
4102
4103 if autoclose
4104 && bracket_pair.close
4105 && following_text_allows_autoclose
4106 && preceding_text_allows_autoclose
4107 && !is_closing_quote
4108 {
4109 let anchor = snapshot.anchor_before(selection.end);
4110 new_selections.push((selection.map(|_| anchor), text.len()));
4111 new_autoclose_regions.push((
4112 anchor,
4113 text.len(),
4114 selection.id,
4115 bracket_pair.clone(),
4116 ));
4117 edits.push((
4118 selection.range(),
4119 format!("{}{}", text, bracket_pair.end).into(),
4120 ));
4121 bracket_inserted = true;
4122 continue;
4123 }
4124 }
4125
4126 if let Some(region) = autoclose_region {
4127 // If the selection is followed by an auto-inserted closing bracket,
4128 // then don't insert that closing bracket again; just move the selection
4129 // past the closing bracket.
4130 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4131 && text.as_ref() == region.pair.end.as_str()
4132 && snapshot.contains_str_at(region.range.end, text.as_ref());
4133 if should_skip {
4134 let anchor = snapshot.anchor_after(selection.end);
4135 new_selections
4136 .push((selection.map(|_| anchor), region.pair.end.len()));
4137 continue;
4138 }
4139 }
4140
4141 let always_treat_brackets_as_autoclosed = snapshot
4142 .language_settings_at(selection.start, cx)
4143 .always_treat_brackets_as_autoclosed;
4144 if always_treat_brackets_as_autoclosed
4145 && is_bracket_pair_end
4146 && snapshot.contains_str_at(selection.end, text.as_ref())
4147 {
4148 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4149 // and the inserted text is a closing bracket and the selection is followed
4150 // by the closing bracket then move the selection past the closing bracket.
4151 let anchor = snapshot.anchor_after(selection.end);
4152 new_selections.push((selection.map(|_| anchor), text.len()));
4153 continue;
4154 }
4155 }
4156 // If an opening bracket is 1 character long and is typed while
4157 // text is selected, then surround that text with the bracket pair.
4158 else if auto_surround
4159 && bracket_pair.surround
4160 && is_bracket_pair_start
4161 && bracket_pair.start.chars().count() == 1
4162 {
4163 edits.push((selection.start..selection.start, text.clone()));
4164 edits.push((
4165 selection.end..selection.end,
4166 bracket_pair.end.as_str().into(),
4167 ));
4168 bracket_inserted = true;
4169 new_selections.push((
4170 Selection {
4171 id: selection.id,
4172 start: snapshot.anchor_after(selection.start),
4173 end: snapshot.anchor_before(selection.end),
4174 reversed: selection.reversed,
4175 goal: selection.goal,
4176 },
4177 0,
4178 ));
4179 continue;
4180 }
4181 }
4182 }
4183
4184 if self.auto_replace_emoji_shortcode
4185 && selection.is_empty()
4186 && text.as_ref().ends_with(':')
4187 && let Some(possible_emoji_short_code) =
4188 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4189 && !possible_emoji_short_code.is_empty()
4190 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4191 {
4192 let emoji_shortcode_start = Point::new(
4193 selection.start.row,
4194 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4195 );
4196
4197 // Remove shortcode from buffer
4198 edits.push((
4199 emoji_shortcode_start..selection.start,
4200 "".to_string().into(),
4201 ));
4202 new_selections.push((
4203 Selection {
4204 id: selection.id,
4205 start: snapshot.anchor_after(emoji_shortcode_start),
4206 end: snapshot.anchor_before(selection.start),
4207 reversed: selection.reversed,
4208 goal: selection.goal,
4209 },
4210 0,
4211 ));
4212
4213 // Insert emoji
4214 let selection_start_anchor = snapshot.anchor_after(selection.start);
4215 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4216 edits.push((selection.start..selection.end, emoji.to_string().into()));
4217
4218 continue;
4219 }
4220
4221 // If not handling any auto-close operation, then just replace the selected
4222 // text with the given input and move the selection to the end of the
4223 // newly inserted text.
4224 let anchor = snapshot.anchor_after(selection.end);
4225 if !self.linked_edit_ranges.is_empty() {
4226 let start_anchor = snapshot.anchor_before(selection.start);
4227
4228 let is_word_char = text.chars().next().is_none_or(|char| {
4229 let classifier = snapshot
4230 .char_classifier_at(start_anchor.to_offset(&snapshot))
4231 .ignore_punctuation(true);
4232 classifier.is_word(char)
4233 });
4234
4235 if is_word_char {
4236 if let Some(ranges) = self
4237 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4238 {
4239 for (buffer, edits) in ranges {
4240 linked_edits
4241 .entry(buffer.clone())
4242 .or_default()
4243 .extend(edits.into_iter().map(|range| (range, text.clone())));
4244 }
4245 }
4246 } else {
4247 clear_linked_edit_ranges = true;
4248 }
4249 }
4250
4251 new_selections.push((selection.map(|_| anchor), 0));
4252 edits.push((selection.start..selection.end, text.clone()));
4253 }
4254
4255 drop(snapshot);
4256
4257 self.transact(window, cx, |this, window, cx| {
4258 if clear_linked_edit_ranges {
4259 this.linked_edit_ranges.clear();
4260 }
4261 let initial_buffer_versions =
4262 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4263
4264 this.buffer.update(cx, |buffer, cx| {
4265 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4266 });
4267 for (buffer, edits) in linked_edits {
4268 buffer.update(cx, |buffer, cx| {
4269 let snapshot = buffer.snapshot();
4270 let edits = edits
4271 .into_iter()
4272 .map(|(range, text)| {
4273 use text::ToPoint as TP;
4274 let end_point = TP::to_point(&range.end, &snapshot);
4275 let start_point = TP::to_point(&range.start, &snapshot);
4276 (start_point..end_point, text)
4277 })
4278 .sorted_by_key(|(range, _)| range.start);
4279 buffer.edit(edits, None, cx);
4280 })
4281 }
4282 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4283 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4284 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4285 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4286 .zip(new_selection_deltas)
4287 .map(|(selection, delta)| Selection {
4288 id: selection.id,
4289 start: selection.start + delta,
4290 end: selection.end + delta,
4291 reversed: selection.reversed,
4292 goal: SelectionGoal::None,
4293 })
4294 .collect::<Vec<_>>();
4295
4296 let mut i = 0;
4297 for (position, delta, selection_id, pair) in new_autoclose_regions {
4298 let position = position.to_offset(&map.buffer_snapshot) + delta;
4299 let start = map.buffer_snapshot.anchor_before(position);
4300 let end = map.buffer_snapshot.anchor_after(position);
4301 while let Some(existing_state) = this.autoclose_regions.get(i) {
4302 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4303 Ordering::Less => i += 1,
4304 Ordering::Greater => break,
4305 Ordering::Equal => {
4306 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4307 Ordering::Less => i += 1,
4308 Ordering::Equal => break,
4309 Ordering::Greater => break,
4310 }
4311 }
4312 }
4313 }
4314 this.autoclose_regions.insert(
4315 i,
4316 AutocloseRegion {
4317 selection_id,
4318 range: start..end,
4319 pair,
4320 },
4321 );
4322 }
4323
4324 let had_active_edit_prediction = this.has_active_edit_prediction();
4325 this.change_selections(
4326 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4327 window,
4328 cx,
4329 |s| s.select(new_selections),
4330 );
4331
4332 if !bracket_inserted
4333 && let Some(on_type_format_task) =
4334 this.trigger_on_type_formatting(text.to_string(), window, cx)
4335 {
4336 on_type_format_task.detach_and_log_err(cx);
4337 }
4338
4339 let editor_settings = EditorSettings::get_global(cx);
4340 if bracket_inserted
4341 && (editor_settings.auto_signature_help
4342 || editor_settings.show_signature_help_after_edits)
4343 {
4344 this.show_signature_help(&ShowSignatureHelp, window, cx);
4345 }
4346
4347 let trigger_in_words =
4348 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4349 if this.hard_wrap.is_some() {
4350 let latest: Range<Point> = this.selections.newest(cx).range();
4351 if latest.is_empty()
4352 && this
4353 .buffer()
4354 .read(cx)
4355 .snapshot(cx)
4356 .line_len(MultiBufferRow(latest.start.row))
4357 == latest.start.column
4358 {
4359 this.rewrap_impl(
4360 RewrapOptions {
4361 override_language_settings: true,
4362 preserve_existing_whitespace: true,
4363 },
4364 cx,
4365 )
4366 }
4367 }
4368 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4369 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4370 this.refresh_edit_prediction(true, false, window, cx);
4371 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4372 });
4373 }
4374
4375 fn find_possible_emoji_shortcode_at_position(
4376 snapshot: &MultiBufferSnapshot,
4377 position: Point,
4378 ) -> Option<String> {
4379 let mut chars = Vec::new();
4380 let mut found_colon = false;
4381 for char in snapshot.reversed_chars_at(position).take(100) {
4382 // Found a possible emoji shortcode in the middle of the buffer
4383 if found_colon {
4384 if char.is_whitespace() {
4385 chars.reverse();
4386 return Some(chars.iter().collect());
4387 }
4388 // If the previous character is not a whitespace, we are in the middle of a word
4389 // and we only want to complete the shortcode if the word is made up of other emojis
4390 let mut containing_word = String::new();
4391 for ch in snapshot
4392 .reversed_chars_at(position)
4393 .skip(chars.len() + 1)
4394 .take(100)
4395 {
4396 if ch.is_whitespace() {
4397 break;
4398 }
4399 containing_word.push(ch);
4400 }
4401 let containing_word = containing_word.chars().rev().collect::<String>();
4402 if util::word_consists_of_emojis(containing_word.as_str()) {
4403 chars.reverse();
4404 return Some(chars.iter().collect());
4405 }
4406 }
4407
4408 if char.is_whitespace() || !char.is_ascii() {
4409 return None;
4410 }
4411 if char == ':' {
4412 found_colon = true;
4413 } else {
4414 chars.push(char);
4415 }
4416 }
4417 // Found a possible emoji shortcode at the beginning of the buffer
4418 chars.reverse();
4419 Some(chars.iter().collect())
4420 }
4421
4422 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4423 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4424 self.transact(window, cx, |this, window, cx| {
4425 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4426 let selections = this.selections.all::<usize>(cx);
4427 let multi_buffer = this.buffer.read(cx);
4428 let buffer = multi_buffer.snapshot(cx);
4429 selections
4430 .iter()
4431 .map(|selection| {
4432 let start_point = selection.start.to_point(&buffer);
4433 let mut existing_indent =
4434 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4435 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4436 let start = selection.start;
4437 let end = selection.end;
4438 let selection_is_empty = start == end;
4439 let language_scope = buffer.language_scope_at(start);
4440 let (
4441 comment_delimiter,
4442 doc_delimiter,
4443 insert_extra_newline,
4444 indent_on_newline,
4445 indent_on_extra_newline,
4446 ) = if let Some(language) = &language_scope {
4447 let mut insert_extra_newline =
4448 insert_extra_newline_brackets(&buffer, start..end, language)
4449 || insert_extra_newline_tree_sitter(&buffer, start..end);
4450
4451 // Comment extension on newline is allowed only for cursor selections
4452 let comment_delimiter = maybe!({
4453 if !selection_is_empty {
4454 return None;
4455 }
4456
4457 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4458 return None;
4459 }
4460
4461 let delimiters = language.line_comment_prefixes();
4462 let max_len_of_delimiter =
4463 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4464 let (snapshot, range) =
4465 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4466
4467 let num_of_whitespaces = snapshot
4468 .chars_for_range(range.clone())
4469 .take_while(|c| c.is_whitespace())
4470 .count();
4471 let comment_candidate = snapshot
4472 .chars_for_range(range.clone())
4473 .skip(num_of_whitespaces)
4474 .take(max_len_of_delimiter)
4475 .collect::<String>();
4476 let (delimiter, trimmed_len) = delimiters
4477 .iter()
4478 .filter_map(|delimiter| {
4479 let prefix = delimiter.trim_end();
4480 if comment_candidate.starts_with(prefix) {
4481 Some((delimiter, prefix.len()))
4482 } else {
4483 None
4484 }
4485 })
4486 .max_by_key(|(_, len)| *len)?;
4487
4488 if let Some(BlockCommentConfig {
4489 start: block_start, ..
4490 }) = language.block_comment()
4491 {
4492 let block_start_trimmed = block_start.trim_end();
4493 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4494 let line_content = snapshot
4495 .chars_for_range(range)
4496 .skip(num_of_whitespaces)
4497 .take(block_start_trimmed.len())
4498 .collect::<String>();
4499
4500 if line_content.starts_with(block_start_trimmed) {
4501 return None;
4502 }
4503 }
4504 }
4505
4506 let cursor_is_placed_after_comment_marker =
4507 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4508 if cursor_is_placed_after_comment_marker {
4509 Some(delimiter.clone())
4510 } else {
4511 None
4512 }
4513 });
4514
4515 let mut indent_on_newline = IndentSize::spaces(0);
4516 let mut indent_on_extra_newline = IndentSize::spaces(0);
4517
4518 let doc_delimiter = maybe!({
4519 if !selection_is_empty {
4520 return None;
4521 }
4522
4523 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4524 return None;
4525 }
4526
4527 let BlockCommentConfig {
4528 start: start_tag,
4529 end: end_tag,
4530 prefix: delimiter,
4531 tab_size: len,
4532 } = language.documentation_comment()?;
4533 let is_within_block_comment = buffer
4534 .language_scope_at(start_point)
4535 .is_some_and(|scope| scope.override_name() == Some("comment"));
4536 if !is_within_block_comment {
4537 return None;
4538 }
4539
4540 let (snapshot, range) =
4541 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4542
4543 let num_of_whitespaces = snapshot
4544 .chars_for_range(range.clone())
4545 .take_while(|c| c.is_whitespace())
4546 .count();
4547
4548 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4549 let column = start_point.column;
4550 let cursor_is_after_start_tag = {
4551 let start_tag_len = start_tag.len();
4552 let start_tag_line = snapshot
4553 .chars_for_range(range.clone())
4554 .skip(num_of_whitespaces)
4555 .take(start_tag_len)
4556 .collect::<String>();
4557 if start_tag_line.starts_with(start_tag.as_ref()) {
4558 num_of_whitespaces + start_tag_len <= column as usize
4559 } else {
4560 false
4561 }
4562 };
4563
4564 let cursor_is_after_delimiter = {
4565 let delimiter_trim = delimiter.trim_end();
4566 let delimiter_line = snapshot
4567 .chars_for_range(range.clone())
4568 .skip(num_of_whitespaces)
4569 .take(delimiter_trim.len())
4570 .collect::<String>();
4571 if delimiter_line.starts_with(delimiter_trim) {
4572 num_of_whitespaces + delimiter_trim.len() <= column as usize
4573 } else {
4574 false
4575 }
4576 };
4577
4578 let cursor_is_before_end_tag_if_exists = {
4579 let mut char_position = 0u32;
4580 let mut end_tag_offset = None;
4581
4582 'outer: for chunk in snapshot.text_for_range(range) {
4583 if let Some(byte_pos) = chunk.find(&**end_tag) {
4584 let chars_before_match =
4585 chunk[..byte_pos].chars().count() as u32;
4586 end_tag_offset =
4587 Some(char_position + chars_before_match);
4588 break 'outer;
4589 }
4590 char_position += chunk.chars().count() as u32;
4591 }
4592
4593 if let Some(end_tag_offset) = end_tag_offset {
4594 let cursor_is_before_end_tag = column <= end_tag_offset;
4595 if cursor_is_after_start_tag {
4596 if cursor_is_before_end_tag {
4597 insert_extra_newline = true;
4598 }
4599 let cursor_is_at_start_of_end_tag =
4600 column == end_tag_offset;
4601 if cursor_is_at_start_of_end_tag {
4602 indent_on_extra_newline.len = *len;
4603 }
4604 }
4605 cursor_is_before_end_tag
4606 } else {
4607 true
4608 }
4609 };
4610
4611 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4612 && cursor_is_before_end_tag_if_exists
4613 {
4614 if cursor_is_after_start_tag {
4615 indent_on_newline.len = *len;
4616 }
4617 Some(delimiter.clone())
4618 } else {
4619 None
4620 }
4621 });
4622
4623 (
4624 comment_delimiter,
4625 doc_delimiter,
4626 insert_extra_newline,
4627 indent_on_newline,
4628 indent_on_extra_newline,
4629 )
4630 } else {
4631 (
4632 None,
4633 None,
4634 false,
4635 IndentSize::default(),
4636 IndentSize::default(),
4637 )
4638 };
4639
4640 let prevent_auto_indent = doc_delimiter.is_some();
4641 let delimiter = comment_delimiter.or(doc_delimiter);
4642
4643 let capacity_for_delimiter =
4644 delimiter.as_deref().map(str::len).unwrap_or_default();
4645 let mut new_text = String::with_capacity(
4646 1 + capacity_for_delimiter
4647 + existing_indent.len as usize
4648 + indent_on_newline.len as usize
4649 + indent_on_extra_newline.len as usize,
4650 );
4651 new_text.push('\n');
4652 new_text.extend(existing_indent.chars());
4653 new_text.extend(indent_on_newline.chars());
4654
4655 if let Some(delimiter) = &delimiter {
4656 new_text.push_str(delimiter);
4657 }
4658
4659 if insert_extra_newline {
4660 new_text.push('\n');
4661 new_text.extend(existing_indent.chars());
4662 new_text.extend(indent_on_extra_newline.chars());
4663 }
4664
4665 let anchor = buffer.anchor_after(end);
4666 let new_selection = selection.map(|_| anchor);
4667 (
4668 ((start..end, new_text), prevent_auto_indent),
4669 (insert_extra_newline, new_selection),
4670 )
4671 })
4672 .unzip()
4673 };
4674
4675 let mut auto_indent_edits = Vec::new();
4676 let mut edits = Vec::new();
4677 for (edit, prevent_auto_indent) in edits_with_flags {
4678 if prevent_auto_indent {
4679 edits.push(edit);
4680 } else {
4681 auto_indent_edits.push(edit);
4682 }
4683 }
4684 if !edits.is_empty() {
4685 this.edit(edits, cx);
4686 }
4687 if !auto_indent_edits.is_empty() {
4688 this.edit_with_autoindent(auto_indent_edits, cx);
4689 }
4690
4691 let buffer = this.buffer.read(cx).snapshot(cx);
4692 let new_selections = selection_info
4693 .into_iter()
4694 .map(|(extra_newline_inserted, new_selection)| {
4695 let mut cursor = new_selection.end.to_point(&buffer);
4696 if extra_newline_inserted {
4697 cursor.row -= 1;
4698 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4699 }
4700 new_selection.map(|_| cursor)
4701 })
4702 .collect();
4703
4704 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4705 this.refresh_edit_prediction(true, false, window, cx);
4706 });
4707 }
4708
4709 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4711
4712 let buffer = self.buffer.read(cx);
4713 let snapshot = buffer.snapshot(cx);
4714
4715 let mut edits = Vec::new();
4716 let mut rows = Vec::new();
4717
4718 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4719 let cursor = selection.head();
4720 let row = cursor.row;
4721
4722 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4723
4724 let newline = "\n".to_string();
4725 edits.push((start_of_line..start_of_line, newline));
4726
4727 rows.push(row + rows_inserted as u32);
4728 }
4729
4730 self.transact(window, cx, |editor, window, cx| {
4731 editor.edit(edits, cx);
4732
4733 editor.change_selections(Default::default(), window, cx, |s| {
4734 let mut index = 0;
4735 s.move_cursors_with(|map, _, _| {
4736 let row = rows[index];
4737 index += 1;
4738
4739 let point = Point::new(row, 0);
4740 let boundary = map.next_line_boundary(point).1;
4741 let clipped = map.clip_point(boundary, Bias::Left);
4742
4743 (clipped, SelectionGoal::None)
4744 });
4745 });
4746
4747 let mut indent_edits = Vec::new();
4748 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4749 for row in rows {
4750 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4751 for (row, indent) in indents {
4752 if indent.len == 0 {
4753 continue;
4754 }
4755
4756 let text = match indent.kind {
4757 IndentKind::Space => " ".repeat(indent.len as usize),
4758 IndentKind::Tab => "\t".repeat(indent.len as usize),
4759 };
4760 let point = Point::new(row.0, 0);
4761 indent_edits.push((point..point, text));
4762 }
4763 }
4764 editor.edit(indent_edits, cx);
4765 });
4766 }
4767
4768 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4770
4771 let buffer = self.buffer.read(cx);
4772 let snapshot = buffer.snapshot(cx);
4773
4774 let mut edits = Vec::new();
4775 let mut rows = Vec::new();
4776 let mut rows_inserted = 0;
4777
4778 for selection in self.selections.all_adjusted(cx) {
4779 let cursor = selection.head();
4780 let row = cursor.row;
4781
4782 let point = Point::new(row + 1, 0);
4783 let start_of_line = snapshot.clip_point(point, Bias::Left);
4784
4785 let newline = "\n".to_string();
4786 edits.push((start_of_line..start_of_line, newline));
4787
4788 rows_inserted += 1;
4789 rows.push(row + rows_inserted);
4790 }
4791
4792 self.transact(window, cx, |editor, window, cx| {
4793 editor.edit(edits, cx);
4794
4795 editor.change_selections(Default::default(), window, cx, |s| {
4796 let mut index = 0;
4797 s.move_cursors_with(|map, _, _| {
4798 let row = rows[index];
4799 index += 1;
4800
4801 let point = Point::new(row, 0);
4802 let boundary = map.next_line_boundary(point).1;
4803 let clipped = map.clip_point(boundary, Bias::Left);
4804
4805 (clipped, SelectionGoal::None)
4806 });
4807 });
4808
4809 let mut indent_edits = Vec::new();
4810 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4811 for row in rows {
4812 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4813 for (row, indent) in indents {
4814 if indent.len == 0 {
4815 continue;
4816 }
4817
4818 let text = match indent.kind {
4819 IndentKind::Space => " ".repeat(indent.len as usize),
4820 IndentKind::Tab => "\t".repeat(indent.len as usize),
4821 };
4822 let point = Point::new(row.0, 0);
4823 indent_edits.push((point..point, text));
4824 }
4825 }
4826 editor.edit(indent_edits, cx);
4827 });
4828 }
4829
4830 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4831 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4832 original_indent_columns: Vec::new(),
4833 });
4834 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4835 }
4836
4837 fn insert_with_autoindent_mode(
4838 &mut self,
4839 text: &str,
4840 autoindent_mode: Option<AutoindentMode>,
4841 window: &mut Window,
4842 cx: &mut Context<Self>,
4843 ) {
4844 if self.read_only(cx) {
4845 return;
4846 }
4847
4848 let text: Arc<str> = text.into();
4849 self.transact(window, cx, |this, window, cx| {
4850 let old_selections = this.selections.all_adjusted(cx);
4851 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4852 let anchors = {
4853 let snapshot = buffer.read(cx);
4854 old_selections
4855 .iter()
4856 .map(|s| {
4857 let anchor = snapshot.anchor_after(s.head());
4858 s.map(|_| anchor)
4859 })
4860 .collect::<Vec<_>>()
4861 };
4862 buffer.edit(
4863 old_selections
4864 .iter()
4865 .map(|s| (s.start..s.end, text.clone())),
4866 autoindent_mode,
4867 cx,
4868 );
4869 anchors
4870 });
4871
4872 this.change_selections(Default::default(), window, cx, |s| {
4873 s.select_anchors(selection_anchors);
4874 });
4875
4876 cx.notify();
4877 });
4878 }
4879
4880 fn trigger_completion_on_input(
4881 &mut self,
4882 text: &str,
4883 trigger_in_words: bool,
4884 window: &mut Window,
4885 cx: &mut Context<Self>,
4886 ) {
4887 let completions_source = self
4888 .context_menu
4889 .borrow()
4890 .as_ref()
4891 .and_then(|menu| match menu {
4892 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4893 CodeContextMenu::CodeActions(_) => None,
4894 });
4895
4896 match completions_source {
4897 Some(CompletionsMenuSource::Words { .. }) => {
4898 self.open_or_update_completions_menu(
4899 Some(CompletionsMenuSource::Words {
4900 ignore_threshold: false,
4901 }),
4902 None,
4903 window,
4904 cx,
4905 );
4906 }
4907 Some(CompletionsMenuSource::Normal)
4908 | Some(CompletionsMenuSource::SnippetChoices)
4909 | None
4910 if self.is_completion_trigger(
4911 text,
4912 trigger_in_words,
4913 completions_source.is_some(),
4914 cx,
4915 ) =>
4916 {
4917 self.show_completions(
4918 &ShowCompletions {
4919 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4920 },
4921 window,
4922 cx,
4923 )
4924 }
4925 _ => {
4926 self.hide_context_menu(window, cx);
4927 }
4928 }
4929 }
4930
4931 fn is_completion_trigger(
4932 &self,
4933 text: &str,
4934 trigger_in_words: bool,
4935 menu_is_open: bool,
4936 cx: &mut Context<Self>,
4937 ) -> bool {
4938 let position = self.selections.newest_anchor().head();
4939 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4940 return false;
4941 };
4942
4943 if let Some(completion_provider) = &self.completion_provider {
4944 completion_provider.is_completion_trigger(
4945 &buffer,
4946 position.text_anchor,
4947 text,
4948 trigger_in_words,
4949 menu_is_open,
4950 cx,
4951 )
4952 } else {
4953 false
4954 }
4955 }
4956
4957 /// If any empty selections is touching the start of its innermost containing autoclose
4958 /// region, expand it to select the brackets.
4959 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4960 let selections = self.selections.all::<usize>(cx);
4961 let buffer = self.buffer.read(cx).read(cx);
4962 let new_selections = self
4963 .selections_with_autoclose_regions(selections, &buffer)
4964 .map(|(mut selection, region)| {
4965 if !selection.is_empty() {
4966 return selection;
4967 }
4968
4969 if let Some(region) = region {
4970 let mut range = region.range.to_offset(&buffer);
4971 if selection.start == range.start && range.start >= region.pair.start.len() {
4972 range.start -= region.pair.start.len();
4973 if buffer.contains_str_at(range.start, ®ion.pair.start)
4974 && buffer.contains_str_at(range.end, ®ion.pair.end)
4975 {
4976 range.end += region.pair.end.len();
4977 selection.start = range.start;
4978 selection.end = range.end;
4979
4980 return selection;
4981 }
4982 }
4983 }
4984
4985 let always_treat_brackets_as_autoclosed = buffer
4986 .language_settings_at(selection.start, cx)
4987 .always_treat_brackets_as_autoclosed;
4988
4989 if !always_treat_brackets_as_autoclosed {
4990 return selection;
4991 }
4992
4993 if let Some(scope) = buffer.language_scope_at(selection.start) {
4994 for (pair, enabled) in scope.brackets() {
4995 if !enabled || !pair.close {
4996 continue;
4997 }
4998
4999 if buffer.contains_str_at(selection.start, &pair.end) {
5000 let pair_start_len = pair.start.len();
5001 if buffer.contains_str_at(
5002 selection.start.saturating_sub(pair_start_len),
5003 &pair.start,
5004 ) {
5005 selection.start -= pair_start_len;
5006 selection.end += pair.end.len();
5007
5008 return selection;
5009 }
5010 }
5011 }
5012 }
5013
5014 selection
5015 })
5016 .collect();
5017
5018 drop(buffer);
5019 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5020 selections.select(new_selections)
5021 });
5022 }
5023
5024 /// Iterate the given selections, and for each one, find the smallest surrounding
5025 /// autoclose region. This uses the ordering of the selections and the autoclose
5026 /// regions to avoid repeated comparisons.
5027 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5028 &'a self,
5029 selections: impl IntoIterator<Item = Selection<D>>,
5030 buffer: &'a MultiBufferSnapshot,
5031 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5032 let mut i = 0;
5033 let mut regions = self.autoclose_regions.as_slice();
5034 selections.into_iter().map(move |selection| {
5035 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5036
5037 let mut enclosing = None;
5038 while let Some(pair_state) = regions.get(i) {
5039 if pair_state.range.end.to_offset(buffer) < range.start {
5040 regions = ®ions[i + 1..];
5041 i = 0;
5042 } else if pair_state.range.start.to_offset(buffer) > range.end {
5043 break;
5044 } else {
5045 if pair_state.selection_id == selection.id {
5046 enclosing = Some(pair_state);
5047 }
5048 i += 1;
5049 }
5050 }
5051
5052 (selection, enclosing)
5053 })
5054 }
5055
5056 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5057 fn invalidate_autoclose_regions(
5058 &mut self,
5059 mut selections: &[Selection<Anchor>],
5060 buffer: &MultiBufferSnapshot,
5061 ) {
5062 self.autoclose_regions.retain(|state| {
5063 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5064 return false;
5065 }
5066
5067 let mut i = 0;
5068 while let Some(selection) = selections.get(i) {
5069 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5070 selections = &selections[1..];
5071 continue;
5072 }
5073 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5074 break;
5075 }
5076 if selection.id == state.selection_id {
5077 return true;
5078 } else {
5079 i += 1;
5080 }
5081 }
5082 false
5083 });
5084 }
5085
5086 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5087 let offset = position.to_offset(buffer);
5088 let (word_range, kind) = buffer.surrounding_word(offset, true);
5089 if offset > word_range.start && kind == Some(CharKind::Word) {
5090 Some(
5091 buffer
5092 .text_for_range(word_range.start..offset)
5093 .collect::<String>(),
5094 )
5095 } else {
5096 None
5097 }
5098 }
5099
5100 pub fn toggle_inline_values(
5101 &mut self,
5102 _: &ToggleInlineValues,
5103 _: &mut Window,
5104 cx: &mut Context<Self>,
5105 ) {
5106 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5107
5108 self.refresh_inline_values(cx);
5109 }
5110
5111 pub fn toggle_inlay_hints(
5112 &mut self,
5113 _: &ToggleInlayHints,
5114 _: &mut Window,
5115 cx: &mut Context<Self>,
5116 ) {
5117 self.refresh_inlay_hints(
5118 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5119 cx,
5120 );
5121 }
5122
5123 pub fn inlay_hints_enabled(&self) -> bool {
5124 self.inlay_hint_cache.enabled
5125 }
5126
5127 pub fn inline_values_enabled(&self) -> bool {
5128 self.inline_value_cache.enabled
5129 }
5130
5131 #[cfg(any(test, feature = "test-support"))]
5132 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5133 self.display_map
5134 .read(cx)
5135 .current_inlays()
5136 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5137 .cloned()
5138 .collect()
5139 }
5140
5141 #[cfg(any(test, feature = "test-support"))]
5142 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5143 self.display_map
5144 .read(cx)
5145 .current_inlays()
5146 .cloned()
5147 .collect()
5148 }
5149
5150 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5151 if self.semantics_provider.is_none() || !self.mode.is_full() {
5152 return;
5153 }
5154
5155 let reason_description = reason.description();
5156 let ignore_debounce = matches!(
5157 reason,
5158 InlayHintRefreshReason::SettingsChange(_)
5159 | InlayHintRefreshReason::Toggle(_)
5160 | InlayHintRefreshReason::ExcerptsRemoved(_)
5161 | InlayHintRefreshReason::ModifiersChanged(_)
5162 );
5163 let (invalidate_cache, required_languages) = match reason {
5164 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5165 match self.inlay_hint_cache.modifiers_override(enabled) {
5166 Some(enabled) => {
5167 if enabled {
5168 (InvalidationStrategy::RefreshRequested, None)
5169 } else {
5170 self.splice_inlays(
5171 &self
5172 .visible_inlay_hints(cx)
5173 .iter()
5174 .map(|inlay| inlay.id)
5175 .collect::<Vec<InlayId>>(),
5176 Vec::new(),
5177 cx,
5178 );
5179 return;
5180 }
5181 }
5182 None => return,
5183 }
5184 }
5185 InlayHintRefreshReason::Toggle(enabled) => {
5186 if self.inlay_hint_cache.toggle(enabled) {
5187 if enabled {
5188 (InvalidationStrategy::RefreshRequested, None)
5189 } else {
5190 self.splice_inlays(
5191 &self
5192 .visible_inlay_hints(cx)
5193 .iter()
5194 .map(|inlay| inlay.id)
5195 .collect::<Vec<InlayId>>(),
5196 Vec::new(),
5197 cx,
5198 );
5199 return;
5200 }
5201 } else {
5202 return;
5203 }
5204 }
5205 InlayHintRefreshReason::SettingsChange(new_settings) => {
5206 match self.inlay_hint_cache.update_settings(
5207 &self.buffer,
5208 new_settings,
5209 self.visible_inlay_hints(cx),
5210 cx,
5211 ) {
5212 ControlFlow::Break(Some(InlaySplice {
5213 to_remove,
5214 to_insert,
5215 })) => {
5216 self.splice_inlays(&to_remove, to_insert, cx);
5217 return;
5218 }
5219 ControlFlow::Break(None) => return,
5220 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5221 }
5222 }
5223 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5224 if let Some(InlaySplice {
5225 to_remove,
5226 to_insert,
5227 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5228 {
5229 self.splice_inlays(&to_remove, to_insert, cx);
5230 }
5231 self.display_map.update(cx, |display_map, _| {
5232 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5233 });
5234 return;
5235 }
5236 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5237 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5238 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5239 }
5240 InlayHintRefreshReason::RefreshRequested => {
5241 (InvalidationStrategy::RefreshRequested, None)
5242 }
5243 };
5244
5245 if let Some(InlaySplice {
5246 to_remove,
5247 to_insert,
5248 }) = self.inlay_hint_cache.spawn_hint_refresh(
5249 reason_description,
5250 self.visible_excerpts(required_languages.as_ref(), cx),
5251 invalidate_cache,
5252 ignore_debounce,
5253 cx,
5254 ) {
5255 self.splice_inlays(&to_remove, to_insert, cx);
5256 }
5257 }
5258
5259 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5260 self.display_map
5261 .read(cx)
5262 .current_inlays()
5263 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5264 .cloned()
5265 .collect()
5266 }
5267
5268 pub fn visible_excerpts(
5269 &self,
5270 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5271 cx: &mut Context<Editor>,
5272 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5273 let Some(project) = self.project() else {
5274 return HashMap::default();
5275 };
5276 let project = project.read(cx);
5277 let multi_buffer = self.buffer().read(cx);
5278 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5279 let multi_buffer_visible_start = self
5280 .scroll_manager
5281 .anchor()
5282 .anchor
5283 .to_point(&multi_buffer_snapshot);
5284 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5285 multi_buffer_visible_start
5286 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5287 Bias::Left,
5288 );
5289 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5290 multi_buffer_snapshot
5291 .range_to_buffer_ranges(multi_buffer_visible_range)
5292 .into_iter()
5293 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5294 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5295 let buffer_file = project::File::from_dyn(buffer.file())?;
5296 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5297 let worktree_entry = buffer_worktree
5298 .read(cx)
5299 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5300 if worktree_entry.is_ignored {
5301 return None;
5302 }
5303
5304 let language = buffer.language()?;
5305 if let Some(restrict_to_languages) = restrict_to_languages
5306 && !restrict_to_languages.contains(language)
5307 {
5308 return None;
5309 }
5310 Some((
5311 excerpt_id,
5312 (
5313 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5314 buffer.version().clone(),
5315 excerpt_visible_range,
5316 ),
5317 ))
5318 })
5319 .collect()
5320 }
5321
5322 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5323 TextLayoutDetails {
5324 text_system: window.text_system().clone(),
5325 editor_style: self.style.clone().unwrap(),
5326 rem_size: window.rem_size(),
5327 scroll_anchor: self.scroll_manager.anchor(),
5328 visible_rows: self.visible_line_count(),
5329 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5330 }
5331 }
5332
5333 pub fn splice_inlays(
5334 &self,
5335 to_remove: &[InlayId],
5336 to_insert: Vec<Inlay>,
5337 cx: &mut Context<Self>,
5338 ) {
5339 self.display_map.update(cx, |display_map, cx| {
5340 display_map.splice_inlays(to_remove, to_insert, cx)
5341 });
5342 cx.notify();
5343 }
5344
5345 fn trigger_on_type_formatting(
5346 &self,
5347 input: String,
5348 window: &mut Window,
5349 cx: &mut Context<Self>,
5350 ) -> Option<Task<Result<()>>> {
5351 if input.len() != 1 {
5352 return None;
5353 }
5354
5355 let project = self.project()?;
5356 let position = self.selections.newest_anchor().head();
5357 let (buffer, buffer_position) = self
5358 .buffer
5359 .read(cx)
5360 .text_anchor_for_position(position, cx)?;
5361
5362 let settings = language_settings::language_settings(
5363 buffer
5364 .read(cx)
5365 .language_at(buffer_position)
5366 .map(|l| l.name()),
5367 buffer.read(cx).file(),
5368 cx,
5369 );
5370 if !settings.use_on_type_format {
5371 return None;
5372 }
5373
5374 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5375 // hence we do LSP request & edit on host side only — add formats to host's history.
5376 let push_to_lsp_host_history = true;
5377 // If this is not the host, append its history with new edits.
5378 let push_to_client_history = project.read(cx).is_via_collab();
5379
5380 let on_type_formatting = project.update(cx, |project, cx| {
5381 project.on_type_format(
5382 buffer.clone(),
5383 buffer_position,
5384 input,
5385 push_to_lsp_host_history,
5386 cx,
5387 )
5388 });
5389 Some(cx.spawn_in(window, async move |editor, cx| {
5390 if let Some(transaction) = on_type_formatting.await? {
5391 if push_to_client_history {
5392 buffer
5393 .update(cx, |buffer, _| {
5394 buffer.push_transaction(transaction, Instant::now());
5395 buffer.finalize_last_transaction();
5396 })
5397 .ok();
5398 }
5399 editor.update(cx, |editor, cx| {
5400 editor.refresh_document_highlights(cx);
5401 })?;
5402 }
5403 Ok(())
5404 }))
5405 }
5406
5407 pub fn show_word_completions(
5408 &mut self,
5409 _: &ShowWordCompletions,
5410 window: &mut Window,
5411 cx: &mut Context<Self>,
5412 ) {
5413 self.open_or_update_completions_menu(
5414 Some(CompletionsMenuSource::Words {
5415 ignore_threshold: true,
5416 }),
5417 None,
5418 window,
5419 cx,
5420 );
5421 }
5422
5423 pub fn show_completions(
5424 &mut self,
5425 options: &ShowCompletions,
5426 window: &mut Window,
5427 cx: &mut Context<Self>,
5428 ) {
5429 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5430 }
5431
5432 fn open_or_update_completions_menu(
5433 &mut self,
5434 requested_source: Option<CompletionsMenuSource>,
5435 trigger: Option<&str>,
5436 window: &mut Window,
5437 cx: &mut Context<Self>,
5438 ) {
5439 if self.pending_rename.is_some() {
5440 return;
5441 }
5442
5443 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5444
5445 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5446 // inserted and selected. To handle that case, the start of the selection is used so that
5447 // the menu starts with all choices.
5448 let position = self
5449 .selections
5450 .newest_anchor()
5451 .start
5452 .bias_right(&multibuffer_snapshot);
5453 if position.diff_base_anchor.is_some() {
5454 return;
5455 }
5456 let (buffer, buffer_position) =
5457 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5458 output
5459 } else {
5460 return;
5461 };
5462 let buffer_snapshot = buffer.read(cx).snapshot();
5463
5464 let query: Option<Arc<String>> =
5465 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5466
5467 drop(multibuffer_snapshot);
5468
5469 let mut ignore_word_threshold = false;
5470 let provider = match requested_source {
5471 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5472 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5473 ignore_word_threshold = ignore_threshold;
5474 None
5475 }
5476 Some(CompletionsMenuSource::SnippetChoices) => {
5477 log::error!("bug: SnippetChoices requested_source is not handled");
5478 None
5479 }
5480 };
5481
5482 let sort_completions = provider
5483 .as_ref()
5484 .is_some_and(|provider| provider.sort_completions());
5485
5486 let filter_completions = provider
5487 .as_ref()
5488 .is_none_or(|provider| provider.filter_completions());
5489
5490 let trigger_kind = match trigger {
5491 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5492 CompletionTriggerKind::TRIGGER_CHARACTER
5493 }
5494 _ => CompletionTriggerKind::INVOKED,
5495 };
5496 let completion_context = CompletionContext {
5497 trigger_character: trigger.and_then(|trigger| {
5498 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5499 Some(String::from(trigger))
5500 } else {
5501 None
5502 }
5503 }),
5504 trigger_kind,
5505 };
5506
5507 // Hide the current completions menu when a trigger char is typed. Without this, cached
5508 // completions from before the trigger char may be reused (#32774). Snippet choices could
5509 // involve trigger chars, so this is skipped in that case.
5510 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5511 {
5512 let menu_is_open = matches!(
5513 self.context_menu.borrow().as_ref(),
5514 Some(CodeContextMenu::Completions(_))
5515 );
5516 if menu_is_open {
5517 self.hide_context_menu(window, cx);
5518 }
5519 }
5520
5521 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5522 if filter_completions {
5523 menu.filter(query.clone(), provider.clone(), window, cx);
5524 }
5525 // When `is_incomplete` is false, no need to re-query completions when the current query
5526 // is a suffix of the initial query.
5527 if !menu.is_incomplete {
5528 // If the new query is a suffix of the old query (typing more characters) and
5529 // the previous result was complete, the existing completions can be filtered.
5530 //
5531 // Note that this is always true for snippet completions.
5532 let query_matches = match (&menu.initial_query, &query) {
5533 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5534 (None, _) => true,
5535 _ => false,
5536 };
5537 if query_matches {
5538 let position_matches = if menu.initial_position == position {
5539 true
5540 } else {
5541 let snapshot = self.buffer.read(cx).read(cx);
5542 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5543 };
5544 if position_matches {
5545 return;
5546 }
5547 }
5548 }
5549 };
5550
5551 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5552 buffer_snapshot.surrounding_word(buffer_position, false)
5553 {
5554 let word_to_exclude = buffer_snapshot
5555 .text_for_range(word_range.clone())
5556 .collect::<String>();
5557 (
5558 buffer_snapshot.anchor_before(word_range.start)
5559 ..buffer_snapshot.anchor_after(buffer_position),
5560 Some(word_to_exclude),
5561 )
5562 } else {
5563 (buffer_position..buffer_position, None)
5564 };
5565
5566 let language = buffer_snapshot
5567 .language_at(buffer_position)
5568 .map(|language| language.name());
5569
5570 let completion_settings =
5571 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5572
5573 let show_completion_documentation = buffer_snapshot
5574 .settings_at(buffer_position, cx)
5575 .show_completion_documentation;
5576
5577 // The document can be large, so stay in reasonable bounds when searching for words,
5578 // otherwise completion pop-up might be slow to appear.
5579 const WORD_LOOKUP_ROWS: u32 = 5_000;
5580 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5581 let min_word_search = buffer_snapshot.clip_point(
5582 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5583 Bias::Left,
5584 );
5585 let max_word_search = buffer_snapshot.clip_point(
5586 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5587 Bias::Right,
5588 );
5589 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5590 ..buffer_snapshot.point_to_offset(max_word_search);
5591
5592 let skip_digits = query
5593 .as_ref()
5594 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5595
5596 let omit_word_completions = !self.word_completions_enabled
5597 || (!ignore_word_threshold
5598 && match &query {
5599 Some(query) => query.chars().count() < completion_settings.words_min_length,
5600 None => completion_settings.words_min_length != 0,
5601 });
5602
5603 let (mut words, provider_responses) = match &provider {
5604 Some(provider) => {
5605 let provider_responses = provider.completions(
5606 position.excerpt_id,
5607 &buffer,
5608 buffer_position,
5609 completion_context,
5610 window,
5611 cx,
5612 );
5613
5614 let words = match (omit_word_completions, completion_settings.words) {
5615 (true, _) | (_, WordsCompletionMode::Disabled) => {
5616 Task::ready(BTreeMap::default())
5617 }
5618 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5619 .background_spawn(async move {
5620 buffer_snapshot.words_in_range(WordsQuery {
5621 fuzzy_contents: None,
5622 range: word_search_range,
5623 skip_digits,
5624 })
5625 }),
5626 };
5627
5628 (words, provider_responses)
5629 }
5630 None => {
5631 let words = if omit_word_completions {
5632 Task::ready(BTreeMap::default())
5633 } else {
5634 cx.background_spawn(async move {
5635 buffer_snapshot.words_in_range(WordsQuery {
5636 fuzzy_contents: None,
5637 range: word_search_range,
5638 skip_digits,
5639 })
5640 })
5641 };
5642 (words, Task::ready(Ok(Vec::new())))
5643 }
5644 };
5645
5646 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5647
5648 let id = post_inc(&mut self.next_completion_id);
5649 let task = cx.spawn_in(window, async move |editor, cx| {
5650 let Ok(()) = editor.update(cx, |this, _| {
5651 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5652 }) else {
5653 return;
5654 };
5655
5656 // TODO: Ideally completions from different sources would be selectively re-queried, so
5657 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5658 let mut completions = Vec::new();
5659 let mut is_incomplete = false;
5660 let mut display_options: Option<CompletionDisplayOptions> = None;
5661 if let Some(provider_responses) = provider_responses.await.log_err()
5662 && !provider_responses.is_empty()
5663 {
5664 for response in provider_responses {
5665 completions.extend(response.completions);
5666 is_incomplete = is_incomplete || response.is_incomplete;
5667 match display_options.as_mut() {
5668 None => {
5669 display_options = Some(response.display_options);
5670 }
5671 Some(options) => options.merge(&response.display_options),
5672 }
5673 }
5674 if completion_settings.words == WordsCompletionMode::Fallback {
5675 words = Task::ready(BTreeMap::default());
5676 }
5677 }
5678 let display_options = display_options.unwrap_or_default();
5679
5680 let mut words = words.await;
5681 if let Some(word_to_exclude) = &word_to_exclude {
5682 words.remove(word_to_exclude);
5683 }
5684 for lsp_completion in &completions {
5685 words.remove(&lsp_completion.new_text);
5686 }
5687 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5688 replace_range: word_replace_range.clone(),
5689 new_text: word.clone(),
5690 label: CodeLabel::plain(word, None),
5691 icon_path: None,
5692 documentation: None,
5693 source: CompletionSource::BufferWord {
5694 word_range,
5695 resolved: false,
5696 },
5697 insert_text_mode: Some(InsertTextMode::AS_IS),
5698 confirm: None,
5699 }));
5700
5701 let menu = if completions.is_empty() {
5702 None
5703 } else {
5704 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5705 let languages = editor
5706 .workspace
5707 .as_ref()
5708 .and_then(|(workspace, _)| workspace.upgrade())
5709 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5710 let menu = CompletionsMenu::new(
5711 id,
5712 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5713 sort_completions,
5714 show_completion_documentation,
5715 position,
5716 query.clone(),
5717 is_incomplete,
5718 buffer.clone(),
5719 completions.into(),
5720 display_options,
5721 snippet_sort_order,
5722 languages,
5723 language,
5724 cx,
5725 );
5726
5727 let query = if filter_completions { query } else { None };
5728 let matches_task = if let Some(query) = query {
5729 menu.do_async_filtering(query, cx)
5730 } else {
5731 Task::ready(menu.unfiltered_matches())
5732 };
5733 (menu, matches_task)
5734 }) else {
5735 return;
5736 };
5737
5738 let matches = matches_task.await;
5739
5740 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5741 // Newer menu already set, so exit.
5742 if let Some(CodeContextMenu::Completions(prev_menu)) =
5743 editor.context_menu.borrow().as_ref()
5744 && prev_menu.id > id
5745 {
5746 return;
5747 };
5748
5749 // Only valid to take prev_menu because it the new menu is immediately set
5750 // below, or the menu is hidden.
5751 if let Some(CodeContextMenu::Completions(prev_menu)) =
5752 editor.context_menu.borrow_mut().take()
5753 {
5754 let position_matches =
5755 if prev_menu.initial_position == menu.initial_position {
5756 true
5757 } else {
5758 let snapshot = editor.buffer.read(cx).read(cx);
5759 prev_menu.initial_position.to_offset(&snapshot)
5760 == menu.initial_position.to_offset(&snapshot)
5761 };
5762 if position_matches {
5763 // Preserve markdown cache before `set_filter_results` because it will
5764 // try to populate the documentation cache.
5765 menu.preserve_markdown_cache(prev_menu);
5766 }
5767 };
5768
5769 menu.set_filter_results(matches, provider, window, cx);
5770 }) else {
5771 return;
5772 };
5773
5774 menu.visible().then_some(menu)
5775 };
5776
5777 editor
5778 .update_in(cx, |editor, window, cx| {
5779 if editor.focus_handle.is_focused(window)
5780 && let Some(menu) = menu
5781 {
5782 *editor.context_menu.borrow_mut() =
5783 Some(CodeContextMenu::Completions(menu));
5784
5785 crate::hover_popover::hide_hover(editor, cx);
5786 if editor.show_edit_predictions_in_menu() {
5787 editor.update_visible_edit_prediction(window, cx);
5788 } else {
5789 editor.discard_edit_prediction(false, cx);
5790 }
5791
5792 cx.notify();
5793 return;
5794 }
5795
5796 if editor.completion_tasks.len() <= 1 {
5797 // If there are no more completion tasks and the last menu was empty, we should hide it.
5798 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5799 // If it was already hidden and we don't show edit predictions in the menu,
5800 // we should also show the edit prediction when available.
5801 if was_hidden && editor.show_edit_predictions_in_menu() {
5802 editor.update_visible_edit_prediction(window, cx);
5803 }
5804 }
5805 })
5806 .ok();
5807 });
5808
5809 self.completion_tasks.push((id, task));
5810 }
5811
5812 #[cfg(feature = "test-support")]
5813 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5814 let menu = self.context_menu.borrow();
5815 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5816 let completions = menu.completions.borrow();
5817 Some(completions.to_vec())
5818 } else {
5819 None
5820 }
5821 }
5822
5823 pub fn with_completions_menu_matching_id<R>(
5824 &self,
5825 id: CompletionId,
5826 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5827 ) -> R {
5828 let mut context_menu = self.context_menu.borrow_mut();
5829 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5830 return f(None);
5831 };
5832 if completions_menu.id != id {
5833 return f(None);
5834 }
5835 f(Some(completions_menu))
5836 }
5837
5838 pub fn confirm_completion(
5839 &mut self,
5840 action: &ConfirmCompletion,
5841 window: &mut Window,
5842 cx: &mut Context<Self>,
5843 ) -> Option<Task<Result<()>>> {
5844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5845 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5846 }
5847
5848 pub fn confirm_completion_insert(
5849 &mut self,
5850 _: &ConfirmCompletionInsert,
5851 window: &mut Window,
5852 cx: &mut Context<Self>,
5853 ) -> Option<Task<Result<()>>> {
5854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5855 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5856 }
5857
5858 pub fn confirm_completion_replace(
5859 &mut self,
5860 _: &ConfirmCompletionReplace,
5861 window: &mut Window,
5862 cx: &mut Context<Self>,
5863 ) -> Option<Task<Result<()>>> {
5864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5865 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5866 }
5867
5868 pub fn compose_completion(
5869 &mut self,
5870 action: &ComposeCompletion,
5871 window: &mut Window,
5872 cx: &mut Context<Self>,
5873 ) -> Option<Task<Result<()>>> {
5874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5875 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5876 }
5877
5878 fn do_completion(
5879 &mut self,
5880 item_ix: Option<usize>,
5881 intent: CompletionIntent,
5882 window: &mut Window,
5883 cx: &mut Context<Editor>,
5884 ) -> Option<Task<Result<()>>> {
5885 use language::ToOffset as _;
5886
5887 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5888 else {
5889 return None;
5890 };
5891
5892 let candidate_id = {
5893 let entries = completions_menu.entries.borrow();
5894 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5895 if self.show_edit_predictions_in_menu() {
5896 self.discard_edit_prediction(true, cx);
5897 }
5898 mat.candidate_id
5899 };
5900
5901 let completion = completions_menu
5902 .completions
5903 .borrow()
5904 .get(candidate_id)?
5905 .clone();
5906 cx.stop_propagation();
5907
5908 let buffer_handle = completions_menu.buffer.clone();
5909
5910 let CompletionEdit {
5911 new_text,
5912 snippet,
5913 replace_range,
5914 } = process_completion_for_edit(
5915 &completion,
5916 intent,
5917 &buffer_handle,
5918 &completions_menu.initial_position.text_anchor,
5919 cx,
5920 );
5921
5922 let buffer = buffer_handle.read(cx);
5923 let snapshot = self.buffer.read(cx).snapshot(cx);
5924 let newest_anchor = self.selections.newest_anchor();
5925 let replace_range_multibuffer = {
5926 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5927 let multibuffer_anchor = snapshot
5928 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5929 .unwrap()
5930 ..snapshot
5931 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5932 .unwrap();
5933 multibuffer_anchor.start.to_offset(&snapshot)
5934 ..multibuffer_anchor.end.to_offset(&snapshot)
5935 };
5936 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5937 return None;
5938 }
5939
5940 let old_text = buffer
5941 .text_for_range(replace_range.clone())
5942 .collect::<String>();
5943 let lookbehind = newest_anchor
5944 .start
5945 .text_anchor
5946 .to_offset(buffer)
5947 .saturating_sub(replace_range.start);
5948 let lookahead = replace_range
5949 .end
5950 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5951 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5952 let suffix = &old_text[lookbehind.min(old_text.len())..];
5953
5954 let selections = self.selections.all::<usize>(cx);
5955 let mut ranges = Vec::new();
5956 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5957
5958 for selection in &selections {
5959 let range = if selection.id == newest_anchor.id {
5960 replace_range_multibuffer.clone()
5961 } else {
5962 let mut range = selection.range();
5963
5964 // if prefix is present, don't duplicate it
5965 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5966 range.start = range.start.saturating_sub(lookbehind);
5967
5968 // if suffix is also present, mimic the newest cursor and replace it
5969 if selection.id != newest_anchor.id
5970 && snapshot.contains_str_at(range.end, suffix)
5971 {
5972 range.end += lookahead;
5973 }
5974 }
5975 range
5976 };
5977
5978 ranges.push(range.clone());
5979
5980 if !self.linked_edit_ranges.is_empty() {
5981 let start_anchor = snapshot.anchor_before(range.start);
5982 let end_anchor = snapshot.anchor_after(range.end);
5983 if let Some(ranges) = self
5984 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5985 {
5986 for (buffer, edits) in ranges {
5987 linked_edits
5988 .entry(buffer.clone())
5989 .or_default()
5990 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5991 }
5992 }
5993 }
5994 }
5995
5996 let common_prefix_len = old_text
5997 .chars()
5998 .zip(new_text.chars())
5999 .take_while(|(a, b)| a == b)
6000 .map(|(a, _)| a.len_utf8())
6001 .sum::<usize>();
6002
6003 cx.emit(EditorEvent::InputHandled {
6004 utf16_range_to_replace: None,
6005 text: new_text[common_prefix_len..].into(),
6006 });
6007
6008 self.transact(window, cx, |editor, window, cx| {
6009 if let Some(mut snippet) = snippet {
6010 snippet.text = new_text.to_string();
6011 editor
6012 .insert_snippet(&ranges, snippet, window, cx)
6013 .log_err();
6014 } else {
6015 editor.buffer.update(cx, |multi_buffer, cx| {
6016 let auto_indent = match completion.insert_text_mode {
6017 Some(InsertTextMode::AS_IS) => None,
6018 _ => editor.autoindent_mode.clone(),
6019 };
6020 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6021 multi_buffer.edit(edits, auto_indent, cx);
6022 });
6023 }
6024 for (buffer, edits) in linked_edits {
6025 buffer.update(cx, |buffer, cx| {
6026 let snapshot = buffer.snapshot();
6027 let edits = edits
6028 .into_iter()
6029 .map(|(range, text)| {
6030 use text::ToPoint as TP;
6031 let end_point = TP::to_point(&range.end, &snapshot);
6032 let start_point = TP::to_point(&range.start, &snapshot);
6033 (start_point..end_point, text)
6034 })
6035 .sorted_by_key(|(range, _)| range.start);
6036 buffer.edit(edits, None, cx);
6037 })
6038 }
6039
6040 editor.refresh_edit_prediction(true, false, window, cx);
6041 });
6042 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6043
6044 let show_new_completions_on_confirm = completion
6045 .confirm
6046 .as_ref()
6047 .is_some_and(|confirm| confirm(intent, window, cx));
6048 if show_new_completions_on_confirm {
6049 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6050 }
6051
6052 let provider = self.completion_provider.as_ref()?;
6053 drop(completion);
6054 let apply_edits = provider.apply_additional_edits_for_completion(
6055 buffer_handle,
6056 completions_menu.completions.clone(),
6057 candidate_id,
6058 true,
6059 cx,
6060 );
6061
6062 let editor_settings = EditorSettings::get_global(cx);
6063 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6064 // After the code completion is finished, users often want to know what signatures are needed.
6065 // so we should automatically call signature_help
6066 self.show_signature_help(&ShowSignatureHelp, window, cx);
6067 }
6068
6069 Some(cx.foreground_executor().spawn(async move {
6070 apply_edits.await?;
6071 Ok(())
6072 }))
6073 }
6074
6075 pub fn toggle_code_actions(
6076 &mut self,
6077 action: &ToggleCodeActions,
6078 window: &mut Window,
6079 cx: &mut Context<Self>,
6080 ) {
6081 let quick_launch = action.quick_launch;
6082 let mut context_menu = self.context_menu.borrow_mut();
6083 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6084 if code_actions.deployed_from == action.deployed_from {
6085 // Toggle if we're selecting the same one
6086 *context_menu = None;
6087 cx.notify();
6088 return;
6089 } else {
6090 // Otherwise, clear it and start a new one
6091 *context_menu = None;
6092 cx.notify();
6093 }
6094 }
6095 drop(context_menu);
6096 let snapshot = self.snapshot(window, cx);
6097 let deployed_from = action.deployed_from.clone();
6098 let action = action.clone();
6099 self.completion_tasks.clear();
6100 self.discard_edit_prediction(false, cx);
6101
6102 let multibuffer_point = match &action.deployed_from {
6103 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6104 DisplayPoint::new(*row, 0).to_point(&snapshot)
6105 }
6106 _ => self.selections.newest::<Point>(cx).head(),
6107 };
6108 let Some((buffer, buffer_row)) = snapshot
6109 .buffer_snapshot
6110 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6111 .and_then(|(buffer_snapshot, range)| {
6112 self.buffer()
6113 .read(cx)
6114 .buffer(buffer_snapshot.remote_id())
6115 .map(|buffer| (buffer, range.start.row))
6116 })
6117 else {
6118 return;
6119 };
6120 let buffer_id = buffer.read(cx).remote_id();
6121 let tasks = self
6122 .tasks
6123 .get(&(buffer_id, buffer_row))
6124 .map(|t| Arc::new(t.to_owned()));
6125
6126 if !self.focus_handle.is_focused(window) {
6127 return;
6128 }
6129 let project = self.project.clone();
6130
6131 let code_actions_task = match deployed_from {
6132 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6133 _ => self.code_actions(buffer_row, window, cx),
6134 };
6135
6136 let runnable_task = match deployed_from {
6137 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6138 _ => {
6139 let mut task_context_task = Task::ready(None);
6140 if let Some(tasks) = &tasks
6141 && let Some(project) = project
6142 {
6143 task_context_task =
6144 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6145 }
6146
6147 cx.spawn_in(window, {
6148 let buffer = buffer.clone();
6149 async move |editor, cx| {
6150 let task_context = task_context_task.await;
6151
6152 let resolved_tasks =
6153 tasks
6154 .zip(task_context.clone())
6155 .map(|(tasks, task_context)| ResolvedTasks {
6156 templates: tasks.resolve(&task_context).collect(),
6157 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6158 multibuffer_point.row,
6159 tasks.column,
6160 )),
6161 });
6162 let debug_scenarios = editor
6163 .update(cx, |editor, cx| {
6164 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6165 })?
6166 .await;
6167 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6168 }
6169 })
6170 }
6171 };
6172
6173 cx.spawn_in(window, async move |editor, cx| {
6174 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6175 let code_actions = code_actions_task.await;
6176 let spawn_straight_away = quick_launch
6177 && resolved_tasks
6178 .as_ref()
6179 .is_some_and(|tasks| tasks.templates.len() == 1)
6180 && code_actions
6181 .as_ref()
6182 .is_none_or(|actions| actions.is_empty())
6183 && debug_scenarios.is_empty();
6184
6185 editor.update_in(cx, |editor, window, cx| {
6186 crate::hover_popover::hide_hover(editor, cx);
6187 let actions = CodeActionContents::new(
6188 resolved_tasks,
6189 code_actions,
6190 debug_scenarios,
6191 task_context.unwrap_or_default(),
6192 );
6193
6194 // Don't show the menu if there are no actions available
6195 if actions.is_empty() {
6196 cx.notify();
6197 return Task::ready(Ok(()));
6198 }
6199
6200 *editor.context_menu.borrow_mut() =
6201 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6202 buffer,
6203 actions,
6204 selected_item: Default::default(),
6205 scroll_handle: UniformListScrollHandle::default(),
6206 deployed_from,
6207 }));
6208 cx.notify();
6209 if spawn_straight_away
6210 && let Some(task) = editor.confirm_code_action(
6211 &ConfirmCodeAction { item_ix: Some(0) },
6212 window,
6213 cx,
6214 )
6215 {
6216 return task;
6217 }
6218
6219 Task::ready(Ok(()))
6220 })
6221 })
6222 .detach_and_log_err(cx);
6223 }
6224
6225 fn debug_scenarios(
6226 &mut self,
6227 resolved_tasks: &Option<ResolvedTasks>,
6228 buffer: &Entity<Buffer>,
6229 cx: &mut App,
6230 ) -> Task<Vec<task::DebugScenario>> {
6231 maybe!({
6232 let project = self.project()?;
6233 let dap_store = project.read(cx).dap_store();
6234 let mut scenarios = vec![];
6235 let resolved_tasks = resolved_tasks.as_ref()?;
6236 let buffer = buffer.read(cx);
6237 let language = buffer.language()?;
6238 let file = buffer.file();
6239 let debug_adapter = language_settings(language.name().into(), file, cx)
6240 .debuggers
6241 .first()
6242 .map(SharedString::from)
6243 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6244
6245 dap_store.update(cx, |dap_store, cx| {
6246 for (_, task) in &resolved_tasks.templates {
6247 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6248 task.original_task().clone(),
6249 debug_adapter.clone().into(),
6250 task.display_label().to_owned().into(),
6251 cx,
6252 );
6253 scenarios.push(maybe_scenario);
6254 }
6255 });
6256 Some(cx.background_spawn(async move {
6257 futures::future::join_all(scenarios)
6258 .await
6259 .into_iter()
6260 .flatten()
6261 .collect::<Vec<_>>()
6262 }))
6263 })
6264 .unwrap_or_else(|| Task::ready(vec![]))
6265 }
6266
6267 fn code_actions(
6268 &mut self,
6269 buffer_row: u32,
6270 window: &mut Window,
6271 cx: &mut Context<Self>,
6272 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6273 let mut task = self.code_actions_task.take();
6274 cx.spawn_in(window, async move |editor, cx| {
6275 while let Some(prev_task) = task {
6276 prev_task.await.log_err();
6277 task = editor
6278 .update(cx, |this, _| this.code_actions_task.take())
6279 .ok()?;
6280 }
6281
6282 editor
6283 .update(cx, |editor, cx| {
6284 editor
6285 .available_code_actions
6286 .clone()
6287 .and_then(|(location, code_actions)| {
6288 let snapshot = location.buffer.read(cx).snapshot();
6289 let point_range = location.range.to_point(&snapshot);
6290 let point_range = point_range.start.row..=point_range.end.row;
6291 if point_range.contains(&buffer_row) {
6292 Some(code_actions)
6293 } else {
6294 None
6295 }
6296 })
6297 })
6298 .ok()
6299 .flatten()
6300 })
6301 }
6302
6303 pub fn confirm_code_action(
6304 &mut self,
6305 action: &ConfirmCodeAction,
6306 window: &mut Window,
6307 cx: &mut Context<Self>,
6308 ) -> Option<Task<Result<()>>> {
6309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6310
6311 let actions_menu =
6312 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6313 menu
6314 } else {
6315 return None;
6316 };
6317
6318 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6319 let action = actions_menu.actions.get(action_ix)?;
6320 let title = action.label();
6321 let buffer = actions_menu.buffer;
6322 let workspace = self.workspace()?;
6323
6324 match action {
6325 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6326 workspace.update(cx, |workspace, cx| {
6327 workspace.schedule_resolved_task(
6328 task_source_kind,
6329 resolved_task,
6330 false,
6331 window,
6332 cx,
6333 );
6334
6335 Some(Task::ready(Ok(())))
6336 })
6337 }
6338 CodeActionsItem::CodeAction {
6339 excerpt_id,
6340 action,
6341 provider,
6342 } => {
6343 let apply_code_action =
6344 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6345 let workspace = workspace.downgrade();
6346 Some(cx.spawn_in(window, async move |editor, cx| {
6347 let project_transaction = apply_code_action.await?;
6348 Self::open_project_transaction(
6349 &editor,
6350 workspace,
6351 project_transaction,
6352 title,
6353 cx,
6354 )
6355 .await
6356 }))
6357 }
6358 CodeActionsItem::DebugScenario(scenario) => {
6359 let context = actions_menu.actions.context;
6360
6361 workspace.update(cx, |workspace, cx| {
6362 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6363 workspace.start_debug_session(
6364 scenario,
6365 context,
6366 Some(buffer),
6367 None,
6368 window,
6369 cx,
6370 );
6371 });
6372 Some(Task::ready(Ok(())))
6373 }
6374 }
6375 }
6376
6377 pub async fn open_project_transaction(
6378 editor: &WeakEntity<Editor>,
6379 workspace: WeakEntity<Workspace>,
6380 transaction: ProjectTransaction,
6381 title: String,
6382 cx: &mut AsyncWindowContext,
6383 ) -> Result<()> {
6384 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6385 cx.update(|_, cx| {
6386 entries.sort_unstable_by_key(|(buffer, _)| {
6387 buffer.read(cx).file().map(|f| f.path().clone())
6388 });
6389 })?;
6390
6391 // If the project transaction's edits are all contained within this editor, then
6392 // avoid opening a new editor to display them.
6393
6394 if let Some((buffer, transaction)) = entries.first() {
6395 if entries.len() == 1 {
6396 let excerpt = editor.update(cx, |editor, cx| {
6397 editor
6398 .buffer()
6399 .read(cx)
6400 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6401 })?;
6402 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6403 && excerpted_buffer == *buffer
6404 {
6405 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6406 let excerpt_range = excerpt_range.to_offset(buffer);
6407 buffer
6408 .edited_ranges_for_transaction::<usize>(transaction)
6409 .all(|range| {
6410 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6411 })
6412 })?;
6413
6414 if all_edits_within_excerpt {
6415 return Ok(());
6416 }
6417 }
6418 }
6419 } else {
6420 return Ok(());
6421 }
6422
6423 let mut ranges_to_highlight = Vec::new();
6424 let excerpt_buffer = cx.new(|cx| {
6425 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6426 for (buffer_handle, transaction) in &entries {
6427 let edited_ranges = buffer_handle
6428 .read(cx)
6429 .edited_ranges_for_transaction::<Point>(transaction)
6430 .collect::<Vec<_>>();
6431 let (ranges, _) = multibuffer.set_excerpts_for_path(
6432 PathKey::for_buffer(buffer_handle, cx),
6433 buffer_handle.clone(),
6434 edited_ranges,
6435 multibuffer_context_lines(cx),
6436 cx,
6437 );
6438
6439 ranges_to_highlight.extend(ranges);
6440 }
6441 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6442 multibuffer
6443 })?;
6444
6445 workspace.update_in(cx, |workspace, window, cx| {
6446 let project = workspace.project().clone();
6447 let editor =
6448 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6449 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6450 editor.update(cx, |editor, cx| {
6451 editor.highlight_background::<Self>(
6452 &ranges_to_highlight,
6453 |theme| theme.colors().editor_highlighted_line_background,
6454 cx,
6455 );
6456 });
6457 })?;
6458
6459 Ok(())
6460 }
6461
6462 pub fn clear_code_action_providers(&mut self) {
6463 self.code_action_providers.clear();
6464 self.available_code_actions.take();
6465 }
6466
6467 pub fn add_code_action_provider(
6468 &mut self,
6469 provider: Rc<dyn CodeActionProvider>,
6470 window: &mut Window,
6471 cx: &mut Context<Self>,
6472 ) {
6473 if self
6474 .code_action_providers
6475 .iter()
6476 .any(|existing_provider| existing_provider.id() == provider.id())
6477 {
6478 return;
6479 }
6480
6481 self.code_action_providers.push(provider);
6482 self.refresh_code_actions(window, cx);
6483 }
6484
6485 pub fn remove_code_action_provider(
6486 &mut self,
6487 id: Arc<str>,
6488 window: &mut Window,
6489 cx: &mut Context<Self>,
6490 ) {
6491 self.code_action_providers
6492 .retain(|provider| provider.id() != id);
6493 self.refresh_code_actions(window, cx);
6494 }
6495
6496 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6497 !self.code_action_providers.is_empty()
6498 && EditorSettings::get_global(cx).toolbar.code_actions
6499 }
6500
6501 pub fn has_available_code_actions(&self) -> bool {
6502 self.available_code_actions
6503 .as_ref()
6504 .is_some_and(|(_, actions)| !actions.is_empty())
6505 }
6506
6507 fn render_inline_code_actions(
6508 &self,
6509 icon_size: ui::IconSize,
6510 display_row: DisplayRow,
6511 is_active: bool,
6512 cx: &mut Context<Self>,
6513 ) -> AnyElement {
6514 let show_tooltip = !self.context_menu_visible();
6515 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6516 .icon_size(icon_size)
6517 .shape(ui::IconButtonShape::Square)
6518 .icon_color(ui::Color::Hidden)
6519 .toggle_state(is_active)
6520 .when(show_tooltip, |this| {
6521 this.tooltip({
6522 let focus_handle = self.focus_handle.clone();
6523 move |window, cx| {
6524 Tooltip::for_action_in(
6525 "Toggle Code Actions",
6526 &ToggleCodeActions {
6527 deployed_from: None,
6528 quick_launch: false,
6529 },
6530 &focus_handle,
6531 window,
6532 cx,
6533 )
6534 }
6535 })
6536 })
6537 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6538 window.focus(&editor.focus_handle(cx));
6539 editor.toggle_code_actions(
6540 &crate::actions::ToggleCodeActions {
6541 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6542 display_row,
6543 )),
6544 quick_launch: false,
6545 },
6546 window,
6547 cx,
6548 );
6549 }))
6550 .into_any_element()
6551 }
6552
6553 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6554 &self.context_menu
6555 }
6556
6557 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6558 let newest_selection = self.selections.newest_anchor().clone();
6559 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6560 let buffer = self.buffer.read(cx);
6561 if newest_selection.head().diff_base_anchor.is_some() {
6562 return None;
6563 }
6564 let (start_buffer, start) =
6565 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6566 let (end_buffer, end) =
6567 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6568 if start_buffer != end_buffer {
6569 return None;
6570 }
6571
6572 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6573 cx.background_executor()
6574 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6575 .await;
6576
6577 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6578 let providers = this.code_action_providers.clone();
6579 let tasks = this
6580 .code_action_providers
6581 .iter()
6582 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6583 .collect::<Vec<_>>();
6584 (providers, tasks)
6585 })?;
6586
6587 let mut actions = Vec::new();
6588 for (provider, provider_actions) in
6589 providers.into_iter().zip(future::join_all(tasks).await)
6590 {
6591 if let Some(provider_actions) = provider_actions.log_err() {
6592 actions.extend(provider_actions.into_iter().map(|action| {
6593 AvailableCodeAction {
6594 excerpt_id: newest_selection.start.excerpt_id,
6595 action,
6596 provider: provider.clone(),
6597 }
6598 }));
6599 }
6600 }
6601
6602 this.update(cx, |this, cx| {
6603 this.available_code_actions = if actions.is_empty() {
6604 None
6605 } else {
6606 Some((
6607 Location {
6608 buffer: start_buffer,
6609 range: start..end,
6610 },
6611 actions.into(),
6612 ))
6613 };
6614 cx.notify();
6615 })
6616 }));
6617 None
6618 }
6619
6620 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6621 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6622 self.show_git_blame_inline = false;
6623
6624 self.show_git_blame_inline_delay_task =
6625 Some(cx.spawn_in(window, async move |this, cx| {
6626 cx.background_executor().timer(delay).await;
6627
6628 this.update(cx, |this, cx| {
6629 this.show_git_blame_inline = true;
6630 cx.notify();
6631 })
6632 .log_err();
6633 }));
6634 }
6635 }
6636
6637 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6638 let snapshot = self.snapshot(window, cx);
6639 let cursor = self.selections.newest::<Point>(cx).head();
6640 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6641 else {
6642 return;
6643 };
6644
6645 let Some(blame) = self.blame.as_ref() else {
6646 return;
6647 };
6648
6649 let row_info = RowInfo {
6650 buffer_id: Some(buffer.remote_id()),
6651 buffer_row: Some(point.row),
6652 ..Default::default()
6653 };
6654 let Some((buffer, blame_entry)) = blame
6655 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6656 .flatten()
6657 else {
6658 return;
6659 };
6660
6661 let anchor = self.selections.newest_anchor().head();
6662 let position = self.to_pixel_point(anchor, &snapshot, window);
6663 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6664 self.show_blame_popover(
6665 buffer,
6666 &blame_entry,
6667 position + last_bounds.origin,
6668 true,
6669 cx,
6670 );
6671 };
6672 }
6673
6674 fn show_blame_popover(
6675 &mut self,
6676 buffer: BufferId,
6677 blame_entry: &BlameEntry,
6678 position: gpui::Point<Pixels>,
6679 ignore_timeout: bool,
6680 cx: &mut Context<Self>,
6681 ) {
6682 if let Some(state) = &mut self.inline_blame_popover {
6683 state.hide_task.take();
6684 } else {
6685 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6686 let blame_entry = blame_entry.clone();
6687 let show_task = cx.spawn(async move |editor, cx| {
6688 if !ignore_timeout {
6689 cx.background_executor()
6690 .timer(std::time::Duration::from_millis(blame_popover_delay))
6691 .await;
6692 }
6693 editor
6694 .update(cx, |editor, cx| {
6695 editor.inline_blame_popover_show_task.take();
6696 let Some(blame) = editor.blame.as_ref() else {
6697 return;
6698 };
6699 let blame = blame.read(cx);
6700 let details = blame.details_for_entry(buffer, &blame_entry);
6701 let markdown = cx.new(|cx| {
6702 Markdown::new(
6703 details
6704 .as_ref()
6705 .map(|message| message.message.clone())
6706 .unwrap_or_default(),
6707 None,
6708 None,
6709 cx,
6710 )
6711 });
6712 editor.inline_blame_popover = Some(InlineBlamePopover {
6713 position,
6714 hide_task: None,
6715 popover_bounds: None,
6716 popover_state: InlineBlamePopoverState {
6717 scroll_handle: ScrollHandle::new(),
6718 commit_message: details,
6719 markdown,
6720 },
6721 keyboard_grace: ignore_timeout,
6722 });
6723 cx.notify();
6724 })
6725 .ok();
6726 });
6727 self.inline_blame_popover_show_task = Some(show_task);
6728 }
6729 }
6730
6731 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6732 self.inline_blame_popover_show_task.take();
6733 if let Some(state) = &mut self.inline_blame_popover {
6734 let hide_task = cx.spawn(async move |editor, cx| {
6735 cx.background_executor()
6736 .timer(std::time::Duration::from_millis(100))
6737 .await;
6738 editor
6739 .update(cx, |editor, cx| {
6740 editor.inline_blame_popover.take();
6741 cx.notify();
6742 })
6743 .ok();
6744 });
6745 state.hide_task = Some(hide_task);
6746 }
6747 }
6748
6749 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6750 if self.pending_rename.is_some() {
6751 return None;
6752 }
6753
6754 let provider = self.semantics_provider.clone()?;
6755 let buffer = self.buffer.read(cx);
6756 let newest_selection = self.selections.newest_anchor().clone();
6757 let cursor_position = newest_selection.head();
6758 let (cursor_buffer, cursor_buffer_position) =
6759 buffer.text_anchor_for_position(cursor_position, cx)?;
6760 let (tail_buffer, tail_buffer_position) =
6761 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6762 if cursor_buffer != tail_buffer {
6763 return None;
6764 }
6765
6766 let snapshot = cursor_buffer.read(cx).snapshot();
6767 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6768 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6769 if start_word_range != end_word_range {
6770 self.document_highlights_task.take();
6771 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6772 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6773 return None;
6774 }
6775
6776 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6777 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6778 cx.background_executor()
6779 .timer(Duration::from_millis(debounce))
6780 .await;
6781
6782 let highlights = if let Some(highlights) = cx
6783 .update(|cx| {
6784 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6785 })
6786 .ok()
6787 .flatten()
6788 {
6789 highlights.await.log_err()
6790 } else {
6791 None
6792 };
6793
6794 if let Some(highlights) = highlights {
6795 this.update(cx, |this, cx| {
6796 if this.pending_rename.is_some() {
6797 return;
6798 }
6799
6800 let buffer = this.buffer.read(cx);
6801 if buffer
6802 .text_anchor_for_position(cursor_position, cx)
6803 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6804 {
6805 return;
6806 }
6807
6808 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6809 let mut write_ranges = Vec::new();
6810 let mut read_ranges = Vec::new();
6811 for highlight in highlights {
6812 let buffer_id = cursor_buffer.read(cx).remote_id();
6813 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6814 {
6815 let start = highlight
6816 .range
6817 .start
6818 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6819 let end = highlight
6820 .range
6821 .end
6822 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6823 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6824 continue;
6825 }
6826
6827 let range = Anchor {
6828 buffer_id: Some(buffer_id),
6829 excerpt_id,
6830 text_anchor: start,
6831 diff_base_anchor: None,
6832 }..Anchor {
6833 buffer_id: Some(buffer_id),
6834 excerpt_id,
6835 text_anchor: end,
6836 diff_base_anchor: None,
6837 };
6838 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6839 write_ranges.push(range);
6840 } else {
6841 read_ranges.push(range);
6842 }
6843 }
6844 }
6845
6846 this.highlight_background::<DocumentHighlightRead>(
6847 &read_ranges,
6848 |theme| theme.colors().editor_document_highlight_read_background,
6849 cx,
6850 );
6851 this.highlight_background::<DocumentHighlightWrite>(
6852 &write_ranges,
6853 |theme| theme.colors().editor_document_highlight_write_background,
6854 cx,
6855 );
6856 cx.notify();
6857 })
6858 .log_err();
6859 }
6860 }));
6861 None
6862 }
6863
6864 fn prepare_highlight_query_from_selection(
6865 &mut self,
6866 cx: &mut Context<Editor>,
6867 ) -> Option<(String, Range<Anchor>)> {
6868 if matches!(self.mode, EditorMode::SingleLine) {
6869 return None;
6870 }
6871 if !EditorSettings::get_global(cx).selection_highlight {
6872 return None;
6873 }
6874 if self.selections.count() != 1 || self.selections.line_mode {
6875 return None;
6876 }
6877 let selection = self.selections.newest::<Point>(cx);
6878 if selection.is_empty() || selection.start.row != selection.end.row {
6879 return None;
6880 }
6881 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6882 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6883 let query = multi_buffer_snapshot
6884 .text_for_range(selection_anchor_range.clone())
6885 .collect::<String>();
6886 if query.trim().is_empty() {
6887 return None;
6888 }
6889 Some((query, selection_anchor_range))
6890 }
6891
6892 fn update_selection_occurrence_highlights(
6893 &mut self,
6894 query_text: String,
6895 query_range: Range<Anchor>,
6896 multi_buffer_range_to_query: Range<Point>,
6897 use_debounce: bool,
6898 window: &mut Window,
6899 cx: &mut Context<Editor>,
6900 ) -> Task<()> {
6901 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6902 cx.spawn_in(window, async move |editor, cx| {
6903 if use_debounce {
6904 cx.background_executor()
6905 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6906 .await;
6907 }
6908 let match_task = cx.background_spawn(async move {
6909 let buffer_ranges = multi_buffer_snapshot
6910 .range_to_buffer_ranges(multi_buffer_range_to_query)
6911 .into_iter()
6912 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6913 let mut match_ranges = Vec::new();
6914 let Ok(regex) = project::search::SearchQuery::text(
6915 query_text.clone(),
6916 false,
6917 false,
6918 false,
6919 Default::default(),
6920 Default::default(),
6921 false,
6922 None,
6923 ) else {
6924 return Vec::default();
6925 };
6926 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6927 match_ranges.extend(
6928 regex
6929 .search(buffer_snapshot, Some(search_range.clone()))
6930 .await
6931 .into_iter()
6932 .filter_map(|match_range| {
6933 let match_start = buffer_snapshot
6934 .anchor_after(search_range.start + match_range.start);
6935 let match_end = buffer_snapshot
6936 .anchor_before(search_range.start + match_range.end);
6937 let match_anchor_range = Anchor::range_in_buffer(
6938 excerpt_id,
6939 buffer_snapshot.remote_id(),
6940 match_start..match_end,
6941 );
6942 (match_anchor_range != query_range).then_some(match_anchor_range)
6943 }),
6944 );
6945 }
6946 match_ranges
6947 });
6948 let match_ranges = match_task.await;
6949 editor
6950 .update_in(cx, |editor, _, cx| {
6951 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6952 if !match_ranges.is_empty() {
6953 editor.highlight_background::<SelectedTextHighlight>(
6954 &match_ranges,
6955 |theme| theme.colors().editor_document_highlight_bracket_background,
6956 cx,
6957 )
6958 }
6959 })
6960 .log_err();
6961 })
6962 }
6963
6964 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6965 struct NewlineFold;
6966 let type_id = std::any::TypeId::of::<NewlineFold>();
6967 if !self.mode.is_single_line() {
6968 return;
6969 }
6970 let snapshot = self.snapshot(window, cx);
6971 if snapshot.buffer_snapshot.max_point().row == 0 {
6972 return;
6973 }
6974 let task = cx.background_spawn(async move {
6975 let new_newlines = snapshot
6976 .buffer_chars_at(0)
6977 .filter_map(|(c, i)| {
6978 if c == '\n' {
6979 Some(
6980 snapshot.buffer_snapshot.anchor_after(i)
6981 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6982 )
6983 } else {
6984 None
6985 }
6986 })
6987 .collect::<Vec<_>>();
6988 let existing_newlines = snapshot
6989 .folds_in_range(0..snapshot.buffer_snapshot.len())
6990 .filter_map(|fold| {
6991 if fold.placeholder.type_tag == Some(type_id) {
6992 Some(fold.range.start..fold.range.end)
6993 } else {
6994 None
6995 }
6996 })
6997 .collect::<Vec<_>>();
6998
6999 (new_newlines, existing_newlines)
7000 });
7001 self.folding_newlines = cx.spawn(async move |this, cx| {
7002 let (new_newlines, existing_newlines) = task.await;
7003 if new_newlines == existing_newlines {
7004 return;
7005 }
7006 let placeholder = FoldPlaceholder {
7007 render: Arc::new(move |_, _, cx| {
7008 div()
7009 .bg(cx.theme().status().hint_background)
7010 .border_b_1()
7011 .size_full()
7012 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7013 .border_color(cx.theme().status().hint)
7014 .child("\\n")
7015 .into_any()
7016 }),
7017 constrain_width: false,
7018 merge_adjacent: false,
7019 type_tag: Some(type_id),
7020 };
7021 let creases = new_newlines
7022 .into_iter()
7023 .map(|range| Crease::simple(range, placeholder.clone()))
7024 .collect();
7025 this.update(cx, |this, cx| {
7026 this.display_map.update(cx, |display_map, cx| {
7027 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7028 display_map.fold(creases, cx);
7029 });
7030 })
7031 .ok();
7032 });
7033 }
7034
7035 fn refresh_selected_text_highlights(
7036 &mut self,
7037 on_buffer_edit: bool,
7038 window: &mut Window,
7039 cx: &mut Context<Editor>,
7040 ) {
7041 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7042 else {
7043 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7044 self.quick_selection_highlight_task.take();
7045 self.debounced_selection_highlight_task.take();
7046 return;
7047 };
7048 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7049 if on_buffer_edit
7050 || self
7051 .quick_selection_highlight_task
7052 .as_ref()
7053 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7054 {
7055 let multi_buffer_visible_start = self
7056 .scroll_manager
7057 .anchor()
7058 .anchor
7059 .to_point(&multi_buffer_snapshot);
7060 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7061 multi_buffer_visible_start
7062 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7063 Bias::Left,
7064 );
7065 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7066 self.quick_selection_highlight_task = Some((
7067 query_range.clone(),
7068 self.update_selection_occurrence_highlights(
7069 query_text.clone(),
7070 query_range.clone(),
7071 multi_buffer_visible_range,
7072 false,
7073 window,
7074 cx,
7075 ),
7076 ));
7077 }
7078 if on_buffer_edit
7079 || self
7080 .debounced_selection_highlight_task
7081 .as_ref()
7082 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7083 {
7084 let multi_buffer_start = multi_buffer_snapshot
7085 .anchor_before(0)
7086 .to_point(&multi_buffer_snapshot);
7087 let multi_buffer_end = multi_buffer_snapshot
7088 .anchor_after(multi_buffer_snapshot.len())
7089 .to_point(&multi_buffer_snapshot);
7090 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7091 self.debounced_selection_highlight_task = Some((
7092 query_range.clone(),
7093 self.update_selection_occurrence_highlights(
7094 query_text,
7095 query_range,
7096 multi_buffer_full_range,
7097 true,
7098 window,
7099 cx,
7100 ),
7101 ));
7102 }
7103 }
7104
7105 pub fn refresh_edit_prediction(
7106 &mut self,
7107 debounce: bool,
7108 user_requested: bool,
7109 window: &mut Window,
7110 cx: &mut Context<Self>,
7111 ) -> Option<()> {
7112 if DisableAiSettings::get_global(cx).disable_ai {
7113 return None;
7114 }
7115
7116 let provider = self.edit_prediction_provider()?;
7117 let cursor = self.selections.newest_anchor().head();
7118 let (buffer, cursor_buffer_position) =
7119 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7120
7121 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7122 self.discard_edit_prediction(false, cx);
7123 return None;
7124 }
7125
7126 if !user_requested
7127 && (!self.should_show_edit_predictions()
7128 || !self.is_focused(window)
7129 || buffer.read(cx).is_empty())
7130 {
7131 self.discard_edit_prediction(false, cx);
7132 return None;
7133 }
7134
7135 self.update_visible_edit_prediction(window, cx);
7136 provider.refresh(
7137 self.project.clone(),
7138 buffer,
7139 cursor_buffer_position,
7140 debounce,
7141 cx,
7142 );
7143 Some(())
7144 }
7145
7146 fn show_edit_predictions_in_menu(&self) -> bool {
7147 match self.edit_prediction_settings {
7148 EditPredictionSettings::Disabled => false,
7149 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7150 }
7151 }
7152
7153 pub fn edit_predictions_enabled(&self) -> bool {
7154 match self.edit_prediction_settings {
7155 EditPredictionSettings::Disabled => false,
7156 EditPredictionSettings::Enabled { .. } => true,
7157 }
7158 }
7159
7160 fn edit_prediction_requires_modifier(&self) -> bool {
7161 match self.edit_prediction_settings {
7162 EditPredictionSettings::Disabled => false,
7163 EditPredictionSettings::Enabled {
7164 preview_requires_modifier,
7165 ..
7166 } => preview_requires_modifier,
7167 }
7168 }
7169
7170 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7171 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7172 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7173 self.discard_edit_prediction(false, cx);
7174 } else {
7175 let selection = self.selections.newest_anchor();
7176 let cursor = selection.head();
7177
7178 if let Some((buffer, cursor_buffer_position)) =
7179 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7180 {
7181 self.edit_prediction_settings =
7182 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7183 }
7184 }
7185 }
7186
7187 fn edit_prediction_settings_at_position(
7188 &self,
7189 buffer: &Entity<Buffer>,
7190 buffer_position: language::Anchor,
7191 cx: &App,
7192 ) -> EditPredictionSettings {
7193 if !self.mode.is_full()
7194 || !self.show_edit_predictions_override.unwrap_or(true)
7195 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7196 {
7197 return EditPredictionSettings::Disabled;
7198 }
7199
7200 let buffer = buffer.read(cx);
7201
7202 let file = buffer.file();
7203
7204 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7205 return EditPredictionSettings::Disabled;
7206 };
7207
7208 let by_provider = matches!(
7209 self.menu_edit_predictions_policy,
7210 MenuEditPredictionsPolicy::ByProvider
7211 );
7212
7213 let show_in_menu = by_provider
7214 && self
7215 .edit_prediction_provider
7216 .as_ref()
7217 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7218
7219 let preview_requires_modifier =
7220 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7221
7222 EditPredictionSettings::Enabled {
7223 show_in_menu,
7224 preview_requires_modifier,
7225 }
7226 }
7227
7228 fn should_show_edit_predictions(&self) -> bool {
7229 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7230 }
7231
7232 pub fn edit_prediction_preview_is_active(&self) -> bool {
7233 matches!(
7234 self.edit_prediction_preview,
7235 EditPredictionPreview::Active { .. }
7236 )
7237 }
7238
7239 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7240 let cursor = self.selections.newest_anchor().head();
7241 if let Some((buffer, cursor_position)) =
7242 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7243 {
7244 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7245 } else {
7246 false
7247 }
7248 }
7249
7250 pub fn supports_minimap(&self, cx: &App) -> bool {
7251 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7252 }
7253
7254 fn edit_predictions_enabled_in_buffer(
7255 &self,
7256 buffer: &Entity<Buffer>,
7257 buffer_position: language::Anchor,
7258 cx: &App,
7259 ) -> bool {
7260 maybe!({
7261 if self.read_only(cx) {
7262 return Some(false);
7263 }
7264 let provider = self.edit_prediction_provider()?;
7265 if !provider.is_enabled(buffer, buffer_position, cx) {
7266 return Some(false);
7267 }
7268 let buffer = buffer.read(cx);
7269 let Some(file) = buffer.file() else {
7270 return Some(true);
7271 };
7272 let settings = all_language_settings(Some(file), cx);
7273 Some(settings.edit_predictions_enabled_for_file(file, cx))
7274 })
7275 .unwrap_or(false)
7276 }
7277
7278 fn cycle_edit_prediction(
7279 &mut self,
7280 direction: Direction,
7281 window: &mut Window,
7282 cx: &mut Context<Self>,
7283 ) -> Option<()> {
7284 let provider = self.edit_prediction_provider()?;
7285 let cursor = self.selections.newest_anchor().head();
7286 let (buffer, cursor_buffer_position) =
7287 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7288 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7289 return None;
7290 }
7291
7292 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7293 self.update_visible_edit_prediction(window, cx);
7294
7295 Some(())
7296 }
7297
7298 pub fn show_edit_prediction(
7299 &mut self,
7300 _: &ShowEditPrediction,
7301 window: &mut Window,
7302 cx: &mut Context<Self>,
7303 ) {
7304 if !self.has_active_edit_prediction() {
7305 self.refresh_edit_prediction(false, true, window, cx);
7306 return;
7307 }
7308
7309 self.update_visible_edit_prediction(window, cx);
7310 }
7311
7312 pub fn display_cursor_names(
7313 &mut self,
7314 _: &DisplayCursorNames,
7315 window: &mut Window,
7316 cx: &mut Context<Self>,
7317 ) {
7318 self.show_cursor_names(window, cx);
7319 }
7320
7321 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7322 self.show_cursor_names = true;
7323 cx.notify();
7324 cx.spawn_in(window, async move |this, cx| {
7325 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7326 this.update(cx, |this, cx| {
7327 this.show_cursor_names = false;
7328 cx.notify()
7329 })
7330 .ok()
7331 })
7332 .detach();
7333 }
7334
7335 pub fn next_edit_prediction(
7336 &mut self,
7337 _: &NextEditPrediction,
7338 window: &mut Window,
7339 cx: &mut Context<Self>,
7340 ) {
7341 if self.has_active_edit_prediction() {
7342 self.cycle_edit_prediction(Direction::Next, window, cx);
7343 } else {
7344 let is_copilot_disabled = self
7345 .refresh_edit_prediction(false, true, window, cx)
7346 .is_none();
7347 if is_copilot_disabled {
7348 cx.propagate();
7349 }
7350 }
7351 }
7352
7353 pub fn previous_edit_prediction(
7354 &mut self,
7355 _: &PreviousEditPrediction,
7356 window: &mut Window,
7357 cx: &mut Context<Self>,
7358 ) {
7359 if self.has_active_edit_prediction() {
7360 self.cycle_edit_prediction(Direction::Prev, window, cx);
7361 } else {
7362 let is_copilot_disabled = self
7363 .refresh_edit_prediction(false, true, window, cx)
7364 .is_none();
7365 if is_copilot_disabled {
7366 cx.propagate();
7367 }
7368 }
7369 }
7370
7371 pub fn accept_edit_prediction(
7372 &mut self,
7373 _: &AcceptEditPrediction,
7374 window: &mut Window,
7375 cx: &mut Context<Self>,
7376 ) {
7377 if self.show_edit_predictions_in_menu() {
7378 self.hide_context_menu(window, cx);
7379 }
7380
7381 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7382 return;
7383 };
7384
7385 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7386
7387 match &active_edit_prediction.completion {
7388 EditPrediction::Move { target, .. } => {
7389 let target = *target;
7390
7391 if let Some(position_map) = &self.last_position_map {
7392 if position_map
7393 .visible_row_range
7394 .contains(&target.to_display_point(&position_map.snapshot).row())
7395 || !self.edit_prediction_requires_modifier()
7396 {
7397 self.unfold_ranges(&[target..target], true, false, cx);
7398 // Note that this is also done in vim's handler of the Tab action.
7399 self.change_selections(
7400 SelectionEffects::scroll(Autoscroll::newest()),
7401 window,
7402 cx,
7403 |selections| {
7404 selections.select_anchor_ranges([target..target]);
7405 },
7406 );
7407 self.clear_row_highlights::<EditPredictionPreview>();
7408
7409 self.edit_prediction_preview
7410 .set_previous_scroll_position(None);
7411 } else {
7412 self.edit_prediction_preview
7413 .set_previous_scroll_position(Some(
7414 position_map.snapshot.scroll_anchor,
7415 ));
7416
7417 self.highlight_rows::<EditPredictionPreview>(
7418 target..target,
7419 cx.theme().colors().editor_highlighted_line_background,
7420 RowHighlightOptions {
7421 autoscroll: true,
7422 ..Default::default()
7423 },
7424 cx,
7425 );
7426 self.request_autoscroll(Autoscroll::fit(), cx);
7427 }
7428 }
7429 }
7430 EditPrediction::Edit { edits, .. } => {
7431 if let Some(provider) = self.edit_prediction_provider() {
7432 provider.accept(cx);
7433 }
7434
7435 // Store the transaction ID and selections before applying the edit
7436 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7437
7438 let snapshot = self.buffer.read(cx).snapshot(cx);
7439 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7440
7441 self.buffer.update(cx, |buffer, cx| {
7442 buffer.edit(edits.iter().cloned(), None, cx)
7443 });
7444
7445 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7446 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7447 });
7448
7449 let selections = self.selections.disjoint_anchors();
7450 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7451 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7452 if has_new_transaction {
7453 self.selection_history
7454 .insert_transaction(transaction_id_now, selections);
7455 }
7456 }
7457
7458 self.update_visible_edit_prediction(window, cx);
7459 if self.active_edit_prediction.is_none() {
7460 self.refresh_edit_prediction(true, true, window, cx);
7461 }
7462
7463 cx.notify();
7464 }
7465 }
7466
7467 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7468 }
7469
7470 pub fn accept_partial_edit_prediction(
7471 &mut self,
7472 _: &AcceptPartialEditPrediction,
7473 window: &mut Window,
7474 cx: &mut Context<Self>,
7475 ) {
7476 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7477 return;
7478 };
7479 if self.selections.count() != 1 {
7480 return;
7481 }
7482
7483 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7484
7485 match &active_edit_prediction.completion {
7486 EditPrediction::Move { target, .. } => {
7487 let target = *target;
7488 self.change_selections(
7489 SelectionEffects::scroll(Autoscroll::newest()),
7490 window,
7491 cx,
7492 |selections| {
7493 selections.select_anchor_ranges([target..target]);
7494 },
7495 );
7496 }
7497 EditPrediction::Edit { edits, .. } => {
7498 // Find an insertion that starts at the cursor position.
7499 let snapshot = self.buffer.read(cx).snapshot(cx);
7500 let cursor_offset = self.selections.newest::<usize>(cx).head();
7501 let insertion = edits.iter().find_map(|(range, text)| {
7502 let range = range.to_offset(&snapshot);
7503 if range.is_empty() && range.start == cursor_offset {
7504 Some(text)
7505 } else {
7506 None
7507 }
7508 });
7509
7510 if let Some(text) = insertion {
7511 let mut partial_completion = text
7512 .chars()
7513 .by_ref()
7514 .take_while(|c| c.is_alphabetic())
7515 .collect::<String>();
7516 if partial_completion.is_empty() {
7517 partial_completion = text
7518 .chars()
7519 .by_ref()
7520 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7521 .collect::<String>();
7522 }
7523
7524 cx.emit(EditorEvent::InputHandled {
7525 utf16_range_to_replace: None,
7526 text: partial_completion.clone().into(),
7527 });
7528
7529 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7530
7531 self.refresh_edit_prediction(true, true, window, cx);
7532 cx.notify();
7533 } else {
7534 self.accept_edit_prediction(&Default::default(), window, cx);
7535 }
7536 }
7537 }
7538 }
7539
7540 fn discard_edit_prediction(
7541 &mut self,
7542 should_report_edit_prediction_event: bool,
7543 cx: &mut Context<Self>,
7544 ) -> bool {
7545 if should_report_edit_prediction_event {
7546 let completion_id = self
7547 .active_edit_prediction
7548 .as_ref()
7549 .and_then(|active_completion| active_completion.completion_id.clone());
7550
7551 self.report_edit_prediction_event(completion_id, false, cx);
7552 }
7553
7554 if let Some(provider) = self.edit_prediction_provider() {
7555 provider.discard(cx);
7556 }
7557
7558 self.take_active_edit_prediction(cx)
7559 }
7560
7561 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7562 let Some(provider) = self.edit_prediction_provider() else {
7563 return;
7564 };
7565
7566 let Some((_, buffer, _)) = self
7567 .buffer
7568 .read(cx)
7569 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7570 else {
7571 return;
7572 };
7573
7574 let extension = buffer
7575 .read(cx)
7576 .file()
7577 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7578
7579 let event_type = match accepted {
7580 true => "Edit Prediction Accepted",
7581 false => "Edit Prediction Discarded",
7582 };
7583 telemetry::event!(
7584 event_type,
7585 provider = provider.name(),
7586 prediction_id = id,
7587 suggestion_accepted = accepted,
7588 file_extension = extension,
7589 );
7590 }
7591
7592 pub fn has_active_edit_prediction(&self) -> bool {
7593 self.active_edit_prediction.is_some()
7594 }
7595
7596 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7597 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7598 return false;
7599 };
7600
7601 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7602 self.clear_highlights::<EditPredictionHighlight>(cx);
7603 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7604 true
7605 }
7606
7607 /// Returns true when we're displaying the edit prediction popover below the cursor
7608 /// like we are not previewing and the LSP autocomplete menu is visible
7609 /// or we are in `when_holding_modifier` mode.
7610 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7611 if self.edit_prediction_preview_is_active()
7612 || !self.show_edit_predictions_in_menu()
7613 || !self.edit_predictions_enabled()
7614 {
7615 return false;
7616 }
7617
7618 if self.has_visible_completions_menu() {
7619 return true;
7620 }
7621
7622 has_completion && self.edit_prediction_requires_modifier()
7623 }
7624
7625 fn handle_modifiers_changed(
7626 &mut self,
7627 modifiers: Modifiers,
7628 position_map: &PositionMap,
7629 window: &mut Window,
7630 cx: &mut Context<Self>,
7631 ) {
7632 if self.show_edit_predictions_in_menu() {
7633 self.update_edit_prediction_preview(&modifiers, window, cx);
7634 }
7635
7636 self.update_selection_mode(&modifiers, position_map, window, cx);
7637
7638 let mouse_position = window.mouse_position();
7639 if !position_map.text_hitbox.is_hovered(window) {
7640 return;
7641 }
7642
7643 self.update_hovered_link(
7644 position_map.point_for_position(mouse_position),
7645 &position_map.snapshot,
7646 modifiers,
7647 window,
7648 cx,
7649 )
7650 }
7651
7652 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7653 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7654 if invert {
7655 match multi_cursor_setting {
7656 MultiCursorModifier::Alt => modifiers.alt,
7657 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7658 }
7659 } else {
7660 match multi_cursor_setting {
7661 MultiCursorModifier::Alt => modifiers.secondary(),
7662 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7663 }
7664 }
7665 }
7666
7667 fn columnar_selection_mode(
7668 modifiers: &Modifiers,
7669 cx: &mut Context<Self>,
7670 ) -> Option<ColumnarMode> {
7671 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7672 if Self::multi_cursor_modifier(false, modifiers, cx) {
7673 Some(ColumnarMode::FromMouse)
7674 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7675 Some(ColumnarMode::FromSelection)
7676 } else {
7677 None
7678 }
7679 } else {
7680 None
7681 }
7682 }
7683
7684 fn update_selection_mode(
7685 &mut self,
7686 modifiers: &Modifiers,
7687 position_map: &PositionMap,
7688 window: &mut Window,
7689 cx: &mut Context<Self>,
7690 ) {
7691 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7692 return;
7693 };
7694 if self.selections.pending.is_none() {
7695 return;
7696 }
7697
7698 let mouse_position = window.mouse_position();
7699 let point_for_position = position_map.point_for_position(mouse_position);
7700 let position = point_for_position.previous_valid;
7701
7702 self.select(
7703 SelectPhase::BeginColumnar {
7704 position,
7705 reset: false,
7706 mode,
7707 goal_column: point_for_position.exact_unclipped.column(),
7708 },
7709 window,
7710 cx,
7711 );
7712 }
7713
7714 fn update_edit_prediction_preview(
7715 &mut self,
7716 modifiers: &Modifiers,
7717 window: &mut Window,
7718 cx: &mut Context<Self>,
7719 ) {
7720 let mut modifiers_held = false;
7721 if let Some(accept_keystroke) = self
7722 .accept_edit_prediction_keybind(false, window, cx)
7723 .keystroke()
7724 {
7725 modifiers_held = modifiers_held
7726 || (accept_keystroke.modifiers() == modifiers
7727 && accept_keystroke.modifiers().modified());
7728 };
7729 if let Some(accept_partial_keystroke) = self
7730 .accept_edit_prediction_keybind(true, window, cx)
7731 .keystroke()
7732 {
7733 modifiers_held = modifiers_held
7734 || (accept_partial_keystroke.modifiers() == modifiers
7735 && accept_partial_keystroke.modifiers().modified());
7736 }
7737
7738 if modifiers_held {
7739 if matches!(
7740 self.edit_prediction_preview,
7741 EditPredictionPreview::Inactive { .. }
7742 ) {
7743 self.edit_prediction_preview = EditPredictionPreview::Active {
7744 previous_scroll_position: None,
7745 since: Instant::now(),
7746 };
7747
7748 self.update_visible_edit_prediction(window, cx);
7749 cx.notify();
7750 }
7751 } else if let EditPredictionPreview::Active {
7752 previous_scroll_position,
7753 since,
7754 } = self.edit_prediction_preview
7755 {
7756 if let (Some(previous_scroll_position), Some(position_map)) =
7757 (previous_scroll_position, self.last_position_map.as_ref())
7758 {
7759 self.set_scroll_position(
7760 previous_scroll_position
7761 .scroll_position(&position_map.snapshot.display_snapshot),
7762 window,
7763 cx,
7764 );
7765 }
7766
7767 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7768 released_too_fast: since.elapsed() < Duration::from_millis(200),
7769 };
7770 self.clear_row_highlights::<EditPredictionPreview>();
7771 self.update_visible_edit_prediction(window, cx);
7772 cx.notify();
7773 }
7774 }
7775
7776 fn update_visible_edit_prediction(
7777 &mut self,
7778 _window: &mut Window,
7779 cx: &mut Context<Self>,
7780 ) -> Option<()> {
7781 if DisableAiSettings::get_global(cx).disable_ai {
7782 return None;
7783 }
7784
7785 if self.ime_transaction.is_some() {
7786 self.discard_edit_prediction(false, cx);
7787 return None;
7788 }
7789
7790 let selection = self.selections.newest_anchor();
7791 let cursor = selection.head();
7792 let multibuffer = self.buffer.read(cx).snapshot(cx);
7793 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7794 let excerpt_id = cursor.excerpt_id;
7795
7796 let show_in_menu = self.show_edit_predictions_in_menu();
7797 let completions_menu_has_precedence = !show_in_menu
7798 && (self.context_menu.borrow().is_some()
7799 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7800
7801 if completions_menu_has_precedence
7802 || !offset_selection.is_empty()
7803 || self
7804 .active_edit_prediction
7805 .as_ref()
7806 .is_some_and(|completion| {
7807 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7808 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7809 !invalidation_range.contains(&offset_selection.head())
7810 })
7811 {
7812 self.discard_edit_prediction(false, cx);
7813 return None;
7814 }
7815
7816 self.take_active_edit_prediction(cx);
7817 let Some(provider) = self.edit_prediction_provider() else {
7818 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7819 return None;
7820 };
7821
7822 let (buffer, cursor_buffer_position) =
7823 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7824
7825 self.edit_prediction_settings =
7826 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7827
7828 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7829 self.discard_edit_prediction(false, cx);
7830 return None;
7831 };
7832
7833 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7834
7835 if self.edit_prediction_indent_conflict {
7836 let cursor_point = cursor.to_point(&multibuffer);
7837
7838 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7839
7840 if let Some((_, indent)) = indents.iter().next()
7841 && indent.len == cursor_point.column
7842 {
7843 self.edit_prediction_indent_conflict = false;
7844 }
7845 }
7846
7847 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7848 let edits = edit_prediction
7849 .edits
7850 .into_iter()
7851 .flat_map(|(range, new_text)| {
7852 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7853 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7854 Some((start..end, new_text))
7855 })
7856 .collect::<Vec<_>>();
7857 if edits.is_empty() {
7858 return None;
7859 }
7860
7861 let first_edit_start = edits.first().unwrap().0.start;
7862 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7863 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7864
7865 let last_edit_end = edits.last().unwrap().0.end;
7866 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7867 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7868
7869 let cursor_row = cursor.to_point(&multibuffer).row;
7870
7871 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7872
7873 let mut inlay_ids = Vec::new();
7874 let invalidation_row_range;
7875 let move_invalidation_row_range = if cursor_row < edit_start_row {
7876 Some(cursor_row..edit_end_row)
7877 } else if cursor_row > edit_end_row {
7878 Some(edit_start_row..cursor_row)
7879 } else {
7880 None
7881 };
7882 let supports_jump = self
7883 .edit_prediction_provider
7884 .as_ref()
7885 .map(|provider| provider.provider.supports_jump_to_edit())
7886 .unwrap_or(true);
7887
7888 let is_move = supports_jump
7889 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7890 let completion = if is_move {
7891 invalidation_row_range =
7892 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7893 let target = first_edit_start;
7894 EditPrediction::Move { target, snapshot }
7895 } else {
7896 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7897 && !self.edit_predictions_hidden_for_vim_mode;
7898
7899 if show_completions_in_buffer {
7900 if edits
7901 .iter()
7902 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7903 {
7904 let mut inlays = Vec::new();
7905 for (range, new_text) in &edits {
7906 let inlay = Inlay::edit_prediction(
7907 post_inc(&mut self.next_inlay_id),
7908 range.start,
7909 new_text.as_str(),
7910 );
7911 inlay_ids.push(inlay.id);
7912 inlays.push(inlay);
7913 }
7914
7915 self.splice_inlays(&[], inlays, cx);
7916 } else {
7917 let background_color = cx.theme().status().deleted_background;
7918 self.highlight_text::<EditPredictionHighlight>(
7919 edits.iter().map(|(range, _)| range.clone()).collect(),
7920 HighlightStyle {
7921 background_color: Some(background_color),
7922 ..Default::default()
7923 },
7924 cx,
7925 );
7926 }
7927 }
7928
7929 invalidation_row_range = edit_start_row..edit_end_row;
7930
7931 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7932 if provider.show_tab_accept_marker() {
7933 EditDisplayMode::TabAccept
7934 } else {
7935 EditDisplayMode::Inline
7936 }
7937 } else {
7938 EditDisplayMode::DiffPopover
7939 };
7940
7941 EditPrediction::Edit {
7942 edits,
7943 edit_preview: edit_prediction.edit_preview,
7944 display_mode,
7945 snapshot,
7946 }
7947 };
7948
7949 let invalidation_range = multibuffer
7950 .anchor_before(Point::new(invalidation_row_range.start, 0))
7951 ..multibuffer.anchor_after(Point::new(
7952 invalidation_row_range.end,
7953 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7954 ));
7955
7956 self.stale_edit_prediction_in_menu = None;
7957 self.active_edit_prediction = Some(EditPredictionState {
7958 inlay_ids,
7959 completion,
7960 completion_id: edit_prediction.id,
7961 invalidation_range,
7962 });
7963
7964 cx.notify();
7965
7966 Some(())
7967 }
7968
7969 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7970 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7971 }
7972
7973 fn clear_tasks(&mut self) {
7974 self.tasks.clear()
7975 }
7976
7977 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7978 if self.tasks.insert(key, value).is_some() {
7979 // This case should hopefully be rare, but just in case...
7980 log::error!(
7981 "multiple different run targets found on a single line, only the last target will be rendered"
7982 )
7983 }
7984 }
7985
7986 /// Get all display points of breakpoints that will be rendered within editor
7987 ///
7988 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7989 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7990 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7991 fn active_breakpoints(
7992 &self,
7993 range: Range<DisplayRow>,
7994 window: &mut Window,
7995 cx: &mut Context<Self>,
7996 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7997 let mut breakpoint_display_points = HashMap::default();
7998
7999 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8000 return breakpoint_display_points;
8001 };
8002
8003 let snapshot = self.snapshot(window, cx);
8004
8005 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8006 let Some(project) = self.project() else {
8007 return breakpoint_display_points;
8008 };
8009
8010 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8011 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8012
8013 for (buffer_snapshot, range, excerpt_id) in
8014 multi_buffer_snapshot.range_to_buffer_ranges(range)
8015 {
8016 let Some(buffer) = project
8017 .read(cx)
8018 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8019 else {
8020 continue;
8021 };
8022 let breakpoints = breakpoint_store.read(cx).breakpoints(
8023 &buffer,
8024 Some(
8025 buffer_snapshot.anchor_before(range.start)
8026 ..buffer_snapshot.anchor_after(range.end),
8027 ),
8028 buffer_snapshot,
8029 cx,
8030 );
8031 for (breakpoint, state) in breakpoints {
8032 let multi_buffer_anchor =
8033 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8034 let position = multi_buffer_anchor
8035 .to_point(multi_buffer_snapshot)
8036 .to_display_point(&snapshot);
8037
8038 breakpoint_display_points.insert(
8039 position.row(),
8040 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8041 );
8042 }
8043 }
8044
8045 breakpoint_display_points
8046 }
8047
8048 fn breakpoint_context_menu(
8049 &self,
8050 anchor: Anchor,
8051 window: &mut Window,
8052 cx: &mut Context<Self>,
8053 ) -> Entity<ui::ContextMenu> {
8054 let weak_editor = cx.weak_entity();
8055 let focus_handle = self.focus_handle(cx);
8056
8057 let row = self
8058 .buffer
8059 .read(cx)
8060 .snapshot(cx)
8061 .summary_for_anchor::<Point>(&anchor)
8062 .row;
8063
8064 let breakpoint = self
8065 .breakpoint_at_row(row, window, cx)
8066 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8067
8068 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8069 "Edit Log Breakpoint"
8070 } else {
8071 "Set Log Breakpoint"
8072 };
8073
8074 let condition_breakpoint_msg = if breakpoint
8075 .as_ref()
8076 .is_some_and(|bp| bp.1.condition.is_some())
8077 {
8078 "Edit Condition Breakpoint"
8079 } else {
8080 "Set Condition Breakpoint"
8081 };
8082
8083 let hit_condition_breakpoint_msg = if breakpoint
8084 .as_ref()
8085 .is_some_and(|bp| bp.1.hit_condition.is_some())
8086 {
8087 "Edit Hit Condition Breakpoint"
8088 } else {
8089 "Set Hit Condition Breakpoint"
8090 };
8091
8092 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8093 "Unset Breakpoint"
8094 } else {
8095 "Set Breakpoint"
8096 };
8097
8098 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8099
8100 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8101 BreakpointState::Enabled => Some("Disable"),
8102 BreakpointState::Disabled => Some("Enable"),
8103 });
8104
8105 let (anchor, breakpoint) =
8106 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8107
8108 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8109 menu.on_blur_subscription(Subscription::new(|| {}))
8110 .context(focus_handle)
8111 .when(run_to_cursor, |this| {
8112 let weak_editor = weak_editor.clone();
8113 this.entry("Run to cursor", None, move |window, cx| {
8114 weak_editor
8115 .update(cx, |editor, cx| {
8116 editor.change_selections(
8117 SelectionEffects::no_scroll(),
8118 window,
8119 cx,
8120 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8121 );
8122 })
8123 .ok();
8124
8125 window.dispatch_action(Box::new(RunToCursor), cx);
8126 })
8127 .separator()
8128 })
8129 .when_some(toggle_state_msg, |this, msg| {
8130 this.entry(msg, None, {
8131 let weak_editor = weak_editor.clone();
8132 let breakpoint = breakpoint.clone();
8133 move |_window, cx| {
8134 weak_editor
8135 .update(cx, |this, cx| {
8136 this.edit_breakpoint_at_anchor(
8137 anchor,
8138 breakpoint.as_ref().clone(),
8139 BreakpointEditAction::InvertState,
8140 cx,
8141 );
8142 })
8143 .log_err();
8144 }
8145 })
8146 })
8147 .entry(set_breakpoint_msg, None, {
8148 let weak_editor = weak_editor.clone();
8149 let breakpoint = breakpoint.clone();
8150 move |_window, cx| {
8151 weak_editor
8152 .update(cx, |this, cx| {
8153 this.edit_breakpoint_at_anchor(
8154 anchor,
8155 breakpoint.as_ref().clone(),
8156 BreakpointEditAction::Toggle,
8157 cx,
8158 );
8159 })
8160 .log_err();
8161 }
8162 })
8163 .entry(log_breakpoint_msg, None, {
8164 let breakpoint = breakpoint.clone();
8165 let weak_editor = weak_editor.clone();
8166 move |window, cx| {
8167 weak_editor
8168 .update(cx, |this, cx| {
8169 this.add_edit_breakpoint_block(
8170 anchor,
8171 breakpoint.as_ref(),
8172 BreakpointPromptEditAction::Log,
8173 window,
8174 cx,
8175 );
8176 })
8177 .log_err();
8178 }
8179 })
8180 .entry(condition_breakpoint_msg, None, {
8181 let breakpoint = breakpoint.clone();
8182 let weak_editor = weak_editor.clone();
8183 move |window, cx| {
8184 weak_editor
8185 .update(cx, |this, cx| {
8186 this.add_edit_breakpoint_block(
8187 anchor,
8188 breakpoint.as_ref(),
8189 BreakpointPromptEditAction::Condition,
8190 window,
8191 cx,
8192 );
8193 })
8194 .log_err();
8195 }
8196 })
8197 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8198 weak_editor
8199 .update(cx, |this, cx| {
8200 this.add_edit_breakpoint_block(
8201 anchor,
8202 breakpoint.as_ref(),
8203 BreakpointPromptEditAction::HitCondition,
8204 window,
8205 cx,
8206 );
8207 })
8208 .log_err();
8209 })
8210 })
8211 }
8212
8213 fn render_breakpoint(
8214 &self,
8215 position: Anchor,
8216 row: DisplayRow,
8217 breakpoint: &Breakpoint,
8218 state: Option<BreakpointSessionState>,
8219 cx: &mut Context<Self>,
8220 ) -> IconButton {
8221 let is_rejected = state.is_some_and(|s| !s.verified);
8222 // Is it a breakpoint that shows up when hovering over gutter?
8223 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8224 (false, false),
8225 |PhantomBreakpointIndicator {
8226 is_active,
8227 display_row,
8228 collides_with_existing_breakpoint,
8229 }| {
8230 (
8231 is_active && display_row == row,
8232 collides_with_existing_breakpoint,
8233 )
8234 },
8235 );
8236
8237 let (color, icon) = {
8238 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8239 (false, false) => ui::IconName::DebugBreakpoint,
8240 (true, false) => ui::IconName::DebugLogBreakpoint,
8241 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8242 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8243 };
8244
8245 let color = if is_phantom {
8246 Color::Hint
8247 } else if is_rejected {
8248 Color::Disabled
8249 } else {
8250 Color::Debugger
8251 };
8252
8253 (color, icon)
8254 };
8255
8256 let breakpoint = Arc::from(breakpoint.clone());
8257
8258 let alt_as_text = gpui::Keystroke {
8259 modifiers: Modifiers::secondary_key(),
8260 ..Default::default()
8261 };
8262 let primary_action_text = if breakpoint.is_disabled() {
8263 "Enable breakpoint"
8264 } else if is_phantom && !collides_with_existing {
8265 "Set breakpoint"
8266 } else {
8267 "Unset breakpoint"
8268 };
8269 let focus_handle = self.focus_handle.clone();
8270
8271 let meta = if is_rejected {
8272 SharedString::from("No executable code is associated with this line.")
8273 } else if collides_with_existing && !breakpoint.is_disabled() {
8274 SharedString::from(format!(
8275 "{alt_as_text}-click to disable,\nright-click for more options."
8276 ))
8277 } else {
8278 SharedString::from("Right-click for more options.")
8279 };
8280 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8281 .icon_size(IconSize::XSmall)
8282 .size(ui::ButtonSize::None)
8283 .when(is_rejected, |this| {
8284 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8285 })
8286 .icon_color(color)
8287 .style(ButtonStyle::Transparent)
8288 .on_click(cx.listener({
8289 move |editor, event: &ClickEvent, window, cx| {
8290 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8291 BreakpointEditAction::InvertState
8292 } else {
8293 BreakpointEditAction::Toggle
8294 };
8295
8296 window.focus(&editor.focus_handle(cx));
8297 editor.edit_breakpoint_at_anchor(
8298 position,
8299 breakpoint.as_ref().clone(),
8300 edit_action,
8301 cx,
8302 );
8303 }
8304 }))
8305 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8306 editor.set_breakpoint_context_menu(
8307 row,
8308 Some(position),
8309 event.position(),
8310 window,
8311 cx,
8312 );
8313 }))
8314 .tooltip(move |window, cx| {
8315 Tooltip::with_meta_in(
8316 primary_action_text,
8317 Some(&ToggleBreakpoint),
8318 meta.clone(),
8319 &focus_handle,
8320 window,
8321 cx,
8322 )
8323 })
8324 }
8325
8326 fn build_tasks_context(
8327 project: &Entity<Project>,
8328 buffer: &Entity<Buffer>,
8329 buffer_row: u32,
8330 tasks: &Arc<RunnableTasks>,
8331 cx: &mut Context<Self>,
8332 ) -> Task<Option<task::TaskContext>> {
8333 let position = Point::new(buffer_row, tasks.column);
8334 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8335 let location = Location {
8336 buffer: buffer.clone(),
8337 range: range_start..range_start,
8338 };
8339 // Fill in the environmental variables from the tree-sitter captures
8340 let mut captured_task_variables = TaskVariables::default();
8341 for (capture_name, value) in tasks.extra_variables.clone() {
8342 captured_task_variables.insert(
8343 task::VariableName::Custom(capture_name.into()),
8344 value.clone(),
8345 );
8346 }
8347 project.update(cx, |project, cx| {
8348 project.task_store().update(cx, |task_store, cx| {
8349 task_store.task_context_for_location(captured_task_variables, location, cx)
8350 })
8351 })
8352 }
8353
8354 pub fn spawn_nearest_task(
8355 &mut self,
8356 action: &SpawnNearestTask,
8357 window: &mut Window,
8358 cx: &mut Context<Self>,
8359 ) {
8360 let Some((workspace, _)) = self.workspace.clone() else {
8361 return;
8362 };
8363 let Some(project) = self.project.clone() else {
8364 return;
8365 };
8366
8367 // Try to find a closest, enclosing node using tree-sitter that has a task
8368 let Some((buffer, buffer_row, tasks)) = self
8369 .find_enclosing_node_task(cx)
8370 // Or find the task that's closest in row-distance.
8371 .or_else(|| self.find_closest_task(cx))
8372 else {
8373 return;
8374 };
8375
8376 let reveal_strategy = action.reveal;
8377 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8378 cx.spawn_in(window, async move |_, cx| {
8379 let context = task_context.await?;
8380 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8381
8382 let resolved = &mut resolved_task.resolved;
8383 resolved.reveal = reveal_strategy;
8384
8385 workspace
8386 .update_in(cx, |workspace, window, cx| {
8387 workspace.schedule_resolved_task(
8388 task_source_kind,
8389 resolved_task,
8390 false,
8391 window,
8392 cx,
8393 );
8394 })
8395 .ok()
8396 })
8397 .detach();
8398 }
8399
8400 fn find_closest_task(
8401 &mut self,
8402 cx: &mut Context<Self>,
8403 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8404 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8405
8406 let ((buffer_id, row), tasks) = self
8407 .tasks
8408 .iter()
8409 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8410
8411 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8412 let tasks = Arc::new(tasks.to_owned());
8413 Some((buffer, *row, tasks))
8414 }
8415
8416 fn find_enclosing_node_task(
8417 &mut self,
8418 cx: &mut Context<Self>,
8419 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8420 let snapshot = self.buffer.read(cx).snapshot(cx);
8421 let offset = self.selections.newest::<usize>(cx).head();
8422 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8423 let buffer_id = excerpt.buffer().remote_id();
8424
8425 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8426 let mut cursor = layer.node().walk();
8427
8428 while cursor.goto_first_child_for_byte(offset).is_some() {
8429 if cursor.node().end_byte() == offset {
8430 cursor.goto_next_sibling();
8431 }
8432 }
8433
8434 // Ascend to the smallest ancestor that contains the range and has a task.
8435 loop {
8436 let node = cursor.node();
8437 let node_range = node.byte_range();
8438 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8439
8440 // Check if this node contains our offset
8441 if node_range.start <= offset && node_range.end >= offset {
8442 // If it contains offset, check for task
8443 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8444 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8445 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8446 }
8447 }
8448
8449 if !cursor.goto_parent() {
8450 break;
8451 }
8452 }
8453 None
8454 }
8455
8456 fn render_run_indicator(
8457 &self,
8458 _style: &EditorStyle,
8459 is_active: bool,
8460 row: DisplayRow,
8461 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8462 cx: &mut Context<Self>,
8463 ) -> IconButton {
8464 let color = Color::Muted;
8465 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8466
8467 IconButton::new(
8468 ("run_indicator", row.0 as usize),
8469 ui::IconName::PlayOutlined,
8470 )
8471 .shape(ui::IconButtonShape::Square)
8472 .icon_size(IconSize::XSmall)
8473 .icon_color(color)
8474 .toggle_state(is_active)
8475 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8476 let quick_launch = match e {
8477 ClickEvent::Keyboard(_) => true,
8478 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8479 };
8480
8481 window.focus(&editor.focus_handle(cx));
8482 editor.toggle_code_actions(
8483 &ToggleCodeActions {
8484 deployed_from: Some(CodeActionSource::RunMenu(row)),
8485 quick_launch,
8486 },
8487 window,
8488 cx,
8489 );
8490 }))
8491 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8492 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8493 }))
8494 }
8495
8496 pub fn context_menu_visible(&self) -> bool {
8497 !self.edit_prediction_preview_is_active()
8498 && self
8499 .context_menu
8500 .borrow()
8501 .as_ref()
8502 .is_some_and(|menu| menu.visible())
8503 }
8504
8505 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8506 self.context_menu
8507 .borrow()
8508 .as_ref()
8509 .map(|menu| menu.origin())
8510 }
8511
8512 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8513 self.context_menu_options = Some(options);
8514 }
8515
8516 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8517 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8518
8519 fn render_edit_prediction_popover(
8520 &mut self,
8521 text_bounds: &Bounds<Pixels>,
8522 content_origin: gpui::Point<Pixels>,
8523 right_margin: Pixels,
8524 editor_snapshot: &EditorSnapshot,
8525 visible_row_range: Range<DisplayRow>,
8526 scroll_top: f32,
8527 scroll_bottom: f32,
8528 line_layouts: &[LineWithInvisibles],
8529 line_height: Pixels,
8530 scroll_pixel_position: gpui::Point<Pixels>,
8531 newest_selection_head: Option<DisplayPoint>,
8532 editor_width: Pixels,
8533 style: &EditorStyle,
8534 window: &mut Window,
8535 cx: &mut App,
8536 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8537 if self.mode().is_minimap() {
8538 return None;
8539 }
8540 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8541
8542 if self.edit_prediction_visible_in_cursor_popover(true) {
8543 return None;
8544 }
8545
8546 match &active_edit_prediction.completion {
8547 EditPrediction::Move { target, .. } => {
8548 let target_display_point = target.to_display_point(editor_snapshot);
8549
8550 if self.edit_prediction_requires_modifier() {
8551 if !self.edit_prediction_preview_is_active() {
8552 return None;
8553 }
8554
8555 self.render_edit_prediction_modifier_jump_popover(
8556 text_bounds,
8557 content_origin,
8558 visible_row_range,
8559 line_layouts,
8560 line_height,
8561 scroll_pixel_position,
8562 newest_selection_head,
8563 target_display_point,
8564 window,
8565 cx,
8566 )
8567 } else {
8568 self.render_edit_prediction_eager_jump_popover(
8569 text_bounds,
8570 content_origin,
8571 editor_snapshot,
8572 visible_row_range,
8573 scroll_top,
8574 scroll_bottom,
8575 line_height,
8576 scroll_pixel_position,
8577 target_display_point,
8578 editor_width,
8579 window,
8580 cx,
8581 )
8582 }
8583 }
8584 EditPrediction::Edit {
8585 display_mode: EditDisplayMode::Inline,
8586 ..
8587 } => None,
8588 EditPrediction::Edit {
8589 display_mode: EditDisplayMode::TabAccept,
8590 edits,
8591 ..
8592 } => {
8593 let range = &edits.first()?.0;
8594 let target_display_point = range.end.to_display_point(editor_snapshot);
8595
8596 self.render_edit_prediction_end_of_line_popover(
8597 "Accept",
8598 editor_snapshot,
8599 visible_row_range,
8600 target_display_point,
8601 line_height,
8602 scroll_pixel_position,
8603 content_origin,
8604 editor_width,
8605 window,
8606 cx,
8607 )
8608 }
8609 EditPrediction::Edit {
8610 edits,
8611 edit_preview,
8612 display_mode: EditDisplayMode::DiffPopover,
8613 snapshot,
8614 } => self.render_edit_prediction_diff_popover(
8615 text_bounds,
8616 content_origin,
8617 right_margin,
8618 editor_snapshot,
8619 visible_row_range,
8620 line_layouts,
8621 line_height,
8622 scroll_pixel_position,
8623 newest_selection_head,
8624 editor_width,
8625 style,
8626 edits,
8627 edit_preview,
8628 snapshot,
8629 window,
8630 cx,
8631 ),
8632 }
8633 }
8634
8635 fn render_edit_prediction_modifier_jump_popover(
8636 &mut self,
8637 text_bounds: &Bounds<Pixels>,
8638 content_origin: gpui::Point<Pixels>,
8639 visible_row_range: Range<DisplayRow>,
8640 line_layouts: &[LineWithInvisibles],
8641 line_height: Pixels,
8642 scroll_pixel_position: gpui::Point<Pixels>,
8643 newest_selection_head: Option<DisplayPoint>,
8644 target_display_point: DisplayPoint,
8645 window: &mut Window,
8646 cx: &mut App,
8647 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8648 let scrolled_content_origin =
8649 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8650
8651 const SCROLL_PADDING_Y: Pixels = px(12.);
8652
8653 if target_display_point.row() < visible_row_range.start {
8654 return self.render_edit_prediction_scroll_popover(
8655 |_| SCROLL_PADDING_Y,
8656 IconName::ArrowUp,
8657 visible_row_range,
8658 line_layouts,
8659 newest_selection_head,
8660 scrolled_content_origin,
8661 window,
8662 cx,
8663 );
8664 } else if target_display_point.row() >= visible_row_range.end {
8665 return self.render_edit_prediction_scroll_popover(
8666 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8667 IconName::ArrowDown,
8668 visible_row_range,
8669 line_layouts,
8670 newest_selection_head,
8671 scrolled_content_origin,
8672 window,
8673 cx,
8674 );
8675 }
8676
8677 const POLE_WIDTH: Pixels = px(2.);
8678
8679 let line_layout =
8680 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8681 let target_column = target_display_point.column() as usize;
8682
8683 let target_x = line_layout.x_for_index(target_column);
8684 let target_y =
8685 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8686
8687 let flag_on_right = target_x < text_bounds.size.width / 2.;
8688
8689 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8690 border_color.l += 0.001;
8691
8692 let mut element = v_flex()
8693 .items_end()
8694 .when(flag_on_right, |el| el.items_start())
8695 .child(if flag_on_right {
8696 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8697 .rounded_bl(px(0.))
8698 .rounded_tl(px(0.))
8699 .border_l_2()
8700 .border_color(border_color)
8701 } else {
8702 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8703 .rounded_br(px(0.))
8704 .rounded_tr(px(0.))
8705 .border_r_2()
8706 .border_color(border_color)
8707 })
8708 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8709 .into_any();
8710
8711 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8712
8713 let mut origin = scrolled_content_origin + point(target_x, target_y)
8714 - point(
8715 if flag_on_right {
8716 POLE_WIDTH
8717 } else {
8718 size.width - POLE_WIDTH
8719 },
8720 size.height - line_height,
8721 );
8722
8723 origin.x = origin.x.max(content_origin.x);
8724
8725 element.prepaint_at(origin, window, cx);
8726
8727 Some((element, origin))
8728 }
8729
8730 fn render_edit_prediction_scroll_popover(
8731 &mut self,
8732 to_y: impl Fn(Size<Pixels>) -> Pixels,
8733 scroll_icon: IconName,
8734 visible_row_range: Range<DisplayRow>,
8735 line_layouts: &[LineWithInvisibles],
8736 newest_selection_head: Option<DisplayPoint>,
8737 scrolled_content_origin: gpui::Point<Pixels>,
8738 window: &mut Window,
8739 cx: &mut App,
8740 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8741 let mut element = self
8742 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8743 .into_any();
8744
8745 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8746
8747 let cursor = newest_selection_head?;
8748 let cursor_row_layout =
8749 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8750 let cursor_column = cursor.column() as usize;
8751
8752 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8753
8754 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8755
8756 element.prepaint_at(origin, window, cx);
8757 Some((element, origin))
8758 }
8759
8760 fn render_edit_prediction_eager_jump_popover(
8761 &mut self,
8762 text_bounds: &Bounds<Pixels>,
8763 content_origin: gpui::Point<Pixels>,
8764 editor_snapshot: &EditorSnapshot,
8765 visible_row_range: Range<DisplayRow>,
8766 scroll_top: f32,
8767 scroll_bottom: f32,
8768 line_height: Pixels,
8769 scroll_pixel_position: gpui::Point<Pixels>,
8770 target_display_point: DisplayPoint,
8771 editor_width: Pixels,
8772 window: &mut Window,
8773 cx: &mut App,
8774 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8775 if target_display_point.row().as_f32() < scroll_top {
8776 let mut element = self
8777 .render_edit_prediction_line_popover(
8778 "Jump to Edit",
8779 Some(IconName::ArrowUp),
8780 window,
8781 cx,
8782 )?
8783 .into_any();
8784
8785 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8786 let offset = point(
8787 (text_bounds.size.width - size.width) / 2.,
8788 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8789 );
8790
8791 let origin = text_bounds.origin + offset;
8792 element.prepaint_at(origin, window, cx);
8793 Some((element, origin))
8794 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8795 let mut element = self
8796 .render_edit_prediction_line_popover(
8797 "Jump to Edit",
8798 Some(IconName::ArrowDown),
8799 window,
8800 cx,
8801 )?
8802 .into_any();
8803
8804 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8805 let offset = point(
8806 (text_bounds.size.width - size.width) / 2.,
8807 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8808 );
8809
8810 let origin = text_bounds.origin + offset;
8811 element.prepaint_at(origin, window, cx);
8812 Some((element, origin))
8813 } else {
8814 self.render_edit_prediction_end_of_line_popover(
8815 "Jump to Edit",
8816 editor_snapshot,
8817 visible_row_range,
8818 target_display_point,
8819 line_height,
8820 scroll_pixel_position,
8821 content_origin,
8822 editor_width,
8823 window,
8824 cx,
8825 )
8826 }
8827 }
8828
8829 fn render_edit_prediction_end_of_line_popover(
8830 self: &mut Editor,
8831 label: &'static str,
8832 editor_snapshot: &EditorSnapshot,
8833 visible_row_range: Range<DisplayRow>,
8834 target_display_point: DisplayPoint,
8835 line_height: Pixels,
8836 scroll_pixel_position: gpui::Point<Pixels>,
8837 content_origin: gpui::Point<Pixels>,
8838 editor_width: Pixels,
8839 window: &mut Window,
8840 cx: &mut App,
8841 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8842 let target_line_end = DisplayPoint::new(
8843 target_display_point.row(),
8844 editor_snapshot.line_len(target_display_point.row()),
8845 );
8846
8847 let mut element = self
8848 .render_edit_prediction_line_popover(label, None, window, cx)?
8849 .into_any();
8850
8851 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8852
8853 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8854
8855 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8856 let mut origin = start_point
8857 + line_origin
8858 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8859 origin.x = origin.x.max(content_origin.x);
8860
8861 let max_x = content_origin.x + editor_width - size.width;
8862
8863 if origin.x > max_x {
8864 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8865
8866 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8867 origin.y += offset;
8868 IconName::ArrowUp
8869 } else {
8870 origin.y -= offset;
8871 IconName::ArrowDown
8872 };
8873
8874 element = self
8875 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8876 .into_any();
8877
8878 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8879
8880 origin.x = content_origin.x + editor_width - size.width - px(2.);
8881 }
8882
8883 element.prepaint_at(origin, window, cx);
8884 Some((element, origin))
8885 }
8886
8887 fn render_edit_prediction_diff_popover(
8888 self: &Editor,
8889 text_bounds: &Bounds<Pixels>,
8890 content_origin: gpui::Point<Pixels>,
8891 right_margin: Pixels,
8892 editor_snapshot: &EditorSnapshot,
8893 visible_row_range: Range<DisplayRow>,
8894 line_layouts: &[LineWithInvisibles],
8895 line_height: Pixels,
8896 scroll_pixel_position: gpui::Point<Pixels>,
8897 newest_selection_head: Option<DisplayPoint>,
8898 editor_width: Pixels,
8899 style: &EditorStyle,
8900 edits: &Vec<(Range<Anchor>, String)>,
8901 edit_preview: &Option<language::EditPreview>,
8902 snapshot: &language::BufferSnapshot,
8903 window: &mut Window,
8904 cx: &mut App,
8905 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8906 let edit_start = edits
8907 .first()
8908 .unwrap()
8909 .0
8910 .start
8911 .to_display_point(editor_snapshot);
8912 let edit_end = edits
8913 .last()
8914 .unwrap()
8915 .0
8916 .end
8917 .to_display_point(editor_snapshot);
8918
8919 let is_visible = visible_row_range.contains(&edit_start.row())
8920 || visible_row_range.contains(&edit_end.row());
8921 if !is_visible {
8922 return None;
8923 }
8924
8925 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8926 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8927 } else {
8928 // Fallback for providers without edit_preview
8929 crate::edit_prediction_fallback_text(edits, cx)
8930 };
8931
8932 let styled_text = highlighted_edits.to_styled_text(&style.text);
8933 let line_count = highlighted_edits.text.lines().count();
8934
8935 const BORDER_WIDTH: Pixels = px(1.);
8936
8937 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8938 let has_keybind = keybind.is_some();
8939
8940 let mut element = h_flex()
8941 .items_start()
8942 .child(
8943 h_flex()
8944 .bg(cx.theme().colors().editor_background)
8945 .border(BORDER_WIDTH)
8946 .shadow_xs()
8947 .border_color(cx.theme().colors().border)
8948 .rounded_l_lg()
8949 .when(line_count > 1, |el| el.rounded_br_lg())
8950 .pr_1()
8951 .child(styled_text),
8952 )
8953 .child(
8954 h_flex()
8955 .h(line_height + BORDER_WIDTH * 2.)
8956 .px_1p5()
8957 .gap_1()
8958 // Workaround: For some reason, there's a gap if we don't do this
8959 .ml(-BORDER_WIDTH)
8960 .shadow(vec![gpui::BoxShadow {
8961 color: gpui::black().opacity(0.05),
8962 offset: point(px(1.), px(1.)),
8963 blur_radius: px(2.),
8964 spread_radius: px(0.),
8965 }])
8966 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8967 .border(BORDER_WIDTH)
8968 .border_color(cx.theme().colors().border)
8969 .rounded_r_lg()
8970 .id("edit_prediction_diff_popover_keybind")
8971 .when(!has_keybind, |el| {
8972 let status_colors = cx.theme().status();
8973
8974 el.bg(status_colors.error_background)
8975 .border_color(status_colors.error.opacity(0.6))
8976 .child(Icon::new(IconName::Info).color(Color::Error))
8977 .cursor_default()
8978 .hoverable_tooltip(move |_window, cx| {
8979 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8980 })
8981 })
8982 .children(keybind),
8983 )
8984 .into_any();
8985
8986 let longest_row =
8987 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8988 let longest_line_width = if visible_row_range.contains(&longest_row) {
8989 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8990 } else {
8991 layout_line(
8992 longest_row,
8993 editor_snapshot,
8994 style,
8995 editor_width,
8996 |_| false,
8997 window,
8998 cx,
8999 )
9000 .width
9001 };
9002
9003 let viewport_bounds =
9004 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9005 right: -right_margin,
9006 ..Default::default()
9007 });
9008
9009 let x_after_longest =
9010 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9011 - scroll_pixel_position.x;
9012
9013 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9014
9015 // Fully visible if it can be displayed within the window (allow overlapping other
9016 // panes). However, this is only allowed if the popover starts within text_bounds.
9017 let can_position_to_the_right = x_after_longest < text_bounds.right()
9018 && x_after_longest + element_bounds.width < viewport_bounds.right();
9019
9020 let mut origin = if can_position_to_the_right {
9021 point(
9022 x_after_longest,
9023 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9024 - scroll_pixel_position.y,
9025 )
9026 } else {
9027 let cursor_row = newest_selection_head.map(|head| head.row());
9028 let above_edit = edit_start
9029 .row()
9030 .0
9031 .checked_sub(line_count as u32)
9032 .map(DisplayRow);
9033 let below_edit = Some(edit_end.row() + 1);
9034 let above_cursor =
9035 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9036 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9037
9038 // Place the edit popover adjacent to the edit if there is a location
9039 // available that is onscreen and does not obscure the cursor. Otherwise,
9040 // place it adjacent to the cursor.
9041 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9042 .into_iter()
9043 .flatten()
9044 .find(|&start_row| {
9045 let end_row = start_row + line_count as u32;
9046 visible_row_range.contains(&start_row)
9047 && visible_row_range.contains(&end_row)
9048 && cursor_row
9049 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9050 })?;
9051
9052 content_origin
9053 + point(
9054 -scroll_pixel_position.x,
9055 row_target.as_f32() * line_height - scroll_pixel_position.y,
9056 )
9057 };
9058
9059 origin.x -= BORDER_WIDTH;
9060
9061 window.defer_draw(element, origin, 1);
9062
9063 // Do not return an element, since it will already be drawn due to defer_draw.
9064 None
9065 }
9066
9067 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9068 px(30.)
9069 }
9070
9071 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9072 if self.read_only(cx) {
9073 cx.theme().players().read_only()
9074 } else {
9075 self.style.as_ref().unwrap().local_player
9076 }
9077 }
9078
9079 fn render_edit_prediction_accept_keybind(
9080 &self,
9081 window: &mut Window,
9082 cx: &App,
9083 ) -> Option<AnyElement> {
9084 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9085 let accept_keystroke = accept_binding.keystroke()?;
9086
9087 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9088
9089 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9090 Color::Accent
9091 } else {
9092 Color::Muted
9093 };
9094
9095 h_flex()
9096 .px_0p5()
9097 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9098 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9099 .text_size(TextSize::XSmall.rems(cx))
9100 .child(h_flex().children(ui::render_modifiers(
9101 accept_keystroke.modifiers(),
9102 PlatformStyle::platform(),
9103 Some(modifiers_color),
9104 Some(IconSize::XSmall.rems().into()),
9105 true,
9106 )))
9107 .when(is_platform_style_mac, |parent| {
9108 parent.child(accept_keystroke.key().to_string())
9109 })
9110 .when(!is_platform_style_mac, |parent| {
9111 parent.child(
9112 Key::new(
9113 util::capitalize(accept_keystroke.key()),
9114 Some(Color::Default),
9115 )
9116 .size(Some(IconSize::XSmall.rems().into())),
9117 )
9118 })
9119 .into_any()
9120 .into()
9121 }
9122
9123 fn render_edit_prediction_line_popover(
9124 &self,
9125 label: impl Into<SharedString>,
9126 icon: Option<IconName>,
9127 window: &mut Window,
9128 cx: &App,
9129 ) -> Option<Stateful<Div>> {
9130 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9131
9132 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9133 let has_keybind = keybind.is_some();
9134
9135 let result = h_flex()
9136 .id("ep-line-popover")
9137 .py_0p5()
9138 .pl_1()
9139 .pr(padding_right)
9140 .gap_1()
9141 .rounded_md()
9142 .border_1()
9143 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9144 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9145 .shadow_xs()
9146 .when(!has_keybind, |el| {
9147 let status_colors = cx.theme().status();
9148
9149 el.bg(status_colors.error_background)
9150 .border_color(status_colors.error.opacity(0.6))
9151 .pl_2()
9152 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9153 .cursor_default()
9154 .hoverable_tooltip(move |_window, cx| {
9155 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9156 })
9157 })
9158 .children(keybind)
9159 .child(
9160 Label::new(label)
9161 .size(LabelSize::Small)
9162 .when(!has_keybind, |el| {
9163 el.color(cx.theme().status().error.into()).strikethrough()
9164 }),
9165 )
9166 .when(!has_keybind, |el| {
9167 el.child(
9168 h_flex().ml_1().child(
9169 Icon::new(IconName::Info)
9170 .size(IconSize::Small)
9171 .color(cx.theme().status().error.into()),
9172 ),
9173 )
9174 })
9175 .when_some(icon, |element, icon| {
9176 element.child(
9177 div()
9178 .mt(px(1.5))
9179 .child(Icon::new(icon).size(IconSize::Small)),
9180 )
9181 });
9182
9183 Some(result)
9184 }
9185
9186 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9187 let accent_color = cx.theme().colors().text_accent;
9188 let editor_bg_color = cx.theme().colors().editor_background;
9189 editor_bg_color.blend(accent_color.opacity(0.1))
9190 }
9191
9192 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9193 let accent_color = cx.theme().colors().text_accent;
9194 let editor_bg_color = cx.theme().colors().editor_background;
9195 editor_bg_color.blend(accent_color.opacity(0.6))
9196 }
9197 fn get_prediction_provider_icon_name(
9198 provider: &Option<RegisteredEditPredictionProvider>,
9199 ) -> IconName {
9200 match provider {
9201 Some(provider) => match provider.provider.name() {
9202 "copilot" => IconName::Copilot,
9203 "supermaven" => IconName::Supermaven,
9204 _ => IconName::ZedPredict,
9205 },
9206 None => IconName::ZedPredict,
9207 }
9208 }
9209
9210 fn render_edit_prediction_cursor_popover(
9211 &self,
9212 min_width: Pixels,
9213 max_width: Pixels,
9214 cursor_point: Point,
9215 style: &EditorStyle,
9216 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9217 _window: &Window,
9218 cx: &mut Context<Editor>,
9219 ) -> Option<AnyElement> {
9220 let provider = self.edit_prediction_provider.as_ref()?;
9221 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9222
9223 let is_refreshing = provider.provider.is_refreshing(cx);
9224
9225 fn pending_completion_container(icon: IconName) -> Div {
9226 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9227 }
9228
9229 let completion = match &self.active_edit_prediction {
9230 Some(prediction) => {
9231 if !self.has_visible_completions_menu() {
9232 const RADIUS: Pixels = px(6.);
9233 const BORDER_WIDTH: Pixels = px(1.);
9234
9235 return Some(
9236 h_flex()
9237 .elevation_2(cx)
9238 .border(BORDER_WIDTH)
9239 .border_color(cx.theme().colors().border)
9240 .when(accept_keystroke.is_none(), |el| {
9241 el.border_color(cx.theme().status().error)
9242 })
9243 .rounded(RADIUS)
9244 .rounded_tl(px(0.))
9245 .overflow_hidden()
9246 .child(div().px_1p5().child(match &prediction.completion {
9247 EditPrediction::Move { target, snapshot } => {
9248 use text::ToPoint as _;
9249 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9250 {
9251 Icon::new(IconName::ZedPredictDown)
9252 } else {
9253 Icon::new(IconName::ZedPredictUp)
9254 }
9255 }
9256 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9257 }))
9258 .child(
9259 h_flex()
9260 .gap_1()
9261 .py_1()
9262 .px_2()
9263 .rounded_r(RADIUS - BORDER_WIDTH)
9264 .border_l_1()
9265 .border_color(cx.theme().colors().border)
9266 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9267 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9268 el.child(
9269 Label::new("Hold")
9270 .size(LabelSize::Small)
9271 .when(accept_keystroke.is_none(), |el| {
9272 el.strikethrough()
9273 })
9274 .line_height_style(LineHeightStyle::UiLabel),
9275 )
9276 })
9277 .id("edit_prediction_cursor_popover_keybind")
9278 .when(accept_keystroke.is_none(), |el| {
9279 let status_colors = cx.theme().status();
9280
9281 el.bg(status_colors.error_background)
9282 .border_color(status_colors.error.opacity(0.6))
9283 .child(Icon::new(IconName::Info).color(Color::Error))
9284 .cursor_default()
9285 .hoverable_tooltip(move |_window, cx| {
9286 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9287 .into()
9288 })
9289 })
9290 .when_some(
9291 accept_keystroke.as_ref(),
9292 |el, accept_keystroke| {
9293 el.child(h_flex().children(ui::render_modifiers(
9294 accept_keystroke.modifiers(),
9295 PlatformStyle::platform(),
9296 Some(Color::Default),
9297 Some(IconSize::XSmall.rems().into()),
9298 false,
9299 )))
9300 },
9301 ),
9302 )
9303 .into_any(),
9304 );
9305 }
9306
9307 self.render_edit_prediction_cursor_popover_preview(
9308 prediction,
9309 cursor_point,
9310 style,
9311 cx,
9312 )?
9313 }
9314
9315 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9316 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9317 stale_completion,
9318 cursor_point,
9319 style,
9320 cx,
9321 )?,
9322
9323 None => pending_completion_container(provider_icon)
9324 .child(Label::new("...").size(LabelSize::Small)),
9325 },
9326
9327 None => pending_completion_container(provider_icon)
9328 .child(Label::new("...").size(LabelSize::Small)),
9329 };
9330
9331 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9332 completion
9333 .with_animation(
9334 "loading-completion",
9335 Animation::new(Duration::from_secs(2))
9336 .repeat()
9337 .with_easing(pulsating_between(0.4, 0.8)),
9338 |label, delta| label.opacity(delta),
9339 )
9340 .into_any_element()
9341 } else {
9342 completion.into_any_element()
9343 };
9344
9345 let has_completion = self.active_edit_prediction.is_some();
9346
9347 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9348 Some(
9349 h_flex()
9350 .min_w(min_width)
9351 .max_w(max_width)
9352 .flex_1()
9353 .elevation_2(cx)
9354 .border_color(cx.theme().colors().border)
9355 .child(
9356 div()
9357 .flex_1()
9358 .py_1()
9359 .px_2()
9360 .overflow_hidden()
9361 .child(completion),
9362 )
9363 .when_some(accept_keystroke, |el, accept_keystroke| {
9364 if !accept_keystroke.modifiers().modified() {
9365 return el;
9366 }
9367
9368 el.child(
9369 h_flex()
9370 .h_full()
9371 .border_l_1()
9372 .rounded_r_lg()
9373 .border_color(cx.theme().colors().border)
9374 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9375 .gap_1()
9376 .py_1()
9377 .px_2()
9378 .child(
9379 h_flex()
9380 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9381 .when(is_platform_style_mac, |parent| parent.gap_1())
9382 .child(h_flex().children(ui::render_modifiers(
9383 accept_keystroke.modifiers(),
9384 PlatformStyle::platform(),
9385 Some(if !has_completion {
9386 Color::Muted
9387 } else {
9388 Color::Default
9389 }),
9390 None,
9391 false,
9392 ))),
9393 )
9394 .child(Label::new("Preview").into_any_element())
9395 .opacity(if has_completion { 1.0 } else { 0.4 }),
9396 )
9397 })
9398 .into_any(),
9399 )
9400 }
9401
9402 fn render_edit_prediction_cursor_popover_preview(
9403 &self,
9404 completion: &EditPredictionState,
9405 cursor_point: Point,
9406 style: &EditorStyle,
9407 cx: &mut Context<Editor>,
9408 ) -> Option<Div> {
9409 use text::ToPoint as _;
9410
9411 fn render_relative_row_jump(
9412 prefix: impl Into<String>,
9413 current_row: u32,
9414 target_row: u32,
9415 ) -> Div {
9416 let (row_diff, arrow) = if target_row < current_row {
9417 (current_row - target_row, IconName::ArrowUp)
9418 } else {
9419 (target_row - current_row, IconName::ArrowDown)
9420 };
9421
9422 h_flex()
9423 .child(
9424 Label::new(format!("{}{}", prefix.into(), row_diff))
9425 .color(Color::Muted)
9426 .size(LabelSize::Small),
9427 )
9428 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9429 }
9430
9431 let supports_jump = self
9432 .edit_prediction_provider
9433 .as_ref()
9434 .map(|provider| provider.provider.supports_jump_to_edit())
9435 .unwrap_or(true);
9436
9437 match &completion.completion {
9438 EditPrediction::Move {
9439 target, snapshot, ..
9440 } => {
9441 if !supports_jump {
9442 return None;
9443 }
9444
9445 Some(
9446 h_flex()
9447 .px_2()
9448 .gap_2()
9449 .flex_1()
9450 .child(
9451 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9452 Icon::new(IconName::ZedPredictDown)
9453 } else {
9454 Icon::new(IconName::ZedPredictUp)
9455 },
9456 )
9457 .child(Label::new("Jump to Edit")),
9458 )
9459 }
9460
9461 EditPrediction::Edit {
9462 edits,
9463 edit_preview,
9464 snapshot,
9465 display_mode: _,
9466 } => {
9467 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9468
9469 let (highlighted_edits, has_more_lines) =
9470 if let Some(edit_preview) = edit_preview.as_ref() {
9471 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9472 .first_line_preview()
9473 } else {
9474 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9475 };
9476
9477 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9478 .with_default_highlights(&style.text, highlighted_edits.highlights);
9479
9480 let preview = h_flex()
9481 .gap_1()
9482 .min_w_16()
9483 .child(styled_text)
9484 .when(has_more_lines, |parent| parent.child("…"));
9485
9486 let left = if supports_jump && first_edit_row != cursor_point.row {
9487 render_relative_row_jump("", cursor_point.row, first_edit_row)
9488 .into_any_element()
9489 } else {
9490 let icon_name =
9491 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9492 Icon::new(icon_name).into_any_element()
9493 };
9494
9495 Some(
9496 h_flex()
9497 .h_full()
9498 .flex_1()
9499 .gap_2()
9500 .pr_1()
9501 .overflow_x_hidden()
9502 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9503 .child(left)
9504 .child(preview),
9505 )
9506 }
9507 }
9508 }
9509
9510 pub fn render_context_menu(
9511 &self,
9512 style: &EditorStyle,
9513 max_height_in_lines: u32,
9514 window: &mut Window,
9515 cx: &mut Context<Editor>,
9516 ) -> Option<AnyElement> {
9517 let menu = self.context_menu.borrow();
9518 let menu = menu.as_ref()?;
9519 if !menu.visible() {
9520 return None;
9521 };
9522 Some(menu.render(style, max_height_in_lines, window, cx))
9523 }
9524
9525 fn render_context_menu_aside(
9526 &mut self,
9527 max_size: Size<Pixels>,
9528 window: &mut Window,
9529 cx: &mut Context<Editor>,
9530 ) -> Option<AnyElement> {
9531 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9532 if menu.visible() {
9533 menu.render_aside(max_size, window, cx)
9534 } else {
9535 None
9536 }
9537 })
9538 }
9539
9540 fn hide_context_menu(
9541 &mut self,
9542 window: &mut Window,
9543 cx: &mut Context<Self>,
9544 ) -> Option<CodeContextMenu> {
9545 cx.notify();
9546 self.completion_tasks.clear();
9547 let context_menu = self.context_menu.borrow_mut().take();
9548 self.stale_edit_prediction_in_menu.take();
9549 self.update_visible_edit_prediction(window, cx);
9550 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9551 && let Some(completion_provider) = &self.completion_provider
9552 {
9553 completion_provider.selection_changed(None, window, cx);
9554 }
9555 context_menu
9556 }
9557
9558 fn show_snippet_choices(
9559 &mut self,
9560 choices: &Vec<String>,
9561 selection: Range<Anchor>,
9562 cx: &mut Context<Self>,
9563 ) {
9564 let Some((_, buffer, _)) = self
9565 .buffer()
9566 .read(cx)
9567 .excerpt_containing(selection.start, cx)
9568 else {
9569 return;
9570 };
9571 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9572 else {
9573 return;
9574 };
9575 if buffer != end_buffer {
9576 log::error!("expected anchor range to have matching buffer IDs");
9577 return;
9578 }
9579
9580 let id = post_inc(&mut self.next_completion_id);
9581 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9582 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9583 CompletionsMenu::new_snippet_choices(
9584 id,
9585 true,
9586 choices,
9587 selection,
9588 buffer,
9589 snippet_sort_order,
9590 ),
9591 ));
9592 }
9593
9594 pub fn insert_snippet(
9595 &mut self,
9596 insertion_ranges: &[Range<usize>],
9597 snippet: Snippet,
9598 window: &mut Window,
9599 cx: &mut Context<Self>,
9600 ) -> Result<()> {
9601 struct Tabstop<T> {
9602 is_end_tabstop: bool,
9603 ranges: Vec<Range<T>>,
9604 choices: Option<Vec<String>>,
9605 }
9606
9607 let tabstops = self.buffer.update(cx, |buffer, cx| {
9608 let snippet_text: Arc<str> = snippet.text.clone().into();
9609 let edits = insertion_ranges
9610 .iter()
9611 .cloned()
9612 .map(|range| (range, snippet_text.clone()));
9613 let autoindent_mode = AutoindentMode::Block {
9614 original_indent_columns: Vec::new(),
9615 };
9616 buffer.edit(edits, Some(autoindent_mode), cx);
9617
9618 let snapshot = &*buffer.read(cx);
9619 let snippet = &snippet;
9620 snippet
9621 .tabstops
9622 .iter()
9623 .map(|tabstop| {
9624 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9625 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9626 });
9627 let mut tabstop_ranges = tabstop
9628 .ranges
9629 .iter()
9630 .flat_map(|tabstop_range| {
9631 let mut delta = 0_isize;
9632 insertion_ranges.iter().map(move |insertion_range| {
9633 let insertion_start = insertion_range.start as isize + delta;
9634 delta +=
9635 snippet.text.len() as isize - insertion_range.len() as isize;
9636
9637 let start = ((insertion_start + tabstop_range.start) as usize)
9638 .min(snapshot.len());
9639 let end = ((insertion_start + tabstop_range.end) as usize)
9640 .min(snapshot.len());
9641 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9642 })
9643 })
9644 .collect::<Vec<_>>();
9645 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9646
9647 Tabstop {
9648 is_end_tabstop,
9649 ranges: tabstop_ranges,
9650 choices: tabstop.choices.clone(),
9651 }
9652 })
9653 .collect::<Vec<_>>()
9654 });
9655 if let Some(tabstop) = tabstops.first() {
9656 self.change_selections(Default::default(), window, cx, |s| {
9657 // Reverse order so that the first range is the newest created selection.
9658 // Completions will use it and autoscroll will prioritize it.
9659 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9660 });
9661
9662 if let Some(choices) = &tabstop.choices
9663 && let Some(selection) = tabstop.ranges.first()
9664 {
9665 self.show_snippet_choices(choices, selection.clone(), cx)
9666 }
9667
9668 // If we're already at the last tabstop and it's at the end of the snippet,
9669 // we're done, we don't need to keep the state around.
9670 if !tabstop.is_end_tabstop {
9671 let choices = tabstops
9672 .iter()
9673 .map(|tabstop| tabstop.choices.clone())
9674 .collect();
9675
9676 let ranges = tabstops
9677 .into_iter()
9678 .map(|tabstop| tabstop.ranges)
9679 .collect::<Vec<_>>();
9680
9681 self.snippet_stack.push(SnippetState {
9682 active_index: 0,
9683 ranges,
9684 choices,
9685 });
9686 }
9687
9688 // Check whether the just-entered snippet ends with an auto-closable bracket.
9689 if self.autoclose_regions.is_empty() {
9690 let snapshot = self.buffer.read(cx).snapshot(cx);
9691 let mut all_selections = self.selections.all::<Point>(cx);
9692 for selection in &mut all_selections {
9693 let selection_head = selection.head();
9694 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9695 continue;
9696 };
9697
9698 let mut bracket_pair = None;
9699 let max_lookup_length = scope
9700 .brackets()
9701 .map(|(pair, _)| {
9702 pair.start
9703 .as_str()
9704 .chars()
9705 .count()
9706 .max(pair.end.as_str().chars().count())
9707 })
9708 .max();
9709 if let Some(max_lookup_length) = max_lookup_length {
9710 let next_text = snapshot
9711 .chars_at(selection_head)
9712 .take(max_lookup_length)
9713 .collect::<String>();
9714 let prev_text = snapshot
9715 .reversed_chars_at(selection_head)
9716 .take(max_lookup_length)
9717 .collect::<String>();
9718
9719 for (pair, enabled) in scope.brackets() {
9720 if enabled
9721 && pair.close
9722 && prev_text.starts_with(pair.start.as_str())
9723 && next_text.starts_with(pair.end.as_str())
9724 {
9725 bracket_pair = Some(pair.clone());
9726 break;
9727 }
9728 }
9729 }
9730
9731 if let Some(pair) = bracket_pair {
9732 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9733 let autoclose_enabled =
9734 self.use_autoclose && snapshot_settings.use_autoclose;
9735 if autoclose_enabled {
9736 let start = snapshot.anchor_after(selection_head);
9737 let end = snapshot.anchor_after(selection_head);
9738 self.autoclose_regions.push(AutocloseRegion {
9739 selection_id: selection.id,
9740 range: start..end,
9741 pair,
9742 });
9743 }
9744 }
9745 }
9746 }
9747 }
9748 Ok(())
9749 }
9750
9751 pub fn move_to_next_snippet_tabstop(
9752 &mut self,
9753 window: &mut Window,
9754 cx: &mut Context<Self>,
9755 ) -> bool {
9756 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9757 }
9758
9759 pub fn move_to_prev_snippet_tabstop(
9760 &mut self,
9761 window: &mut Window,
9762 cx: &mut Context<Self>,
9763 ) -> bool {
9764 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9765 }
9766
9767 pub fn move_to_snippet_tabstop(
9768 &mut self,
9769 bias: Bias,
9770 window: &mut Window,
9771 cx: &mut Context<Self>,
9772 ) -> bool {
9773 if let Some(mut snippet) = self.snippet_stack.pop() {
9774 match bias {
9775 Bias::Left => {
9776 if snippet.active_index > 0 {
9777 snippet.active_index -= 1;
9778 } else {
9779 self.snippet_stack.push(snippet);
9780 return false;
9781 }
9782 }
9783 Bias::Right => {
9784 if snippet.active_index + 1 < snippet.ranges.len() {
9785 snippet.active_index += 1;
9786 } else {
9787 self.snippet_stack.push(snippet);
9788 return false;
9789 }
9790 }
9791 }
9792 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9793 self.change_selections(Default::default(), window, cx, |s| {
9794 // Reverse order so that the first range is the newest created selection.
9795 // Completions will use it and autoscroll will prioritize it.
9796 s.select_ranges(current_ranges.iter().rev().cloned())
9797 });
9798
9799 if let Some(choices) = &snippet.choices[snippet.active_index]
9800 && let Some(selection) = current_ranges.first()
9801 {
9802 self.show_snippet_choices(choices, selection.clone(), cx);
9803 }
9804
9805 // If snippet state is not at the last tabstop, push it back on the stack
9806 if snippet.active_index + 1 < snippet.ranges.len() {
9807 self.snippet_stack.push(snippet);
9808 }
9809 return true;
9810 }
9811 }
9812
9813 false
9814 }
9815
9816 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9817 self.transact(window, cx, |this, window, cx| {
9818 this.select_all(&SelectAll, window, cx);
9819 this.insert("", window, cx);
9820 });
9821 }
9822
9823 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9824 if self.read_only(cx) {
9825 return;
9826 }
9827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9828 self.transact(window, cx, |this, window, cx| {
9829 this.select_autoclose_pair(window, cx);
9830 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9831 if !this.linked_edit_ranges.is_empty() {
9832 let selections = this.selections.all::<MultiBufferPoint>(cx);
9833 let snapshot = this.buffer.read(cx).snapshot(cx);
9834
9835 for selection in selections.iter() {
9836 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9837 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9838 if selection_start.buffer_id != selection_end.buffer_id {
9839 continue;
9840 }
9841 if let Some(ranges) =
9842 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9843 {
9844 for (buffer, entries) in ranges {
9845 linked_ranges.entry(buffer).or_default().extend(entries);
9846 }
9847 }
9848 }
9849 }
9850
9851 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9852 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9853 for selection in &mut selections {
9854 if selection.is_empty() {
9855 let old_head = selection.head();
9856 let mut new_head =
9857 movement::left(&display_map, old_head.to_display_point(&display_map))
9858 .to_point(&display_map);
9859 if let Some((buffer, line_buffer_range)) = display_map
9860 .buffer_snapshot
9861 .buffer_line_for_row(MultiBufferRow(old_head.row))
9862 {
9863 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9864 let indent_len = match indent_size.kind {
9865 IndentKind::Space => {
9866 buffer.settings_at(line_buffer_range.start, cx).tab_size
9867 }
9868 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9869 };
9870 if old_head.column <= indent_size.len && old_head.column > 0 {
9871 let indent_len = indent_len.get();
9872 new_head = cmp::min(
9873 new_head,
9874 MultiBufferPoint::new(
9875 old_head.row,
9876 ((old_head.column - 1) / indent_len) * indent_len,
9877 ),
9878 );
9879 }
9880 }
9881
9882 selection.set_head(new_head, SelectionGoal::None);
9883 }
9884 }
9885
9886 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9887 this.insert("", window, cx);
9888 let empty_str: Arc<str> = Arc::from("");
9889 for (buffer, edits) in linked_ranges {
9890 let snapshot = buffer.read(cx).snapshot();
9891 use text::ToPoint as TP;
9892
9893 let edits = edits
9894 .into_iter()
9895 .map(|range| {
9896 let end_point = TP::to_point(&range.end, &snapshot);
9897 let mut start_point = TP::to_point(&range.start, &snapshot);
9898
9899 if end_point == start_point {
9900 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9901 .saturating_sub(1);
9902 start_point =
9903 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9904 };
9905
9906 (start_point..end_point, empty_str.clone())
9907 })
9908 .sorted_by_key(|(range, _)| range.start)
9909 .collect::<Vec<_>>();
9910 buffer.update(cx, |this, cx| {
9911 this.edit(edits, None, cx);
9912 })
9913 }
9914 this.refresh_edit_prediction(true, false, window, cx);
9915 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9916 });
9917 }
9918
9919 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9920 if self.read_only(cx) {
9921 return;
9922 }
9923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9924 self.transact(window, cx, |this, window, cx| {
9925 this.change_selections(Default::default(), window, cx, |s| {
9926 s.move_with(|map, selection| {
9927 if selection.is_empty() {
9928 let cursor = movement::right(map, selection.head());
9929 selection.end = cursor;
9930 selection.reversed = true;
9931 selection.goal = SelectionGoal::None;
9932 }
9933 })
9934 });
9935 this.insert("", window, cx);
9936 this.refresh_edit_prediction(true, false, window, cx);
9937 });
9938 }
9939
9940 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9941 if self.mode.is_single_line() {
9942 cx.propagate();
9943 return;
9944 }
9945
9946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9947 if self.move_to_prev_snippet_tabstop(window, cx) {
9948 return;
9949 }
9950 self.outdent(&Outdent, window, cx);
9951 }
9952
9953 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9954 if self.mode.is_single_line() {
9955 cx.propagate();
9956 return;
9957 }
9958
9959 if self.move_to_next_snippet_tabstop(window, cx) {
9960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9961 return;
9962 }
9963 if self.read_only(cx) {
9964 return;
9965 }
9966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9967 let mut selections = self.selections.all_adjusted(cx);
9968 let buffer = self.buffer.read(cx);
9969 let snapshot = buffer.snapshot(cx);
9970 let rows_iter = selections.iter().map(|s| s.head().row);
9971 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9972
9973 let has_some_cursor_in_whitespace = selections
9974 .iter()
9975 .filter(|selection| selection.is_empty())
9976 .any(|selection| {
9977 let cursor = selection.head();
9978 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9979 cursor.column < current_indent.len
9980 });
9981
9982 let mut edits = Vec::new();
9983 let mut prev_edited_row = 0;
9984 let mut row_delta = 0;
9985 for selection in &mut selections {
9986 if selection.start.row != prev_edited_row {
9987 row_delta = 0;
9988 }
9989 prev_edited_row = selection.end.row;
9990
9991 // If the selection is non-empty, then increase the indentation of the selected lines.
9992 if !selection.is_empty() {
9993 row_delta =
9994 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9995 continue;
9996 }
9997
9998 let cursor = selection.head();
9999 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10000 if let Some(suggested_indent) =
10001 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10002 {
10003 // Don't do anything if already at suggested indent
10004 // and there is any other cursor which is not
10005 if has_some_cursor_in_whitespace
10006 && cursor.column == current_indent.len
10007 && current_indent.len == suggested_indent.len
10008 {
10009 continue;
10010 }
10011
10012 // Adjust line and move cursor to suggested indent
10013 // if cursor is not at suggested indent
10014 if cursor.column < suggested_indent.len
10015 && cursor.column <= current_indent.len
10016 && current_indent.len <= suggested_indent.len
10017 {
10018 selection.start = Point::new(cursor.row, suggested_indent.len);
10019 selection.end = selection.start;
10020 if row_delta == 0 {
10021 edits.extend(Buffer::edit_for_indent_size_adjustment(
10022 cursor.row,
10023 current_indent,
10024 suggested_indent,
10025 ));
10026 row_delta = suggested_indent.len - current_indent.len;
10027 }
10028 continue;
10029 }
10030
10031 // If current indent is more than suggested indent
10032 // only move cursor to current indent and skip indent
10033 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10034 selection.start = Point::new(cursor.row, current_indent.len);
10035 selection.end = selection.start;
10036 continue;
10037 }
10038 }
10039
10040 // Otherwise, insert a hard or soft tab.
10041 let settings = buffer.language_settings_at(cursor, cx);
10042 let tab_size = if settings.hard_tabs {
10043 IndentSize::tab()
10044 } else {
10045 let tab_size = settings.tab_size.get();
10046 let indent_remainder = snapshot
10047 .text_for_range(Point::new(cursor.row, 0)..cursor)
10048 .flat_map(str::chars)
10049 .fold(row_delta % tab_size, |counter: u32, c| {
10050 if c == '\t' {
10051 0
10052 } else {
10053 (counter + 1) % tab_size
10054 }
10055 });
10056
10057 let chars_to_next_tab_stop = tab_size - indent_remainder;
10058 IndentSize::spaces(chars_to_next_tab_stop)
10059 };
10060 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10061 selection.end = selection.start;
10062 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10063 row_delta += tab_size.len;
10064 }
10065
10066 self.transact(window, cx, |this, window, cx| {
10067 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10068 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10069 this.refresh_edit_prediction(true, false, window, cx);
10070 });
10071 }
10072
10073 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10074 if self.read_only(cx) {
10075 return;
10076 }
10077 if self.mode.is_single_line() {
10078 cx.propagate();
10079 return;
10080 }
10081
10082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10083 let mut selections = self.selections.all::<Point>(cx);
10084 let mut prev_edited_row = 0;
10085 let mut row_delta = 0;
10086 let mut edits = Vec::new();
10087 let buffer = self.buffer.read(cx);
10088 let snapshot = buffer.snapshot(cx);
10089 for selection in &mut selections {
10090 if selection.start.row != prev_edited_row {
10091 row_delta = 0;
10092 }
10093 prev_edited_row = selection.end.row;
10094
10095 row_delta =
10096 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10097 }
10098
10099 self.transact(window, cx, |this, window, cx| {
10100 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10101 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10102 });
10103 }
10104
10105 fn indent_selection(
10106 buffer: &MultiBuffer,
10107 snapshot: &MultiBufferSnapshot,
10108 selection: &mut Selection<Point>,
10109 edits: &mut Vec<(Range<Point>, String)>,
10110 delta_for_start_row: u32,
10111 cx: &App,
10112 ) -> u32 {
10113 let settings = buffer.language_settings_at(selection.start, cx);
10114 let tab_size = settings.tab_size.get();
10115 let indent_kind = if settings.hard_tabs {
10116 IndentKind::Tab
10117 } else {
10118 IndentKind::Space
10119 };
10120 let mut start_row = selection.start.row;
10121 let mut end_row = selection.end.row + 1;
10122
10123 // If a selection ends at the beginning of a line, don't indent
10124 // that last line.
10125 if selection.end.column == 0 && selection.end.row > selection.start.row {
10126 end_row -= 1;
10127 }
10128
10129 // Avoid re-indenting a row that has already been indented by a
10130 // previous selection, but still update this selection's column
10131 // to reflect that indentation.
10132 if delta_for_start_row > 0 {
10133 start_row += 1;
10134 selection.start.column += delta_for_start_row;
10135 if selection.end.row == selection.start.row {
10136 selection.end.column += delta_for_start_row;
10137 }
10138 }
10139
10140 let mut delta_for_end_row = 0;
10141 let has_multiple_rows = start_row + 1 != end_row;
10142 for row in start_row..end_row {
10143 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10144 let indent_delta = match (current_indent.kind, indent_kind) {
10145 (IndentKind::Space, IndentKind::Space) => {
10146 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10147 IndentSize::spaces(columns_to_next_tab_stop)
10148 }
10149 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10150 (_, IndentKind::Tab) => IndentSize::tab(),
10151 };
10152
10153 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10154 0
10155 } else {
10156 selection.start.column
10157 };
10158 let row_start = Point::new(row, start);
10159 edits.push((
10160 row_start..row_start,
10161 indent_delta.chars().collect::<String>(),
10162 ));
10163
10164 // Update this selection's endpoints to reflect the indentation.
10165 if row == selection.start.row {
10166 selection.start.column += indent_delta.len;
10167 }
10168 if row == selection.end.row {
10169 selection.end.column += indent_delta.len;
10170 delta_for_end_row = indent_delta.len;
10171 }
10172 }
10173
10174 if selection.start.row == selection.end.row {
10175 delta_for_start_row + delta_for_end_row
10176 } else {
10177 delta_for_end_row
10178 }
10179 }
10180
10181 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10182 if self.read_only(cx) {
10183 return;
10184 }
10185 if self.mode.is_single_line() {
10186 cx.propagate();
10187 return;
10188 }
10189
10190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10192 let selections = self.selections.all::<Point>(cx);
10193 let mut deletion_ranges = Vec::new();
10194 let mut last_outdent = None;
10195 {
10196 let buffer = self.buffer.read(cx);
10197 let snapshot = buffer.snapshot(cx);
10198 for selection in &selections {
10199 let settings = buffer.language_settings_at(selection.start, cx);
10200 let tab_size = settings.tab_size.get();
10201 let mut rows = selection.spanned_rows(false, &display_map);
10202
10203 // Avoid re-outdenting a row that has already been outdented by a
10204 // previous selection.
10205 if let Some(last_row) = last_outdent
10206 && last_row == rows.start
10207 {
10208 rows.start = rows.start.next_row();
10209 }
10210 let has_multiple_rows = rows.len() > 1;
10211 for row in rows.iter_rows() {
10212 let indent_size = snapshot.indent_size_for_line(row);
10213 if indent_size.len > 0 {
10214 let deletion_len = match indent_size.kind {
10215 IndentKind::Space => {
10216 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10217 if columns_to_prev_tab_stop == 0 {
10218 tab_size
10219 } else {
10220 columns_to_prev_tab_stop
10221 }
10222 }
10223 IndentKind::Tab => 1,
10224 };
10225 let start = if has_multiple_rows
10226 || deletion_len > selection.start.column
10227 || indent_size.len < selection.start.column
10228 {
10229 0
10230 } else {
10231 selection.start.column - deletion_len
10232 };
10233 deletion_ranges.push(
10234 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10235 );
10236 last_outdent = Some(row);
10237 }
10238 }
10239 }
10240 }
10241
10242 self.transact(window, cx, |this, window, cx| {
10243 this.buffer.update(cx, |buffer, cx| {
10244 let empty_str: Arc<str> = Arc::default();
10245 buffer.edit(
10246 deletion_ranges
10247 .into_iter()
10248 .map(|range| (range, empty_str.clone())),
10249 None,
10250 cx,
10251 );
10252 });
10253 let selections = this.selections.all::<usize>(cx);
10254 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10255 });
10256 }
10257
10258 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10259 if self.read_only(cx) {
10260 return;
10261 }
10262 if self.mode.is_single_line() {
10263 cx.propagate();
10264 return;
10265 }
10266
10267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10268 let selections = self
10269 .selections
10270 .all::<usize>(cx)
10271 .into_iter()
10272 .map(|s| s.range());
10273
10274 self.transact(window, cx, |this, window, cx| {
10275 this.buffer.update(cx, |buffer, cx| {
10276 buffer.autoindent_ranges(selections, cx);
10277 });
10278 let selections = this.selections.all::<usize>(cx);
10279 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10280 });
10281 }
10282
10283 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10284 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10286 let selections = self.selections.all::<Point>(cx);
10287
10288 let mut new_cursors = Vec::new();
10289 let mut edit_ranges = Vec::new();
10290 let mut selections = selections.iter().peekable();
10291 while let Some(selection) = selections.next() {
10292 let mut rows = selection.spanned_rows(false, &display_map);
10293 let goal_display_column = selection.head().to_display_point(&display_map).column();
10294
10295 // Accumulate contiguous regions of rows that we want to delete.
10296 while let Some(next_selection) = selections.peek() {
10297 let next_rows = next_selection.spanned_rows(false, &display_map);
10298 if next_rows.start <= rows.end {
10299 rows.end = next_rows.end;
10300 selections.next().unwrap();
10301 } else {
10302 break;
10303 }
10304 }
10305
10306 let buffer = &display_map.buffer_snapshot;
10307 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10308 let edit_end;
10309 let cursor_buffer_row;
10310 if buffer.max_point().row >= rows.end.0 {
10311 // If there's a line after the range, delete the \n from the end of the row range
10312 // and position the cursor on the next line.
10313 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10314 cursor_buffer_row = rows.end;
10315 } else {
10316 // If there isn't a line after the range, delete the \n from the line before the
10317 // start of the row range and position the cursor there.
10318 edit_start = edit_start.saturating_sub(1);
10319 edit_end = buffer.len();
10320 cursor_buffer_row = rows.start.previous_row();
10321 }
10322
10323 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10324 *cursor.column_mut() =
10325 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10326
10327 new_cursors.push((
10328 selection.id,
10329 buffer.anchor_after(cursor.to_point(&display_map)),
10330 ));
10331 edit_ranges.push(edit_start..edit_end);
10332 }
10333
10334 self.transact(window, cx, |this, window, cx| {
10335 let buffer = this.buffer.update(cx, |buffer, cx| {
10336 let empty_str: Arc<str> = Arc::default();
10337 buffer.edit(
10338 edit_ranges
10339 .into_iter()
10340 .map(|range| (range, empty_str.clone())),
10341 None,
10342 cx,
10343 );
10344 buffer.snapshot(cx)
10345 });
10346 let new_selections = new_cursors
10347 .into_iter()
10348 .map(|(id, cursor)| {
10349 let cursor = cursor.to_point(&buffer);
10350 Selection {
10351 id,
10352 start: cursor,
10353 end: cursor,
10354 reversed: false,
10355 goal: SelectionGoal::None,
10356 }
10357 })
10358 .collect();
10359
10360 this.change_selections(Default::default(), window, cx, |s| {
10361 s.select(new_selections);
10362 });
10363 });
10364 }
10365
10366 pub fn join_lines_impl(
10367 &mut self,
10368 insert_whitespace: bool,
10369 window: &mut Window,
10370 cx: &mut Context<Self>,
10371 ) {
10372 if self.read_only(cx) {
10373 return;
10374 }
10375 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10376 for selection in self.selections.all::<Point>(cx) {
10377 let start = MultiBufferRow(selection.start.row);
10378 // Treat single line selections as if they include the next line. Otherwise this action
10379 // would do nothing for single line selections individual cursors.
10380 let end = if selection.start.row == selection.end.row {
10381 MultiBufferRow(selection.start.row + 1)
10382 } else {
10383 MultiBufferRow(selection.end.row)
10384 };
10385
10386 if let Some(last_row_range) = row_ranges.last_mut()
10387 && start <= last_row_range.end
10388 {
10389 last_row_range.end = end;
10390 continue;
10391 }
10392 row_ranges.push(start..end);
10393 }
10394
10395 let snapshot = self.buffer.read(cx).snapshot(cx);
10396 let mut cursor_positions = Vec::new();
10397 for row_range in &row_ranges {
10398 let anchor = snapshot.anchor_before(Point::new(
10399 row_range.end.previous_row().0,
10400 snapshot.line_len(row_range.end.previous_row()),
10401 ));
10402 cursor_positions.push(anchor..anchor);
10403 }
10404
10405 self.transact(window, cx, |this, window, cx| {
10406 for row_range in row_ranges.into_iter().rev() {
10407 for row in row_range.iter_rows().rev() {
10408 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10409 let next_line_row = row.next_row();
10410 let indent = snapshot.indent_size_for_line(next_line_row);
10411 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10412
10413 let replace =
10414 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10415 " "
10416 } else {
10417 ""
10418 };
10419
10420 this.buffer.update(cx, |buffer, cx| {
10421 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10422 });
10423 }
10424 }
10425
10426 this.change_selections(Default::default(), window, cx, |s| {
10427 s.select_anchor_ranges(cursor_positions)
10428 });
10429 });
10430 }
10431
10432 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10433 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10434 self.join_lines_impl(true, window, cx);
10435 }
10436
10437 pub fn sort_lines_case_sensitive(
10438 &mut self,
10439 _: &SortLinesCaseSensitive,
10440 window: &mut Window,
10441 cx: &mut Context<Self>,
10442 ) {
10443 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10444 }
10445
10446 pub fn sort_lines_by_length(
10447 &mut self,
10448 _: &SortLinesByLength,
10449 window: &mut Window,
10450 cx: &mut Context<Self>,
10451 ) {
10452 self.manipulate_immutable_lines(window, cx, |lines| {
10453 lines.sort_by_key(|&line| line.chars().count())
10454 })
10455 }
10456
10457 pub fn sort_lines_case_insensitive(
10458 &mut self,
10459 _: &SortLinesCaseInsensitive,
10460 window: &mut Window,
10461 cx: &mut Context<Self>,
10462 ) {
10463 self.manipulate_immutable_lines(window, cx, |lines| {
10464 lines.sort_by_key(|line| line.to_lowercase())
10465 })
10466 }
10467
10468 pub fn unique_lines_case_insensitive(
10469 &mut self,
10470 _: &UniqueLinesCaseInsensitive,
10471 window: &mut Window,
10472 cx: &mut Context<Self>,
10473 ) {
10474 self.manipulate_immutable_lines(window, cx, |lines| {
10475 let mut seen = HashSet::default();
10476 lines.retain(|line| seen.insert(line.to_lowercase()));
10477 })
10478 }
10479
10480 pub fn unique_lines_case_sensitive(
10481 &mut self,
10482 _: &UniqueLinesCaseSensitive,
10483 window: &mut Window,
10484 cx: &mut Context<Self>,
10485 ) {
10486 self.manipulate_immutable_lines(window, cx, |lines| {
10487 let mut seen = HashSet::default();
10488 lines.retain(|line| seen.insert(*line));
10489 })
10490 }
10491
10492 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10493 let snapshot = self.buffer.read(cx).snapshot(cx);
10494 for selection in self.selections.disjoint_anchors().iter() {
10495 if snapshot
10496 .language_at(selection.start)
10497 .and_then(|lang| lang.config().wrap_characters.as_ref())
10498 .is_some()
10499 {
10500 return true;
10501 }
10502 }
10503 false
10504 }
10505
10506 fn wrap_selections_in_tag(
10507 &mut self,
10508 _: &WrapSelectionsInTag,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10513
10514 let snapshot = self.buffer.read(cx).snapshot(cx);
10515
10516 let mut edits = Vec::new();
10517 let mut boundaries = Vec::new();
10518
10519 for selection in self.selections.all::<Point>(cx).iter() {
10520 let Some(wrap_config) = snapshot
10521 .language_at(selection.start)
10522 .and_then(|lang| lang.config().wrap_characters.clone())
10523 else {
10524 continue;
10525 };
10526
10527 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10528 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10529
10530 let start_before = snapshot.anchor_before(selection.start);
10531 let end_after = snapshot.anchor_after(selection.end);
10532
10533 edits.push((start_before..start_before, open_tag));
10534 edits.push((end_after..end_after, close_tag));
10535
10536 boundaries.push((
10537 start_before,
10538 end_after,
10539 wrap_config.start_prefix.len(),
10540 wrap_config.end_suffix.len(),
10541 ));
10542 }
10543
10544 if edits.is_empty() {
10545 return;
10546 }
10547
10548 self.transact(window, cx, |this, window, cx| {
10549 let buffer = this.buffer.update(cx, |buffer, cx| {
10550 buffer.edit(edits, None, cx);
10551 buffer.snapshot(cx)
10552 });
10553
10554 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10555 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10556 boundaries.into_iter()
10557 {
10558 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10559 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10560 new_selections.push(open_offset..open_offset);
10561 new_selections.push(close_offset..close_offset);
10562 }
10563
10564 this.change_selections(Default::default(), window, cx, |s| {
10565 s.select_ranges(new_selections);
10566 });
10567
10568 this.request_autoscroll(Autoscroll::fit(), cx);
10569 });
10570 }
10571
10572 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10573 let Some(project) = self.project.clone() else {
10574 return;
10575 };
10576 self.reload(project, window, cx)
10577 .detach_and_notify_err(window, cx);
10578 }
10579
10580 pub fn restore_file(
10581 &mut self,
10582 _: &::git::RestoreFile,
10583 window: &mut Window,
10584 cx: &mut Context<Self>,
10585 ) {
10586 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10587 let mut buffer_ids = HashSet::default();
10588 let snapshot = self.buffer().read(cx).snapshot(cx);
10589 for selection in self.selections.all::<usize>(cx) {
10590 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10591 }
10592
10593 let buffer = self.buffer().read(cx);
10594 let ranges = buffer_ids
10595 .into_iter()
10596 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10597 .collect::<Vec<_>>();
10598
10599 self.restore_hunks_in_ranges(ranges, window, cx);
10600 }
10601
10602 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10603 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10604 let selections = self
10605 .selections
10606 .all(cx)
10607 .into_iter()
10608 .map(|s| s.range())
10609 .collect();
10610 self.restore_hunks_in_ranges(selections, window, cx);
10611 }
10612
10613 pub fn restore_hunks_in_ranges(
10614 &mut self,
10615 ranges: Vec<Range<Point>>,
10616 window: &mut Window,
10617 cx: &mut Context<Editor>,
10618 ) {
10619 let mut revert_changes = HashMap::default();
10620 let chunk_by = self
10621 .snapshot(window, cx)
10622 .hunks_for_ranges(ranges)
10623 .into_iter()
10624 .chunk_by(|hunk| hunk.buffer_id);
10625 for (buffer_id, hunks) in &chunk_by {
10626 let hunks = hunks.collect::<Vec<_>>();
10627 for hunk in &hunks {
10628 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10629 }
10630 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10631 }
10632 drop(chunk_by);
10633 if !revert_changes.is_empty() {
10634 self.transact(window, cx, |editor, window, cx| {
10635 editor.restore(revert_changes, window, cx);
10636 });
10637 }
10638 }
10639
10640 pub fn open_active_item_in_terminal(
10641 &mut self,
10642 _: &OpenInTerminal,
10643 window: &mut Window,
10644 cx: &mut Context<Self>,
10645 ) {
10646 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10647 let project_path = buffer.read(cx).project_path(cx)?;
10648 let project = self.project()?.read(cx);
10649 let entry = project.entry_for_path(&project_path, cx)?;
10650 let parent = match &entry.canonical_path {
10651 Some(canonical_path) => canonical_path.to_path_buf(),
10652 None => project.absolute_path(&project_path, cx)?,
10653 }
10654 .parent()?
10655 .to_path_buf();
10656 Some(parent)
10657 }) {
10658 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10659 }
10660 }
10661
10662 fn set_breakpoint_context_menu(
10663 &mut self,
10664 display_row: DisplayRow,
10665 position: Option<Anchor>,
10666 clicked_point: gpui::Point<Pixels>,
10667 window: &mut Window,
10668 cx: &mut Context<Self>,
10669 ) {
10670 let source = self
10671 .buffer
10672 .read(cx)
10673 .snapshot(cx)
10674 .anchor_before(Point::new(display_row.0, 0u32));
10675
10676 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10677
10678 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10679 self,
10680 source,
10681 clicked_point,
10682 context_menu,
10683 window,
10684 cx,
10685 );
10686 }
10687
10688 fn add_edit_breakpoint_block(
10689 &mut self,
10690 anchor: Anchor,
10691 breakpoint: &Breakpoint,
10692 edit_action: BreakpointPromptEditAction,
10693 window: &mut Window,
10694 cx: &mut Context<Self>,
10695 ) {
10696 let weak_editor = cx.weak_entity();
10697 let bp_prompt = cx.new(|cx| {
10698 BreakpointPromptEditor::new(
10699 weak_editor,
10700 anchor,
10701 breakpoint.clone(),
10702 edit_action,
10703 window,
10704 cx,
10705 )
10706 });
10707
10708 let height = bp_prompt.update(cx, |this, cx| {
10709 this.prompt
10710 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10711 });
10712 let cloned_prompt = bp_prompt.clone();
10713 let blocks = vec![BlockProperties {
10714 style: BlockStyle::Sticky,
10715 placement: BlockPlacement::Above(anchor),
10716 height: Some(height),
10717 render: Arc::new(move |cx| {
10718 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10719 cloned_prompt.clone().into_any_element()
10720 }),
10721 priority: 0,
10722 }];
10723
10724 let focus_handle = bp_prompt.focus_handle(cx);
10725 window.focus(&focus_handle);
10726
10727 let block_ids = self.insert_blocks(blocks, None, cx);
10728 bp_prompt.update(cx, |prompt, _| {
10729 prompt.add_block_ids(block_ids);
10730 });
10731 }
10732
10733 pub(crate) fn breakpoint_at_row(
10734 &self,
10735 row: u32,
10736 window: &mut Window,
10737 cx: &mut Context<Self>,
10738 ) -> Option<(Anchor, Breakpoint)> {
10739 let snapshot = self.snapshot(window, cx);
10740 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10741
10742 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10743 }
10744
10745 pub(crate) fn breakpoint_at_anchor(
10746 &self,
10747 breakpoint_position: Anchor,
10748 snapshot: &EditorSnapshot,
10749 cx: &mut Context<Self>,
10750 ) -> Option<(Anchor, Breakpoint)> {
10751 let buffer = self
10752 .buffer
10753 .read(cx)
10754 .buffer_for_anchor(breakpoint_position, cx)?;
10755
10756 let enclosing_excerpt = breakpoint_position.excerpt_id;
10757 let buffer_snapshot = buffer.read(cx).snapshot();
10758
10759 let row = buffer_snapshot
10760 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10761 .row;
10762
10763 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10764 let anchor_end = snapshot
10765 .buffer_snapshot
10766 .anchor_after(Point::new(row, line_len));
10767
10768 self.breakpoint_store
10769 .as_ref()?
10770 .read_with(cx, |breakpoint_store, cx| {
10771 breakpoint_store
10772 .breakpoints(
10773 &buffer,
10774 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10775 &buffer_snapshot,
10776 cx,
10777 )
10778 .next()
10779 .and_then(|(bp, _)| {
10780 let breakpoint_row = buffer_snapshot
10781 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10782 .row;
10783
10784 if breakpoint_row == row {
10785 snapshot
10786 .buffer_snapshot
10787 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10788 .map(|position| (position, bp.bp.clone()))
10789 } else {
10790 None
10791 }
10792 })
10793 })
10794 }
10795
10796 pub fn edit_log_breakpoint(
10797 &mut self,
10798 _: &EditLogBreakpoint,
10799 window: &mut Window,
10800 cx: &mut Context<Self>,
10801 ) {
10802 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10803 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10804 message: None,
10805 state: BreakpointState::Enabled,
10806 condition: None,
10807 hit_condition: None,
10808 });
10809
10810 self.add_edit_breakpoint_block(
10811 anchor,
10812 &breakpoint,
10813 BreakpointPromptEditAction::Log,
10814 window,
10815 cx,
10816 );
10817 }
10818 }
10819
10820 fn breakpoints_at_cursors(
10821 &self,
10822 window: &mut Window,
10823 cx: &mut Context<Self>,
10824 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10825 let snapshot = self.snapshot(window, cx);
10826 let cursors = self
10827 .selections
10828 .disjoint_anchors()
10829 .iter()
10830 .map(|selection| {
10831 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10832
10833 let breakpoint_position = self
10834 .breakpoint_at_row(cursor_position.row, window, cx)
10835 .map(|bp| bp.0)
10836 .unwrap_or_else(|| {
10837 snapshot
10838 .display_snapshot
10839 .buffer_snapshot
10840 .anchor_after(Point::new(cursor_position.row, 0))
10841 });
10842
10843 let breakpoint = self
10844 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10845 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10846
10847 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10848 })
10849 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
10850 .collect::<HashMap<Anchor, _>>();
10851
10852 cursors.into_iter().collect()
10853 }
10854
10855 pub fn enable_breakpoint(
10856 &mut self,
10857 _: &crate::actions::EnableBreakpoint,
10858 window: &mut Window,
10859 cx: &mut Context<Self>,
10860 ) {
10861 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10862 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10863 continue;
10864 };
10865 self.edit_breakpoint_at_anchor(
10866 anchor,
10867 breakpoint,
10868 BreakpointEditAction::InvertState,
10869 cx,
10870 );
10871 }
10872 }
10873
10874 pub fn disable_breakpoint(
10875 &mut self,
10876 _: &crate::actions::DisableBreakpoint,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10881 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10882 continue;
10883 };
10884 self.edit_breakpoint_at_anchor(
10885 anchor,
10886 breakpoint,
10887 BreakpointEditAction::InvertState,
10888 cx,
10889 );
10890 }
10891 }
10892
10893 pub fn toggle_breakpoint(
10894 &mut self,
10895 _: &crate::actions::ToggleBreakpoint,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10900 if let Some(breakpoint) = breakpoint {
10901 self.edit_breakpoint_at_anchor(
10902 anchor,
10903 breakpoint,
10904 BreakpointEditAction::Toggle,
10905 cx,
10906 );
10907 } else {
10908 self.edit_breakpoint_at_anchor(
10909 anchor,
10910 Breakpoint::new_standard(),
10911 BreakpointEditAction::Toggle,
10912 cx,
10913 );
10914 }
10915 }
10916 }
10917
10918 pub fn edit_breakpoint_at_anchor(
10919 &mut self,
10920 breakpoint_position: Anchor,
10921 breakpoint: Breakpoint,
10922 edit_action: BreakpointEditAction,
10923 cx: &mut Context<Self>,
10924 ) {
10925 let Some(breakpoint_store) = &self.breakpoint_store else {
10926 return;
10927 };
10928
10929 let Some(buffer) = self
10930 .buffer
10931 .read(cx)
10932 .buffer_for_anchor(breakpoint_position, cx)
10933 else {
10934 return;
10935 };
10936
10937 breakpoint_store.update(cx, |breakpoint_store, cx| {
10938 breakpoint_store.toggle_breakpoint(
10939 buffer,
10940 BreakpointWithPosition {
10941 position: breakpoint_position.text_anchor,
10942 bp: breakpoint,
10943 },
10944 edit_action,
10945 cx,
10946 );
10947 });
10948
10949 cx.notify();
10950 }
10951
10952 #[cfg(any(test, feature = "test-support"))]
10953 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10954 self.breakpoint_store.clone()
10955 }
10956
10957 pub fn prepare_restore_change(
10958 &self,
10959 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10960 hunk: &MultiBufferDiffHunk,
10961 cx: &mut App,
10962 ) -> Option<()> {
10963 if hunk.is_created_file() {
10964 return None;
10965 }
10966 let buffer = self.buffer.read(cx);
10967 let diff = buffer.diff_for(hunk.buffer_id)?;
10968 let buffer = buffer.buffer(hunk.buffer_id)?;
10969 let buffer = buffer.read(cx);
10970 let original_text = diff
10971 .read(cx)
10972 .base_text()
10973 .as_rope()
10974 .slice(hunk.diff_base_byte_range.clone());
10975 let buffer_snapshot = buffer.snapshot();
10976 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10977 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10978 probe
10979 .0
10980 .start
10981 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10982 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10983 }) {
10984 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10985 Some(())
10986 } else {
10987 None
10988 }
10989 }
10990
10991 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10992 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10993 }
10994
10995 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10996 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
10997 }
10998
10999 fn manipulate_lines<M>(
11000 &mut self,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 mut manipulate: M,
11004 ) where
11005 M: FnMut(&str) -> LineManipulationResult,
11006 {
11007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11008
11009 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11010 let buffer = self.buffer.read(cx).snapshot(cx);
11011
11012 let mut edits = Vec::new();
11013
11014 let selections = self.selections.all::<Point>(cx);
11015 let mut selections = selections.iter().peekable();
11016 let mut contiguous_row_selections = Vec::new();
11017 let mut new_selections = Vec::new();
11018 let mut added_lines = 0;
11019 let mut removed_lines = 0;
11020
11021 while let Some(selection) = selections.next() {
11022 let (start_row, end_row) = consume_contiguous_rows(
11023 &mut contiguous_row_selections,
11024 selection,
11025 &display_map,
11026 &mut selections,
11027 );
11028
11029 let start_point = Point::new(start_row.0, 0);
11030 let end_point = Point::new(
11031 end_row.previous_row().0,
11032 buffer.line_len(end_row.previous_row()),
11033 );
11034 let text = buffer
11035 .text_for_range(start_point..end_point)
11036 .collect::<String>();
11037
11038 let LineManipulationResult {
11039 new_text,
11040 line_count_before,
11041 line_count_after,
11042 } = manipulate(&text);
11043
11044 edits.push((start_point..end_point, new_text));
11045
11046 // Selections must change based on added and removed line count
11047 let start_row =
11048 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11049 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11050 new_selections.push(Selection {
11051 id: selection.id,
11052 start: start_row,
11053 end: end_row,
11054 goal: SelectionGoal::None,
11055 reversed: selection.reversed,
11056 });
11057
11058 if line_count_after > line_count_before {
11059 added_lines += line_count_after - line_count_before;
11060 } else if line_count_before > line_count_after {
11061 removed_lines += line_count_before - line_count_after;
11062 }
11063 }
11064
11065 self.transact(window, cx, |this, window, cx| {
11066 let buffer = this.buffer.update(cx, |buffer, cx| {
11067 buffer.edit(edits, None, cx);
11068 buffer.snapshot(cx)
11069 });
11070
11071 // Recalculate offsets on newly edited buffer
11072 let new_selections = new_selections
11073 .iter()
11074 .map(|s| {
11075 let start_point = Point::new(s.start.0, 0);
11076 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11077 Selection {
11078 id: s.id,
11079 start: buffer.point_to_offset(start_point),
11080 end: buffer.point_to_offset(end_point),
11081 goal: s.goal,
11082 reversed: s.reversed,
11083 }
11084 })
11085 .collect();
11086
11087 this.change_selections(Default::default(), window, cx, |s| {
11088 s.select(new_selections);
11089 });
11090
11091 this.request_autoscroll(Autoscroll::fit(), cx);
11092 });
11093 }
11094
11095 fn manipulate_immutable_lines<Fn>(
11096 &mut self,
11097 window: &mut Window,
11098 cx: &mut Context<Self>,
11099 mut callback: Fn,
11100 ) where
11101 Fn: FnMut(&mut Vec<&str>),
11102 {
11103 self.manipulate_lines(window, cx, |text| {
11104 let mut lines: Vec<&str> = text.split('\n').collect();
11105 let line_count_before = lines.len();
11106
11107 callback(&mut lines);
11108
11109 LineManipulationResult {
11110 new_text: lines.join("\n"),
11111 line_count_before,
11112 line_count_after: lines.len(),
11113 }
11114 });
11115 }
11116
11117 fn manipulate_mutable_lines<Fn>(
11118 &mut self,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 mut callback: Fn,
11122 ) where
11123 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11124 {
11125 self.manipulate_lines(window, cx, |text| {
11126 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11127 let line_count_before = lines.len();
11128
11129 callback(&mut lines);
11130
11131 LineManipulationResult {
11132 new_text: lines.join("\n"),
11133 line_count_before,
11134 line_count_after: lines.len(),
11135 }
11136 });
11137 }
11138
11139 pub fn convert_indentation_to_spaces(
11140 &mut self,
11141 _: &ConvertIndentationToSpaces,
11142 window: &mut Window,
11143 cx: &mut Context<Self>,
11144 ) {
11145 let settings = self.buffer.read(cx).language_settings(cx);
11146 let tab_size = settings.tab_size.get() as usize;
11147
11148 self.manipulate_mutable_lines(window, cx, |lines| {
11149 // Allocates a reasonably sized scratch buffer once for the whole loop
11150 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11151 // Avoids recomputing spaces that could be inserted many times
11152 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11153 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11154 .collect();
11155
11156 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11157 let mut chars = line.as_ref().chars();
11158 let mut col = 0;
11159 let mut changed = false;
11160
11161 for ch in chars.by_ref() {
11162 match ch {
11163 ' ' => {
11164 reindented_line.push(' ');
11165 col += 1;
11166 }
11167 '\t' => {
11168 // \t are converted to spaces depending on the current column
11169 let spaces_len = tab_size - (col % tab_size);
11170 reindented_line.extend(&space_cache[spaces_len - 1]);
11171 col += spaces_len;
11172 changed = true;
11173 }
11174 _ => {
11175 // If we dont append before break, the character is consumed
11176 reindented_line.push(ch);
11177 break;
11178 }
11179 }
11180 }
11181
11182 if !changed {
11183 reindented_line.clear();
11184 continue;
11185 }
11186 // Append the rest of the line and replace old reference with new one
11187 reindented_line.extend(chars);
11188 *line = Cow::Owned(reindented_line.clone());
11189 reindented_line.clear();
11190 }
11191 });
11192 }
11193
11194 pub fn convert_indentation_to_tabs(
11195 &mut self,
11196 _: &ConvertIndentationToTabs,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 let settings = self.buffer.read(cx).language_settings(cx);
11201 let tab_size = settings.tab_size.get() as usize;
11202
11203 self.manipulate_mutable_lines(window, cx, |lines| {
11204 // Allocates a reasonably sized buffer once for the whole loop
11205 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11206 // Avoids recomputing spaces that could be inserted many times
11207 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11208 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11209 .collect();
11210
11211 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11212 let mut chars = line.chars();
11213 let mut spaces_count = 0;
11214 let mut first_non_indent_char = None;
11215 let mut changed = false;
11216
11217 for ch in chars.by_ref() {
11218 match ch {
11219 ' ' => {
11220 // Keep track of spaces. Append \t when we reach tab_size
11221 spaces_count += 1;
11222 changed = true;
11223 if spaces_count == tab_size {
11224 reindented_line.push('\t');
11225 spaces_count = 0;
11226 }
11227 }
11228 '\t' => {
11229 reindented_line.push('\t');
11230 spaces_count = 0;
11231 }
11232 _ => {
11233 // Dont append it yet, we might have remaining spaces
11234 first_non_indent_char = Some(ch);
11235 break;
11236 }
11237 }
11238 }
11239
11240 if !changed {
11241 reindented_line.clear();
11242 continue;
11243 }
11244 // Remaining spaces that didn't make a full tab stop
11245 if spaces_count > 0 {
11246 reindented_line.extend(&space_cache[spaces_count - 1]);
11247 }
11248 // If we consume an extra character that was not indentation, add it back
11249 if let Some(extra_char) = first_non_indent_char {
11250 reindented_line.push(extra_char);
11251 }
11252 // Append the rest of the line and replace old reference with new one
11253 reindented_line.extend(chars);
11254 *line = Cow::Owned(reindented_line.clone());
11255 reindented_line.clear();
11256 }
11257 });
11258 }
11259
11260 pub fn convert_to_upper_case(
11261 &mut self,
11262 _: &ConvertToUpperCase,
11263 window: &mut Window,
11264 cx: &mut Context<Self>,
11265 ) {
11266 self.manipulate_text(window, cx, |text| text.to_uppercase())
11267 }
11268
11269 pub fn convert_to_lower_case(
11270 &mut self,
11271 _: &ConvertToLowerCase,
11272 window: &mut Window,
11273 cx: &mut Context<Self>,
11274 ) {
11275 self.manipulate_text(window, cx, |text| text.to_lowercase())
11276 }
11277
11278 pub fn convert_to_title_case(
11279 &mut self,
11280 _: &ConvertToTitleCase,
11281 window: &mut Window,
11282 cx: &mut Context<Self>,
11283 ) {
11284 self.manipulate_text(window, cx, |text| {
11285 text.split('\n')
11286 .map(|line| line.to_case(Case::Title))
11287 .join("\n")
11288 })
11289 }
11290
11291 pub fn convert_to_snake_case(
11292 &mut self,
11293 _: &ConvertToSnakeCase,
11294 window: &mut Window,
11295 cx: &mut Context<Self>,
11296 ) {
11297 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11298 }
11299
11300 pub fn convert_to_kebab_case(
11301 &mut self,
11302 _: &ConvertToKebabCase,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11307 }
11308
11309 pub fn convert_to_upper_camel_case(
11310 &mut self,
11311 _: &ConvertToUpperCamelCase,
11312 window: &mut Window,
11313 cx: &mut Context<Self>,
11314 ) {
11315 self.manipulate_text(window, cx, |text| {
11316 text.split('\n')
11317 .map(|line| line.to_case(Case::UpperCamel))
11318 .join("\n")
11319 })
11320 }
11321
11322 pub fn convert_to_lower_camel_case(
11323 &mut self,
11324 _: &ConvertToLowerCamelCase,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11329 }
11330
11331 pub fn convert_to_opposite_case(
11332 &mut self,
11333 _: &ConvertToOppositeCase,
11334 window: &mut Window,
11335 cx: &mut Context<Self>,
11336 ) {
11337 self.manipulate_text(window, cx, |text| {
11338 text.chars()
11339 .fold(String::with_capacity(text.len()), |mut t, c| {
11340 if c.is_uppercase() {
11341 t.extend(c.to_lowercase());
11342 } else {
11343 t.extend(c.to_uppercase());
11344 }
11345 t
11346 })
11347 })
11348 }
11349
11350 pub fn convert_to_sentence_case(
11351 &mut self,
11352 _: &ConvertToSentenceCase,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11357 }
11358
11359 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11360 self.manipulate_text(window, cx, |text| {
11361 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11362 if has_upper_case_characters {
11363 text.to_lowercase()
11364 } else {
11365 text.to_uppercase()
11366 }
11367 })
11368 }
11369
11370 pub fn convert_to_rot13(
11371 &mut self,
11372 _: &ConvertToRot13,
11373 window: &mut Window,
11374 cx: &mut Context<Self>,
11375 ) {
11376 self.manipulate_text(window, cx, |text| {
11377 text.chars()
11378 .map(|c| match c {
11379 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11380 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11381 _ => c,
11382 })
11383 .collect()
11384 })
11385 }
11386
11387 pub fn convert_to_rot47(
11388 &mut self,
11389 _: &ConvertToRot47,
11390 window: &mut Window,
11391 cx: &mut Context<Self>,
11392 ) {
11393 self.manipulate_text(window, cx, |text| {
11394 text.chars()
11395 .map(|c| {
11396 let code_point = c as u32;
11397 if code_point >= 33 && code_point <= 126 {
11398 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11399 }
11400 c
11401 })
11402 .collect()
11403 })
11404 }
11405
11406 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11407 where
11408 Fn: FnMut(&str) -> String,
11409 {
11410 let buffer = self.buffer.read(cx).snapshot(cx);
11411
11412 let mut new_selections = Vec::new();
11413 let mut edits = Vec::new();
11414 let mut selection_adjustment = 0i32;
11415
11416 for selection in self.selections.all_adjusted(cx) {
11417 let selection_is_empty = selection.is_empty();
11418
11419 let (start, end) = if selection_is_empty {
11420 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11421 (word_range.start, word_range.end)
11422 } else {
11423 (
11424 buffer.point_to_offset(selection.start),
11425 buffer.point_to_offset(selection.end),
11426 )
11427 };
11428
11429 let text = buffer.text_for_range(start..end).collect::<String>();
11430 let old_length = text.len() as i32;
11431 let text = callback(&text);
11432
11433 new_selections.push(Selection {
11434 start: (start as i32 - selection_adjustment) as usize,
11435 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11436 goal: SelectionGoal::None,
11437 id: selection.id,
11438 reversed: selection.reversed,
11439 });
11440
11441 selection_adjustment += old_length - text.len() as i32;
11442
11443 edits.push((start..end, text));
11444 }
11445
11446 self.transact(window, cx, |this, window, cx| {
11447 this.buffer.update(cx, |buffer, cx| {
11448 buffer.edit(edits, None, cx);
11449 });
11450
11451 this.change_selections(Default::default(), window, cx, |s| {
11452 s.select(new_selections);
11453 });
11454
11455 this.request_autoscroll(Autoscroll::fit(), cx);
11456 });
11457 }
11458
11459 pub fn move_selection_on_drop(
11460 &mut self,
11461 selection: &Selection<Anchor>,
11462 target: DisplayPoint,
11463 is_cut: bool,
11464 window: &mut Window,
11465 cx: &mut Context<Self>,
11466 ) {
11467 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11468 let buffer = &display_map.buffer_snapshot;
11469 let mut edits = Vec::new();
11470 let insert_point = display_map
11471 .clip_point(target, Bias::Left)
11472 .to_point(&display_map);
11473 let text = buffer
11474 .text_for_range(selection.start..selection.end)
11475 .collect::<String>();
11476 if is_cut {
11477 edits.push(((selection.start..selection.end), String::new()));
11478 }
11479 let insert_anchor = buffer.anchor_before(insert_point);
11480 edits.push(((insert_anchor..insert_anchor), text));
11481 let last_edit_start = insert_anchor.bias_left(buffer);
11482 let last_edit_end = insert_anchor.bias_right(buffer);
11483 self.transact(window, cx, |this, window, cx| {
11484 this.buffer.update(cx, |buffer, cx| {
11485 buffer.edit(edits, None, cx);
11486 });
11487 this.change_selections(Default::default(), window, cx, |s| {
11488 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11489 });
11490 });
11491 }
11492
11493 pub fn clear_selection_drag_state(&mut self) {
11494 self.selection_drag_state = SelectionDragState::None;
11495 }
11496
11497 pub fn duplicate(
11498 &mut self,
11499 upwards: bool,
11500 whole_lines: bool,
11501 window: &mut Window,
11502 cx: &mut Context<Self>,
11503 ) {
11504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11505
11506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11507 let buffer = &display_map.buffer_snapshot;
11508 let selections = self.selections.all::<Point>(cx);
11509
11510 let mut edits = Vec::new();
11511 let mut selections_iter = selections.iter().peekable();
11512 while let Some(selection) = selections_iter.next() {
11513 let mut rows = selection.spanned_rows(false, &display_map);
11514 // duplicate line-wise
11515 if whole_lines || selection.start == selection.end {
11516 // Avoid duplicating the same lines twice.
11517 while let Some(next_selection) = selections_iter.peek() {
11518 let next_rows = next_selection.spanned_rows(false, &display_map);
11519 if next_rows.start < rows.end {
11520 rows.end = next_rows.end;
11521 selections_iter.next().unwrap();
11522 } else {
11523 break;
11524 }
11525 }
11526
11527 // Copy the text from the selected row region and splice it either at the start
11528 // or end of the region.
11529 let start = Point::new(rows.start.0, 0);
11530 let end = Point::new(
11531 rows.end.previous_row().0,
11532 buffer.line_len(rows.end.previous_row()),
11533 );
11534 let text = buffer
11535 .text_for_range(start..end)
11536 .chain(Some("\n"))
11537 .collect::<String>();
11538 let insert_location = if upwards {
11539 Point::new(rows.end.0, 0)
11540 } else {
11541 start
11542 };
11543 edits.push((insert_location..insert_location, text));
11544 } else {
11545 // duplicate character-wise
11546 let start = selection.start;
11547 let end = selection.end;
11548 let text = buffer.text_for_range(start..end).collect::<String>();
11549 edits.push((selection.end..selection.end, text));
11550 }
11551 }
11552
11553 self.transact(window, cx, |this, _, cx| {
11554 this.buffer.update(cx, |buffer, cx| {
11555 buffer.edit(edits, None, cx);
11556 });
11557
11558 this.request_autoscroll(Autoscroll::fit(), cx);
11559 });
11560 }
11561
11562 pub fn duplicate_line_up(
11563 &mut self,
11564 _: &DuplicateLineUp,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 ) {
11568 self.duplicate(true, true, window, cx);
11569 }
11570
11571 pub fn duplicate_line_down(
11572 &mut self,
11573 _: &DuplicateLineDown,
11574 window: &mut Window,
11575 cx: &mut Context<Self>,
11576 ) {
11577 self.duplicate(false, true, window, cx);
11578 }
11579
11580 pub fn duplicate_selection(
11581 &mut self,
11582 _: &DuplicateSelection,
11583 window: &mut Window,
11584 cx: &mut Context<Self>,
11585 ) {
11586 self.duplicate(false, false, window, cx);
11587 }
11588
11589 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11591 if self.mode.is_single_line() {
11592 cx.propagate();
11593 return;
11594 }
11595
11596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11597 let buffer = self.buffer.read(cx).snapshot(cx);
11598
11599 let mut edits = Vec::new();
11600 let mut unfold_ranges = Vec::new();
11601 let mut refold_creases = Vec::new();
11602
11603 let selections = self.selections.all::<Point>(cx);
11604 let mut selections = selections.iter().peekable();
11605 let mut contiguous_row_selections = Vec::new();
11606 let mut new_selections = Vec::new();
11607
11608 while let Some(selection) = selections.next() {
11609 // Find all the selections that span a contiguous row range
11610 let (start_row, end_row) = consume_contiguous_rows(
11611 &mut contiguous_row_selections,
11612 selection,
11613 &display_map,
11614 &mut selections,
11615 );
11616
11617 // Move the text spanned by the row range to be before the line preceding the row range
11618 if start_row.0 > 0 {
11619 let range_to_move = Point::new(
11620 start_row.previous_row().0,
11621 buffer.line_len(start_row.previous_row()),
11622 )
11623 ..Point::new(
11624 end_row.previous_row().0,
11625 buffer.line_len(end_row.previous_row()),
11626 );
11627 let insertion_point = display_map
11628 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11629 .0;
11630
11631 // Don't move lines across excerpts
11632 if buffer
11633 .excerpt_containing(insertion_point..range_to_move.end)
11634 .is_some()
11635 {
11636 let text = buffer
11637 .text_for_range(range_to_move.clone())
11638 .flat_map(|s| s.chars())
11639 .skip(1)
11640 .chain(['\n'])
11641 .collect::<String>();
11642
11643 edits.push((
11644 buffer.anchor_after(range_to_move.start)
11645 ..buffer.anchor_before(range_to_move.end),
11646 String::new(),
11647 ));
11648 let insertion_anchor = buffer.anchor_after(insertion_point);
11649 edits.push((insertion_anchor..insertion_anchor, text));
11650
11651 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11652
11653 // Move selections up
11654 new_selections.extend(contiguous_row_selections.drain(..).map(
11655 |mut selection| {
11656 selection.start.row -= row_delta;
11657 selection.end.row -= row_delta;
11658 selection
11659 },
11660 ));
11661
11662 // Move folds up
11663 unfold_ranges.push(range_to_move.clone());
11664 for fold in display_map.folds_in_range(
11665 buffer.anchor_before(range_to_move.start)
11666 ..buffer.anchor_after(range_to_move.end),
11667 ) {
11668 let mut start = fold.range.start.to_point(&buffer);
11669 let mut end = fold.range.end.to_point(&buffer);
11670 start.row -= row_delta;
11671 end.row -= row_delta;
11672 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11673 }
11674 }
11675 }
11676
11677 // If we didn't move line(s), preserve the existing selections
11678 new_selections.append(&mut contiguous_row_selections);
11679 }
11680
11681 self.transact(window, cx, |this, window, cx| {
11682 this.unfold_ranges(&unfold_ranges, true, true, cx);
11683 this.buffer.update(cx, |buffer, cx| {
11684 for (range, text) in edits {
11685 buffer.edit([(range, text)], None, cx);
11686 }
11687 });
11688 this.fold_creases(refold_creases, true, window, cx);
11689 this.change_selections(Default::default(), window, cx, |s| {
11690 s.select(new_selections);
11691 })
11692 });
11693 }
11694
11695 pub fn move_line_down(
11696 &mut self,
11697 _: &MoveLineDown,
11698 window: &mut Window,
11699 cx: &mut Context<Self>,
11700 ) {
11701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11702 if self.mode.is_single_line() {
11703 cx.propagate();
11704 return;
11705 }
11706
11707 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11708 let buffer = self.buffer.read(cx).snapshot(cx);
11709
11710 let mut edits = Vec::new();
11711 let mut unfold_ranges = Vec::new();
11712 let mut refold_creases = Vec::new();
11713
11714 let selections = self.selections.all::<Point>(cx);
11715 let mut selections = selections.iter().peekable();
11716 let mut contiguous_row_selections = Vec::new();
11717 let mut new_selections = Vec::new();
11718
11719 while let Some(selection) = selections.next() {
11720 // Find all the selections that span a contiguous row range
11721 let (start_row, end_row) = consume_contiguous_rows(
11722 &mut contiguous_row_selections,
11723 selection,
11724 &display_map,
11725 &mut selections,
11726 );
11727
11728 // Move the text spanned by the row range to be after the last line of the row range
11729 if end_row.0 <= buffer.max_point().row {
11730 let range_to_move =
11731 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11732 let insertion_point = display_map
11733 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11734 .0;
11735
11736 // Don't move lines across excerpt boundaries
11737 if buffer
11738 .excerpt_containing(range_to_move.start..insertion_point)
11739 .is_some()
11740 {
11741 let mut text = String::from("\n");
11742 text.extend(buffer.text_for_range(range_to_move.clone()));
11743 text.pop(); // Drop trailing newline
11744 edits.push((
11745 buffer.anchor_after(range_to_move.start)
11746 ..buffer.anchor_before(range_to_move.end),
11747 String::new(),
11748 ));
11749 let insertion_anchor = buffer.anchor_after(insertion_point);
11750 edits.push((insertion_anchor..insertion_anchor, text));
11751
11752 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11753
11754 // Move selections down
11755 new_selections.extend(contiguous_row_selections.drain(..).map(
11756 |mut selection| {
11757 selection.start.row += row_delta;
11758 selection.end.row += row_delta;
11759 selection
11760 },
11761 ));
11762
11763 // Move folds down
11764 unfold_ranges.push(range_to_move.clone());
11765 for fold in display_map.folds_in_range(
11766 buffer.anchor_before(range_to_move.start)
11767 ..buffer.anchor_after(range_to_move.end),
11768 ) {
11769 let mut start = fold.range.start.to_point(&buffer);
11770 let mut end = fold.range.end.to_point(&buffer);
11771 start.row += row_delta;
11772 end.row += row_delta;
11773 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11774 }
11775 }
11776 }
11777
11778 // If we didn't move line(s), preserve the existing selections
11779 new_selections.append(&mut contiguous_row_selections);
11780 }
11781
11782 self.transact(window, cx, |this, window, cx| {
11783 this.unfold_ranges(&unfold_ranges, true, true, cx);
11784 this.buffer.update(cx, |buffer, cx| {
11785 for (range, text) in edits {
11786 buffer.edit([(range, text)], None, cx);
11787 }
11788 });
11789 this.fold_creases(refold_creases, true, window, cx);
11790 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11791 });
11792 }
11793
11794 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11796 let text_layout_details = &self.text_layout_details(window);
11797 self.transact(window, cx, |this, window, cx| {
11798 let edits = this.change_selections(Default::default(), window, cx, |s| {
11799 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11800 s.move_with(|display_map, selection| {
11801 if !selection.is_empty() {
11802 return;
11803 }
11804
11805 let mut head = selection.head();
11806 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11807 if head.column() == display_map.line_len(head.row()) {
11808 transpose_offset = display_map
11809 .buffer_snapshot
11810 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11811 }
11812
11813 if transpose_offset == 0 {
11814 return;
11815 }
11816
11817 *head.column_mut() += 1;
11818 head = display_map.clip_point(head, Bias::Right);
11819 let goal = SelectionGoal::HorizontalPosition(
11820 display_map
11821 .x_for_display_point(head, text_layout_details)
11822 .into(),
11823 );
11824 selection.collapse_to(head, goal);
11825
11826 let transpose_start = display_map
11827 .buffer_snapshot
11828 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11829 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11830 let transpose_end = display_map
11831 .buffer_snapshot
11832 .clip_offset(transpose_offset + 1, Bias::Right);
11833 if let Some(ch) =
11834 display_map.buffer_snapshot.chars_at(transpose_start).next()
11835 {
11836 edits.push((transpose_start..transpose_offset, String::new()));
11837 edits.push((transpose_end..transpose_end, ch.to_string()));
11838 }
11839 }
11840 });
11841 edits
11842 });
11843 this.buffer
11844 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11845 let selections = this.selections.all::<usize>(cx);
11846 this.change_selections(Default::default(), window, cx, |s| {
11847 s.select(selections);
11848 });
11849 });
11850 }
11851
11852 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11854 if self.mode.is_single_line() {
11855 cx.propagate();
11856 return;
11857 }
11858
11859 self.rewrap_impl(RewrapOptions::default(), cx)
11860 }
11861
11862 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11863 let buffer = self.buffer.read(cx).snapshot(cx);
11864 let selections = self.selections.all::<Point>(cx);
11865
11866 #[derive(Clone, Debug, PartialEq)]
11867 enum CommentFormat {
11868 /// single line comment, with prefix for line
11869 Line(String),
11870 /// single line within a block comment, with prefix for line
11871 BlockLine(String),
11872 /// a single line of a block comment that includes the initial delimiter
11873 BlockCommentWithStart(BlockCommentConfig),
11874 /// a single line of a block comment that includes the ending delimiter
11875 BlockCommentWithEnd(BlockCommentConfig),
11876 }
11877
11878 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11879 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11880 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11881 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11882 .peekable();
11883
11884 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11885 row
11886 } else {
11887 return Vec::new();
11888 };
11889
11890 let language_settings = buffer.language_settings_at(selection.head(), cx);
11891 let language_scope = buffer.language_scope_at(selection.head());
11892
11893 let indent_and_prefix_for_row =
11894 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11895 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11896 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11897 &language_scope
11898 {
11899 let indent_end = Point::new(row, indent.len);
11900 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11901 let line_text_after_indent = buffer
11902 .text_for_range(indent_end..line_end)
11903 .collect::<String>();
11904
11905 let is_within_comment_override = buffer
11906 .language_scope_at(indent_end)
11907 .is_some_and(|scope| scope.override_name() == Some("comment"));
11908 let comment_delimiters = if is_within_comment_override {
11909 // we are within a comment syntax node, but we don't
11910 // yet know what kind of comment: block, doc or line
11911 match (
11912 language_scope.documentation_comment(),
11913 language_scope.block_comment(),
11914 ) {
11915 (Some(config), _) | (_, Some(config))
11916 if buffer.contains_str_at(indent_end, &config.start) =>
11917 {
11918 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11919 }
11920 (Some(config), _) | (_, Some(config))
11921 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11922 {
11923 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11924 }
11925 (Some(config), _) | (_, Some(config))
11926 if buffer.contains_str_at(indent_end, &config.prefix) =>
11927 {
11928 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11929 }
11930 (_, _) => language_scope
11931 .line_comment_prefixes()
11932 .iter()
11933 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11934 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11935 }
11936 } else {
11937 // we not in an overridden comment node, but we may
11938 // be within a non-overridden line comment node
11939 language_scope
11940 .line_comment_prefixes()
11941 .iter()
11942 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11943 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11944 };
11945
11946 let rewrap_prefix = language_scope
11947 .rewrap_prefixes()
11948 .iter()
11949 .find_map(|prefix_regex| {
11950 prefix_regex.find(&line_text_after_indent).map(|mat| {
11951 if mat.start() == 0 {
11952 Some(mat.as_str().to_string())
11953 } else {
11954 None
11955 }
11956 })
11957 })
11958 .flatten();
11959 (comment_delimiters, rewrap_prefix)
11960 } else {
11961 (None, None)
11962 };
11963 (indent, comment_prefix, rewrap_prefix)
11964 };
11965
11966 let mut ranges = Vec::new();
11967 let from_empty_selection = selection.is_empty();
11968
11969 let mut current_range_start = first_row;
11970 let mut prev_row = first_row;
11971 let (
11972 mut current_range_indent,
11973 mut current_range_comment_delimiters,
11974 mut current_range_rewrap_prefix,
11975 ) = indent_and_prefix_for_row(first_row);
11976
11977 for row in non_blank_rows_iter.skip(1) {
11978 let has_paragraph_break = row > prev_row + 1;
11979
11980 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
11981 indent_and_prefix_for_row(row);
11982
11983 let has_indent_change = row_indent != current_range_indent;
11984 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
11985
11986 let has_boundary_change = has_comment_change
11987 || row_rewrap_prefix.is_some()
11988 || (has_indent_change && current_range_comment_delimiters.is_some());
11989
11990 if has_paragraph_break || has_boundary_change {
11991 ranges.push((
11992 language_settings.clone(),
11993 Point::new(current_range_start, 0)
11994 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11995 current_range_indent,
11996 current_range_comment_delimiters.clone(),
11997 current_range_rewrap_prefix.clone(),
11998 from_empty_selection,
11999 ));
12000 current_range_start = row;
12001 current_range_indent = row_indent;
12002 current_range_comment_delimiters = row_comment_delimiters;
12003 current_range_rewrap_prefix = row_rewrap_prefix;
12004 }
12005 prev_row = row;
12006 }
12007
12008 ranges.push((
12009 language_settings.clone(),
12010 Point::new(current_range_start, 0)
12011 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12012 current_range_indent,
12013 current_range_comment_delimiters,
12014 current_range_rewrap_prefix,
12015 from_empty_selection,
12016 ));
12017
12018 ranges
12019 });
12020
12021 let mut edits = Vec::new();
12022 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12023
12024 for (
12025 language_settings,
12026 wrap_range,
12027 mut indent_size,
12028 comment_prefix,
12029 rewrap_prefix,
12030 from_empty_selection,
12031 ) in wrap_ranges
12032 {
12033 let mut start_row = wrap_range.start.row;
12034 let mut end_row = wrap_range.end.row;
12035
12036 // Skip selections that overlap with a range that has already been rewrapped.
12037 let selection_range = start_row..end_row;
12038 if rewrapped_row_ranges
12039 .iter()
12040 .any(|range| range.overlaps(&selection_range))
12041 {
12042 continue;
12043 }
12044
12045 let tab_size = language_settings.tab_size;
12046
12047 let (line_prefix, inside_comment) = match &comment_prefix {
12048 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12049 (Some(prefix.as_str()), true)
12050 }
12051 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12052 (Some(prefix.as_ref()), true)
12053 }
12054 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12055 start: _,
12056 end: _,
12057 prefix,
12058 tab_size,
12059 })) => {
12060 indent_size.len += tab_size;
12061 (Some(prefix.as_ref()), true)
12062 }
12063 None => (None, false),
12064 };
12065 let indent_prefix = indent_size.chars().collect::<String>();
12066 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12067
12068 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12069 RewrapBehavior::InComments => inside_comment,
12070 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12071 RewrapBehavior::Anywhere => true,
12072 };
12073
12074 let should_rewrap = options.override_language_settings
12075 || allow_rewrap_based_on_language
12076 || self.hard_wrap.is_some();
12077 if !should_rewrap {
12078 continue;
12079 }
12080
12081 if from_empty_selection {
12082 'expand_upwards: while start_row > 0 {
12083 let prev_row = start_row - 1;
12084 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12085 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12086 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12087 {
12088 start_row = prev_row;
12089 } else {
12090 break 'expand_upwards;
12091 }
12092 }
12093
12094 'expand_downwards: while end_row < buffer.max_point().row {
12095 let next_row = end_row + 1;
12096 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12097 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12098 && !buffer.is_line_blank(MultiBufferRow(next_row))
12099 {
12100 end_row = next_row;
12101 } else {
12102 break 'expand_downwards;
12103 }
12104 }
12105 }
12106
12107 let start = Point::new(start_row, 0);
12108 let start_offset = start.to_offset(&buffer);
12109 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12110 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12111 let mut first_line_delimiter = None;
12112 let mut last_line_delimiter = None;
12113 let Some(lines_without_prefixes) = selection_text
12114 .lines()
12115 .enumerate()
12116 .map(|(ix, line)| {
12117 let line_trimmed = line.trim_start();
12118 if rewrap_prefix.is_some() && ix > 0 {
12119 Ok(line_trimmed)
12120 } else if let Some(
12121 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12122 start,
12123 prefix,
12124 end,
12125 tab_size,
12126 })
12127 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12128 start,
12129 prefix,
12130 end,
12131 tab_size,
12132 }),
12133 ) = &comment_prefix
12134 {
12135 let line_trimmed = line_trimmed
12136 .strip_prefix(start.as_ref())
12137 .map(|s| {
12138 let mut indent_size = indent_size;
12139 indent_size.len -= tab_size;
12140 let indent_prefix: String = indent_size.chars().collect();
12141 first_line_delimiter = Some((indent_prefix, start));
12142 s.trim_start()
12143 })
12144 .unwrap_or(line_trimmed);
12145 let line_trimmed = line_trimmed
12146 .strip_suffix(end.as_ref())
12147 .map(|s| {
12148 last_line_delimiter = Some(end);
12149 s.trim_end()
12150 })
12151 .unwrap_or(line_trimmed);
12152 let line_trimmed = line_trimmed
12153 .strip_prefix(prefix.as_ref())
12154 .unwrap_or(line_trimmed);
12155 Ok(line_trimmed)
12156 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12157 line_trimmed.strip_prefix(prefix).with_context(|| {
12158 format!("line did not start with prefix {prefix:?}: {line:?}")
12159 })
12160 } else {
12161 line_trimmed
12162 .strip_prefix(&line_prefix.trim_start())
12163 .with_context(|| {
12164 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12165 })
12166 }
12167 })
12168 .collect::<Result<Vec<_>, _>>()
12169 .log_err()
12170 else {
12171 continue;
12172 };
12173
12174 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12175 buffer
12176 .language_settings_at(Point::new(start_row, 0), cx)
12177 .preferred_line_length as usize
12178 });
12179
12180 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12181 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12182 } else {
12183 line_prefix.clone()
12184 };
12185
12186 let wrapped_text = {
12187 let mut wrapped_text = wrap_with_prefix(
12188 line_prefix,
12189 subsequent_lines_prefix,
12190 lines_without_prefixes.join("\n"),
12191 wrap_column,
12192 tab_size,
12193 options.preserve_existing_whitespace,
12194 );
12195
12196 if let Some((indent, delimiter)) = first_line_delimiter {
12197 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12198 }
12199 if let Some(last_line) = last_line_delimiter {
12200 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12201 }
12202
12203 wrapped_text
12204 };
12205
12206 // TODO: should always use char-based diff while still supporting cursor behavior that
12207 // matches vim.
12208 let mut diff_options = DiffOptions::default();
12209 if options.override_language_settings {
12210 diff_options.max_word_diff_len = 0;
12211 diff_options.max_word_diff_line_count = 0;
12212 } else {
12213 diff_options.max_word_diff_len = usize::MAX;
12214 diff_options.max_word_diff_line_count = usize::MAX;
12215 }
12216
12217 for (old_range, new_text) in
12218 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12219 {
12220 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12221 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12222 edits.push((edit_start..edit_end, new_text));
12223 }
12224
12225 rewrapped_row_ranges.push(start_row..=end_row);
12226 }
12227
12228 self.buffer
12229 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12230 }
12231
12232 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12233 let mut text = String::new();
12234 let buffer = self.buffer.read(cx).snapshot(cx);
12235 let mut selections = self.selections.all::<Point>(cx);
12236 let mut clipboard_selections = Vec::with_capacity(selections.len());
12237 {
12238 let max_point = buffer.max_point();
12239 let mut is_first = true;
12240 for selection in &mut selections {
12241 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12242 if is_entire_line {
12243 selection.start = Point::new(selection.start.row, 0);
12244 if !selection.is_empty() && selection.end.column == 0 {
12245 selection.end = cmp::min(max_point, selection.end);
12246 } else {
12247 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12248 }
12249 selection.goal = SelectionGoal::None;
12250 }
12251 if is_first {
12252 is_first = false;
12253 } else {
12254 text += "\n";
12255 }
12256 let mut len = 0;
12257 for chunk in buffer.text_for_range(selection.start..selection.end) {
12258 text.push_str(chunk);
12259 len += chunk.len();
12260 }
12261 clipboard_selections.push(ClipboardSelection {
12262 len,
12263 is_entire_line,
12264 first_line_indent: buffer
12265 .indent_size_for_line(MultiBufferRow(selection.start.row))
12266 .len,
12267 });
12268 }
12269 }
12270
12271 self.transact(window, cx, |this, window, cx| {
12272 this.change_selections(Default::default(), window, cx, |s| {
12273 s.select(selections);
12274 });
12275 this.insert("", window, cx);
12276 });
12277 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12278 }
12279
12280 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12282 let item = self.cut_common(window, cx);
12283 cx.write_to_clipboard(item);
12284 }
12285
12286 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12288 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12289 s.move_with(|snapshot, sel| {
12290 if sel.is_empty() {
12291 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12292 }
12293 });
12294 });
12295 let item = self.cut_common(window, cx);
12296 cx.set_global(KillRing(item))
12297 }
12298
12299 pub fn kill_ring_yank(
12300 &mut self,
12301 _: &KillRingYank,
12302 window: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12306 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12307 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12308 (kill_ring.text().to_string(), kill_ring.metadata_json())
12309 } else {
12310 return;
12311 }
12312 } else {
12313 return;
12314 };
12315 self.do_paste(&text, metadata, false, window, cx);
12316 }
12317
12318 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12319 self.do_copy(true, cx);
12320 }
12321
12322 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12323 self.do_copy(false, cx);
12324 }
12325
12326 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12327 let selections = self.selections.all::<Point>(cx);
12328 let buffer = self.buffer.read(cx).read(cx);
12329 let mut text = String::new();
12330
12331 let mut clipboard_selections = Vec::with_capacity(selections.len());
12332 {
12333 let max_point = buffer.max_point();
12334 let mut is_first = true;
12335 for selection in &selections {
12336 let mut start = selection.start;
12337 let mut end = selection.end;
12338 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12339 if is_entire_line {
12340 start = Point::new(start.row, 0);
12341 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12342 }
12343
12344 let mut trimmed_selections = Vec::new();
12345 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12346 let row = MultiBufferRow(start.row);
12347 let first_indent = buffer.indent_size_for_line(row);
12348 if first_indent.len == 0 || start.column > first_indent.len {
12349 trimmed_selections.push(start..end);
12350 } else {
12351 trimmed_selections.push(
12352 Point::new(row.0, first_indent.len)
12353 ..Point::new(row.0, buffer.line_len(row)),
12354 );
12355 for row in start.row + 1..=end.row {
12356 let mut line_len = buffer.line_len(MultiBufferRow(row));
12357 if row == end.row {
12358 line_len = end.column;
12359 }
12360 if line_len == 0 {
12361 trimmed_selections
12362 .push(Point::new(row, 0)..Point::new(row, line_len));
12363 continue;
12364 }
12365 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12366 if row_indent_size.len >= first_indent.len {
12367 trimmed_selections.push(
12368 Point::new(row, first_indent.len)..Point::new(row, line_len),
12369 );
12370 } else {
12371 trimmed_selections.clear();
12372 trimmed_selections.push(start..end);
12373 break;
12374 }
12375 }
12376 }
12377 } else {
12378 trimmed_selections.push(start..end);
12379 }
12380
12381 for trimmed_range in trimmed_selections {
12382 if is_first {
12383 is_first = false;
12384 } else {
12385 text += "\n";
12386 }
12387 let mut len = 0;
12388 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12389 text.push_str(chunk);
12390 len += chunk.len();
12391 }
12392 clipboard_selections.push(ClipboardSelection {
12393 len,
12394 is_entire_line,
12395 first_line_indent: buffer
12396 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12397 .len,
12398 });
12399 }
12400 }
12401 }
12402
12403 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12404 text,
12405 clipboard_selections,
12406 ));
12407 }
12408
12409 pub fn do_paste(
12410 &mut self,
12411 text: &String,
12412 clipboard_selections: Option<Vec<ClipboardSelection>>,
12413 handle_entire_lines: bool,
12414 window: &mut Window,
12415 cx: &mut Context<Self>,
12416 ) {
12417 if self.read_only(cx) {
12418 return;
12419 }
12420
12421 let clipboard_text = Cow::Borrowed(text);
12422
12423 self.transact(window, cx, |this, window, cx| {
12424 let had_active_edit_prediction = this.has_active_edit_prediction();
12425
12426 if let Some(mut clipboard_selections) = clipboard_selections {
12427 let old_selections = this.selections.all::<usize>(cx);
12428 let all_selections_were_entire_line =
12429 clipboard_selections.iter().all(|s| s.is_entire_line);
12430 let first_selection_indent_column =
12431 clipboard_selections.first().map(|s| s.first_line_indent);
12432 if clipboard_selections.len() != old_selections.len() {
12433 clipboard_selections.drain(..);
12434 }
12435 let cursor_offset = this.selections.last::<usize>(cx).head();
12436 let mut auto_indent_on_paste = true;
12437
12438 this.buffer.update(cx, |buffer, cx| {
12439 let snapshot = buffer.read(cx);
12440 auto_indent_on_paste = snapshot
12441 .language_settings_at(cursor_offset, cx)
12442 .auto_indent_on_paste;
12443
12444 let mut start_offset = 0;
12445 let mut edits = Vec::new();
12446 let mut original_indent_columns = Vec::new();
12447 for (ix, selection) in old_selections.iter().enumerate() {
12448 let to_insert;
12449 let entire_line;
12450 let original_indent_column;
12451 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12452 let end_offset = start_offset + clipboard_selection.len;
12453 to_insert = &clipboard_text[start_offset..end_offset];
12454 entire_line = clipboard_selection.is_entire_line;
12455 start_offset = end_offset + 1;
12456 original_indent_column = Some(clipboard_selection.first_line_indent);
12457 } else {
12458 to_insert = clipboard_text.as_str();
12459 entire_line = all_selections_were_entire_line;
12460 original_indent_column = first_selection_indent_column
12461 }
12462
12463 // If the corresponding selection was empty when this slice of the
12464 // clipboard text was written, then the entire line containing the
12465 // selection was copied. If this selection is also currently empty,
12466 // then paste the line before the current line of the buffer.
12467 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12468 let column = selection.start.to_point(&snapshot).column as usize;
12469 let line_start = selection.start - column;
12470 line_start..line_start
12471 } else {
12472 selection.range()
12473 };
12474
12475 edits.push((range, to_insert));
12476 original_indent_columns.push(original_indent_column);
12477 }
12478 drop(snapshot);
12479
12480 buffer.edit(
12481 edits,
12482 if auto_indent_on_paste {
12483 Some(AutoindentMode::Block {
12484 original_indent_columns,
12485 })
12486 } else {
12487 None
12488 },
12489 cx,
12490 );
12491 });
12492
12493 let selections = this.selections.all::<usize>(cx);
12494 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12495 } else {
12496 this.insert(&clipboard_text, window, cx);
12497 }
12498
12499 let trigger_in_words =
12500 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12501
12502 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12503 });
12504 }
12505
12506 pub fn diff_clipboard_with_selection(
12507 &mut self,
12508 _: &DiffClipboardWithSelection,
12509 window: &mut Window,
12510 cx: &mut Context<Self>,
12511 ) {
12512 let selections = self.selections.all::<usize>(cx);
12513
12514 if selections.is_empty() {
12515 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12516 return;
12517 };
12518
12519 let clipboard_text = match cx.read_from_clipboard() {
12520 Some(item) => match item.entries().first() {
12521 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12522 _ => None,
12523 },
12524 None => None,
12525 };
12526
12527 let Some(clipboard_text) = clipboard_text else {
12528 log::warn!("Clipboard doesn't contain text.");
12529 return;
12530 };
12531
12532 window.dispatch_action(
12533 Box::new(DiffClipboardWithSelectionData {
12534 clipboard_text,
12535 editor: cx.entity(),
12536 }),
12537 cx,
12538 );
12539 }
12540
12541 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12542 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12543 if let Some(item) = cx.read_from_clipboard() {
12544 let entries = item.entries();
12545
12546 match entries.first() {
12547 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12548 // of all the pasted entries.
12549 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12550 .do_paste(
12551 clipboard_string.text(),
12552 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12553 true,
12554 window,
12555 cx,
12556 ),
12557 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12558 }
12559 }
12560 }
12561
12562 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12563 if self.read_only(cx) {
12564 return;
12565 }
12566
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12568
12569 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12570 if let Some((selections, _)) =
12571 self.selection_history.transaction(transaction_id).cloned()
12572 {
12573 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12574 s.select_anchors(selections.to_vec());
12575 });
12576 } else {
12577 log::error!(
12578 "No entry in selection_history found for undo. \
12579 This may correspond to a bug where undo does not update the selection. \
12580 If this is occurring, please add details to \
12581 https://github.com/zed-industries/zed/issues/22692"
12582 );
12583 }
12584 self.request_autoscroll(Autoscroll::fit(), cx);
12585 self.unmark_text(window, cx);
12586 self.refresh_edit_prediction(true, false, window, cx);
12587 cx.emit(EditorEvent::Edited { transaction_id });
12588 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12589 }
12590 }
12591
12592 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12593 if self.read_only(cx) {
12594 return;
12595 }
12596
12597 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12598
12599 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12600 if let Some((_, Some(selections))) =
12601 self.selection_history.transaction(transaction_id).cloned()
12602 {
12603 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12604 s.select_anchors(selections.to_vec());
12605 });
12606 } else {
12607 log::error!(
12608 "No entry in selection_history found for redo. \
12609 This may correspond to a bug where undo does not update the selection. \
12610 If this is occurring, please add details to \
12611 https://github.com/zed-industries/zed/issues/22692"
12612 );
12613 }
12614 self.request_autoscroll(Autoscroll::fit(), cx);
12615 self.unmark_text(window, cx);
12616 self.refresh_edit_prediction(true, false, window, cx);
12617 cx.emit(EditorEvent::Edited { transaction_id });
12618 }
12619 }
12620
12621 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12622 self.buffer
12623 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12624 }
12625
12626 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12627 self.buffer
12628 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12629 }
12630
12631 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12633 self.change_selections(Default::default(), window, cx, |s| {
12634 s.move_with(|map, selection| {
12635 let cursor = if selection.is_empty() {
12636 movement::left(map, selection.start)
12637 } else {
12638 selection.start
12639 };
12640 selection.collapse_to(cursor, SelectionGoal::None);
12641 });
12642 })
12643 }
12644
12645 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12646 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12647 self.change_selections(Default::default(), window, cx, |s| {
12648 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12649 })
12650 }
12651
12652 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12654 self.change_selections(Default::default(), window, cx, |s| {
12655 s.move_with(|map, selection| {
12656 let cursor = if selection.is_empty() {
12657 movement::right(map, selection.end)
12658 } else {
12659 selection.end
12660 };
12661 selection.collapse_to(cursor, SelectionGoal::None)
12662 });
12663 })
12664 }
12665
12666 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12668 self.change_selections(Default::default(), window, cx, |s| {
12669 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12670 })
12671 }
12672
12673 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12674 if self.take_rename(true, window, cx).is_some() {
12675 return;
12676 }
12677
12678 if self.mode.is_single_line() {
12679 cx.propagate();
12680 return;
12681 }
12682
12683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12684
12685 let text_layout_details = &self.text_layout_details(window);
12686 let selection_count = self.selections.count();
12687 let first_selection = self.selections.first_anchor();
12688
12689 self.change_selections(Default::default(), window, cx, |s| {
12690 s.move_with(|map, selection| {
12691 if !selection.is_empty() {
12692 selection.goal = SelectionGoal::None;
12693 }
12694 let (cursor, goal) = movement::up(
12695 map,
12696 selection.start,
12697 selection.goal,
12698 false,
12699 text_layout_details,
12700 );
12701 selection.collapse_to(cursor, goal);
12702 });
12703 });
12704
12705 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12706 {
12707 cx.propagate();
12708 }
12709 }
12710
12711 pub fn move_up_by_lines(
12712 &mut self,
12713 action: &MoveUpByLines,
12714 window: &mut Window,
12715 cx: &mut Context<Self>,
12716 ) {
12717 if self.take_rename(true, window, cx).is_some() {
12718 return;
12719 }
12720
12721 if self.mode.is_single_line() {
12722 cx.propagate();
12723 return;
12724 }
12725
12726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12727
12728 let text_layout_details = &self.text_layout_details(window);
12729
12730 self.change_selections(Default::default(), window, cx, |s| {
12731 s.move_with(|map, selection| {
12732 if !selection.is_empty() {
12733 selection.goal = SelectionGoal::None;
12734 }
12735 let (cursor, goal) = movement::up_by_rows(
12736 map,
12737 selection.start,
12738 action.lines,
12739 selection.goal,
12740 false,
12741 text_layout_details,
12742 );
12743 selection.collapse_to(cursor, goal);
12744 });
12745 })
12746 }
12747
12748 pub fn move_down_by_lines(
12749 &mut self,
12750 action: &MoveDownByLines,
12751 window: &mut Window,
12752 cx: &mut Context<Self>,
12753 ) {
12754 if self.take_rename(true, window, cx).is_some() {
12755 return;
12756 }
12757
12758 if self.mode.is_single_line() {
12759 cx.propagate();
12760 return;
12761 }
12762
12763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12764
12765 let text_layout_details = &self.text_layout_details(window);
12766
12767 self.change_selections(Default::default(), window, cx, |s| {
12768 s.move_with(|map, selection| {
12769 if !selection.is_empty() {
12770 selection.goal = SelectionGoal::None;
12771 }
12772 let (cursor, goal) = movement::down_by_rows(
12773 map,
12774 selection.start,
12775 action.lines,
12776 selection.goal,
12777 false,
12778 text_layout_details,
12779 );
12780 selection.collapse_to(cursor, goal);
12781 });
12782 })
12783 }
12784
12785 pub fn select_down_by_lines(
12786 &mut self,
12787 action: &SelectDownByLines,
12788 window: &mut Window,
12789 cx: &mut Context<Self>,
12790 ) {
12791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12792 let text_layout_details = &self.text_layout_details(window);
12793 self.change_selections(Default::default(), window, cx, |s| {
12794 s.move_heads_with(|map, head, goal| {
12795 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12796 })
12797 })
12798 }
12799
12800 pub fn select_up_by_lines(
12801 &mut self,
12802 action: &SelectUpByLines,
12803 window: &mut Window,
12804 cx: &mut Context<Self>,
12805 ) {
12806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12807 let text_layout_details = &self.text_layout_details(window);
12808 self.change_selections(Default::default(), window, cx, |s| {
12809 s.move_heads_with(|map, head, goal| {
12810 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12811 })
12812 })
12813 }
12814
12815 pub fn select_page_up(
12816 &mut self,
12817 _: &SelectPageUp,
12818 window: &mut Window,
12819 cx: &mut Context<Self>,
12820 ) {
12821 let Some(row_count) = self.visible_row_count() else {
12822 return;
12823 };
12824
12825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12826
12827 let text_layout_details = &self.text_layout_details(window);
12828
12829 self.change_selections(Default::default(), window, cx, |s| {
12830 s.move_heads_with(|map, head, goal| {
12831 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12832 })
12833 })
12834 }
12835
12836 pub fn move_page_up(
12837 &mut self,
12838 action: &MovePageUp,
12839 window: &mut Window,
12840 cx: &mut Context<Self>,
12841 ) {
12842 if self.take_rename(true, window, cx).is_some() {
12843 return;
12844 }
12845
12846 if self
12847 .context_menu
12848 .borrow_mut()
12849 .as_mut()
12850 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12851 .unwrap_or(false)
12852 {
12853 return;
12854 }
12855
12856 if matches!(self.mode, EditorMode::SingleLine) {
12857 cx.propagate();
12858 return;
12859 }
12860
12861 let Some(row_count) = self.visible_row_count() else {
12862 return;
12863 };
12864
12865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12866
12867 let effects = if action.center_cursor {
12868 SelectionEffects::scroll(Autoscroll::center())
12869 } else {
12870 SelectionEffects::default()
12871 };
12872
12873 let text_layout_details = &self.text_layout_details(window);
12874
12875 self.change_selections(effects, window, cx, |s| {
12876 s.move_with(|map, selection| {
12877 if !selection.is_empty() {
12878 selection.goal = SelectionGoal::None;
12879 }
12880 let (cursor, goal) = movement::up_by_rows(
12881 map,
12882 selection.end,
12883 row_count,
12884 selection.goal,
12885 false,
12886 text_layout_details,
12887 );
12888 selection.collapse_to(cursor, goal);
12889 });
12890 });
12891 }
12892
12893 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12895 let text_layout_details = &self.text_layout_details(window);
12896 self.change_selections(Default::default(), window, cx, |s| {
12897 s.move_heads_with(|map, head, goal| {
12898 movement::up(map, head, goal, false, text_layout_details)
12899 })
12900 })
12901 }
12902
12903 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12904 self.take_rename(true, window, cx);
12905
12906 if self.mode.is_single_line() {
12907 cx.propagate();
12908 return;
12909 }
12910
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912
12913 let text_layout_details = &self.text_layout_details(window);
12914 let selection_count = self.selections.count();
12915 let first_selection = self.selections.first_anchor();
12916
12917 self.change_selections(Default::default(), window, cx, |s| {
12918 s.move_with(|map, selection| {
12919 if !selection.is_empty() {
12920 selection.goal = SelectionGoal::None;
12921 }
12922 let (cursor, goal) = movement::down(
12923 map,
12924 selection.end,
12925 selection.goal,
12926 false,
12927 text_layout_details,
12928 );
12929 selection.collapse_to(cursor, goal);
12930 });
12931 });
12932
12933 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12934 {
12935 cx.propagate();
12936 }
12937 }
12938
12939 pub fn select_page_down(
12940 &mut self,
12941 _: &SelectPageDown,
12942 window: &mut Window,
12943 cx: &mut Context<Self>,
12944 ) {
12945 let Some(row_count) = self.visible_row_count() else {
12946 return;
12947 };
12948
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950
12951 let text_layout_details = &self.text_layout_details(window);
12952
12953 self.change_selections(Default::default(), window, cx, |s| {
12954 s.move_heads_with(|map, head, goal| {
12955 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12956 })
12957 })
12958 }
12959
12960 pub fn move_page_down(
12961 &mut self,
12962 action: &MovePageDown,
12963 window: &mut Window,
12964 cx: &mut Context<Self>,
12965 ) {
12966 if self.take_rename(true, window, cx).is_some() {
12967 return;
12968 }
12969
12970 if self
12971 .context_menu
12972 .borrow_mut()
12973 .as_mut()
12974 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12975 .unwrap_or(false)
12976 {
12977 return;
12978 }
12979
12980 if matches!(self.mode, EditorMode::SingleLine) {
12981 cx.propagate();
12982 return;
12983 }
12984
12985 let Some(row_count) = self.visible_row_count() else {
12986 return;
12987 };
12988
12989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12990
12991 let effects = if action.center_cursor {
12992 SelectionEffects::scroll(Autoscroll::center())
12993 } else {
12994 SelectionEffects::default()
12995 };
12996
12997 let text_layout_details = &self.text_layout_details(window);
12998 self.change_selections(effects, window, cx, |s| {
12999 s.move_with(|map, selection| {
13000 if !selection.is_empty() {
13001 selection.goal = SelectionGoal::None;
13002 }
13003 let (cursor, goal) = movement::down_by_rows(
13004 map,
13005 selection.end,
13006 row_count,
13007 selection.goal,
13008 false,
13009 text_layout_details,
13010 );
13011 selection.collapse_to(cursor, goal);
13012 });
13013 });
13014 }
13015
13016 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13018 let text_layout_details = &self.text_layout_details(window);
13019 self.change_selections(Default::default(), window, cx, |s| {
13020 s.move_heads_with(|map, head, goal| {
13021 movement::down(map, head, goal, false, text_layout_details)
13022 })
13023 });
13024 }
13025
13026 pub fn context_menu_first(
13027 &mut self,
13028 _: &ContextMenuFirst,
13029 window: &mut Window,
13030 cx: &mut Context<Self>,
13031 ) {
13032 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13033 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13034 }
13035 }
13036
13037 pub fn context_menu_prev(
13038 &mut self,
13039 _: &ContextMenuPrevious,
13040 window: &mut Window,
13041 cx: &mut Context<Self>,
13042 ) {
13043 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13044 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13045 }
13046 }
13047
13048 pub fn context_menu_next(
13049 &mut self,
13050 _: &ContextMenuNext,
13051 window: &mut Window,
13052 cx: &mut Context<Self>,
13053 ) {
13054 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13055 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13056 }
13057 }
13058
13059 pub fn context_menu_last(
13060 &mut self,
13061 _: &ContextMenuLast,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13066 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13067 }
13068 }
13069
13070 pub fn signature_help_prev(
13071 &mut self,
13072 _: &SignatureHelpPrevious,
13073 _: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) {
13076 if let Some(popover) = self.signature_help_state.popover_mut() {
13077 if popover.current_signature == 0 {
13078 popover.current_signature = popover.signatures.len() - 1;
13079 } else {
13080 popover.current_signature -= 1;
13081 }
13082 cx.notify();
13083 }
13084 }
13085
13086 pub fn signature_help_next(
13087 &mut self,
13088 _: &SignatureHelpNext,
13089 _: &mut Window,
13090 cx: &mut Context<Self>,
13091 ) {
13092 if let Some(popover) = self.signature_help_state.popover_mut() {
13093 if popover.current_signature + 1 == popover.signatures.len() {
13094 popover.current_signature = 0;
13095 } else {
13096 popover.current_signature += 1;
13097 }
13098 cx.notify();
13099 }
13100 }
13101
13102 pub fn move_to_previous_word_start(
13103 &mut self,
13104 _: &MoveToPreviousWordStart,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13109 self.change_selections(Default::default(), window, cx, |s| {
13110 s.move_cursors_with(|map, head, _| {
13111 (
13112 movement::previous_word_start(map, head),
13113 SelectionGoal::None,
13114 )
13115 });
13116 })
13117 }
13118
13119 pub fn move_to_previous_subword_start(
13120 &mut self,
13121 _: &MoveToPreviousSubwordStart,
13122 window: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) {
13125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13126 self.change_selections(Default::default(), window, cx, |s| {
13127 s.move_cursors_with(|map, head, _| {
13128 (
13129 movement::previous_subword_start(map, head),
13130 SelectionGoal::None,
13131 )
13132 });
13133 })
13134 }
13135
13136 pub fn select_to_previous_word_start(
13137 &mut self,
13138 _: &SelectToPreviousWordStart,
13139 window: &mut Window,
13140 cx: &mut Context<Self>,
13141 ) {
13142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13143 self.change_selections(Default::default(), window, cx, |s| {
13144 s.move_heads_with(|map, head, _| {
13145 (
13146 movement::previous_word_start(map, head),
13147 SelectionGoal::None,
13148 )
13149 });
13150 })
13151 }
13152
13153 pub fn select_to_previous_subword_start(
13154 &mut self,
13155 _: &SelectToPreviousSubwordStart,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13160 self.change_selections(Default::default(), window, cx, |s| {
13161 s.move_heads_with(|map, head, _| {
13162 (
13163 movement::previous_subword_start(map, head),
13164 SelectionGoal::None,
13165 )
13166 });
13167 })
13168 }
13169
13170 pub fn delete_to_previous_word_start(
13171 &mut self,
13172 action: &DeleteToPreviousWordStart,
13173 window: &mut Window,
13174 cx: &mut Context<Self>,
13175 ) {
13176 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13177 self.transact(window, cx, |this, window, cx| {
13178 this.select_autoclose_pair(window, cx);
13179 this.change_selections(Default::default(), window, cx, |s| {
13180 s.move_with(|map, selection| {
13181 if selection.is_empty() {
13182 let mut cursor = if action.ignore_newlines {
13183 movement::previous_word_start(map, selection.head())
13184 } else {
13185 movement::previous_word_start_or_newline(map, selection.head())
13186 };
13187 cursor = movement::adjust_greedy_deletion(
13188 map,
13189 selection.head(),
13190 cursor,
13191 action.ignore_brackets,
13192 );
13193 selection.set_head(cursor, SelectionGoal::None);
13194 }
13195 });
13196 });
13197 this.insert("", window, cx);
13198 });
13199 }
13200
13201 pub fn delete_to_previous_subword_start(
13202 &mut self,
13203 _: &DeleteToPreviousSubwordStart,
13204 window: &mut Window,
13205 cx: &mut Context<Self>,
13206 ) {
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13208 self.transact(window, cx, |this, window, cx| {
13209 this.select_autoclose_pair(window, cx);
13210 this.change_selections(Default::default(), window, cx, |s| {
13211 s.move_with(|map, selection| {
13212 if selection.is_empty() {
13213 let mut cursor = movement::previous_subword_start(map, selection.head());
13214 cursor =
13215 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13216 selection.set_head(cursor, SelectionGoal::None);
13217 }
13218 });
13219 });
13220 this.insert("", window, cx);
13221 });
13222 }
13223
13224 pub fn move_to_next_word_end(
13225 &mut self,
13226 _: &MoveToNextWordEnd,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_cursors_with(|map, head, _| {
13233 (movement::next_word_end(map, head), SelectionGoal::None)
13234 });
13235 })
13236 }
13237
13238 pub fn move_to_next_subword_end(
13239 &mut self,
13240 _: &MoveToNextSubwordEnd,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) {
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245 self.change_selections(Default::default(), window, cx, |s| {
13246 s.move_cursors_with(|map, head, _| {
13247 (movement::next_subword_end(map, head), SelectionGoal::None)
13248 });
13249 })
13250 }
13251
13252 pub fn select_to_next_word_end(
13253 &mut self,
13254 _: &SelectToNextWordEnd,
13255 window: &mut Window,
13256 cx: &mut Context<Self>,
13257 ) {
13258 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13259 self.change_selections(Default::default(), window, cx, |s| {
13260 s.move_heads_with(|map, head, _| {
13261 (movement::next_word_end(map, head), SelectionGoal::None)
13262 });
13263 })
13264 }
13265
13266 pub fn select_to_next_subword_end(
13267 &mut self,
13268 _: &SelectToNextSubwordEnd,
13269 window: &mut Window,
13270 cx: &mut Context<Self>,
13271 ) {
13272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13273 self.change_selections(Default::default(), window, cx, |s| {
13274 s.move_heads_with(|map, head, _| {
13275 (movement::next_subword_end(map, head), SelectionGoal::None)
13276 });
13277 })
13278 }
13279
13280 pub fn delete_to_next_word_end(
13281 &mut self,
13282 action: &DeleteToNextWordEnd,
13283 window: &mut Window,
13284 cx: &mut Context<Self>,
13285 ) {
13286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13287 self.transact(window, cx, |this, window, cx| {
13288 this.change_selections(Default::default(), window, cx, |s| {
13289 s.move_with(|map, selection| {
13290 if selection.is_empty() {
13291 let mut cursor = if action.ignore_newlines {
13292 movement::next_word_end(map, selection.head())
13293 } else {
13294 movement::next_word_end_or_newline(map, selection.head())
13295 };
13296 cursor = movement::adjust_greedy_deletion(
13297 map,
13298 selection.head(),
13299 cursor,
13300 action.ignore_brackets,
13301 );
13302 selection.set_head(cursor, SelectionGoal::None);
13303 }
13304 });
13305 });
13306 this.insert("", window, cx);
13307 });
13308 }
13309
13310 pub fn delete_to_next_subword_end(
13311 &mut self,
13312 _: &DeleteToNextSubwordEnd,
13313 window: &mut Window,
13314 cx: &mut Context<Self>,
13315 ) {
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13317 self.transact(window, cx, |this, window, cx| {
13318 this.change_selections(Default::default(), window, cx, |s| {
13319 s.move_with(|map, selection| {
13320 if selection.is_empty() {
13321 let mut cursor = movement::next_subword_end(map, selection.head());
13322 cursor =
13323 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13324 selection.set_head(cursor, SelectionGoal::None);
13325 }
13326 });
13327 });
13328 this.insert("", window, cx);
13329 });
13330 }
13331
13332 pub fn move_to_beginning_of_line(
13333 &mut self,
13334 action: &MoveToBeginningOfLine,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13339 self.change_selections(Default::default(), window, cx, |s| {
13340 s.move_cursors_with(|map, head, _| {
13341 (
13342 movement::indented_line_beginning(
13343 map,
13344 head,
13345 action.stop_at_soft_wraps,
13346 action.stop_at_indent,
13347 ),
13348 SelectionGoal::None,
13349 )
13350 });
13351 })
13352 }
13353
13354 pub fn select_to_beginning_of_line(
13355 &mut self,
13356 action: &SelectToBeginningOfLine,
13357 window: &mut Window,
13358 cx: &mut Context<Self>,
13359 ) {
13360 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13361 self.change_selections(Default::default(), window, cx, |s| {
13362 s.move_heads_with(|map, head, _| {
13363 (
13364 movement::indented_line_beginning(
13365 map,
13366 head,
13367 action.stop_at_soft_wraps,
13368 action.stop_at_indent,
13369 ),
13370 SelectionGoal::None,
13371 )
13372 });
13373 });
13374 }
13375
13376 pub fn delete_to_beginning_of_line(
13377 &mut self,
13378 action: &DeleteToBeginningOfLine,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13383 self.transact(window, cx, |this, window, cx| {
13384 this.change_selections(Default::default(), window, cx, |s| {
13385 s.move_with(|_, selection| {
13386 selection.reversed = true;
13387 });
13388 });
13389
13390 this.select_to_beginning_of_line(
13391 &SelectToBeginningOfLine {
13392 stop_at_soft_wraps: false,
13393 stop_at_indent: action.stop_at_indent,
13394 },
13395 window,
13396 cx,
13397 );
13398 this.backspace(&Backspace, window, cx);
13399 });
13400 }
13401
13402 pub fn move_to_end_of_line(
13403 &mut self,
13404 action: &MoveToEndOfLine,
13405 window: &mut Window,
13406 cx: &mut Context<Self>,
13407 ) {
13408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13409 self.change_selections(Default::default(), window, cx, |s| {
13410 s.move_cursors_with(|map, head, _| {
13411 (
13412 movement::line_end(map, head, action.stop_at_soft_wraps),
13413 SelectionGoal::None,
13414 )
13415 });
13416 })
13417 }
13418
13419 pub fn select_to_end_of_line(
13420 &mut self,
13421 action: &SelectToEndOfLine,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13426 self.change_selections(Default::default(), window, cx, |s| {
13427 s.move_heads_with(|map, head, _| {
13428 (
13429 movement::line_end(map, head, action.stop_at_soft_wraps),
13430 SelectionGoal::None,
13431 )
13432 });
13433 })
13434 }
13435
13436 pub fn delete_to_end_of_line(
13437 &mut self,
13438 _: &DeleteToEndOfLine,
13439 window: &mut Window,
13440 cx: &mut Context<Self>,
13441 ) {
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13443 self.transact(window, cx, |this, window, cx| {
13444 this.select_to_end_of_line(
13445 &SelectToEndOfLine {
13446 stop_at_soft_wraps: false,
13447 },
13448 window,
13449 cx,
13450 );
13451 this.delete(&Delete, window, cx);
13452 });
13453 }
13454
13455 pub fn cut_to_end_of_line(
13456 &mut self,
13457 _: &CutToEndOfLine,
13458 window: &mut Window,
13459 cx: &mut Context<Self>,
13460 ) {
13461 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13462 self.transact(window, cx, |this, window, cx| {
13463 this.select_to_end_of_line(
13464 &SelectToEndOfLine {
13465 stop_at_soft_wraps: false,
13466 },
13467 window,
13468 cx,
13469 );
13470 this.cut(&Cut, window, cx);
13471 });
13472 }
13473
13474 pub fn move_to_start_of_paragraph(
13475 &mut self,
13476 _: &MoveToStartOfParagraph,
13477 window: &mut Window,
13478 cx: &mut Context<Self>,
13479 ) {
13480 if matches!(self.mode, EditorMode::SingleLine) {
13481 cx.propagate();
13482 return;
13483 }
13484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13485 self.change_selections(Default::default(), window, cx, |s| {
13486 s.move_with(|map, selection| {
13487 selection.collapse_to(
13488 movement::start_of_paragraph(map, selection.head(), 1),
13489 SelectionGoal::None,
13490 )
13491 });
13492 })
13493 }
13494
13495 pub fn move_to_end_of_paragraph(
13496 &mut self,
13497 _: &MoveToEndOfParagraph,
13498 window: &mut Window,
13499 cx: &mut Context<Self>,
13500 ) {
13501 if matches!(self.mode, EditorMode::SingleLine) {
13502 cx.propagate();
13503 return;
13504 }
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.move_with(|map, selection| {
13508 selection.collapse_to(
13509 movement::end_of_paragraph(map, selection.head(), 1),
13510 SelectionGoal::None,
13511 )
13512 });
13513 })
13514 }
13515
13516 pub fn select_to_start_of_paragraph(
13517 &mut self,
13518 _: &SelectToStartOfParagraph,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 if matches!(self.mode, EditorMode::SingleLine) {
13523 cx.propagate();
13524 return;
13525 }
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_heads_with(|map, head, _| {
13529 (
13530 movement::start_of_paragraph(map, head, 1),
13531 SelectionGoal::None,
13532 )
13533 });
13534 })
13535 }
13536
13537 pub fn select_to_end_of_paragraph(
13538 &mut self,
13539 _: &SelectToEndOfParagraph,
13540 window: &mut Window,
13541 cx: &mut Context<Self>,
13542 ) {
13543 if matches!(self.mode, EditorMode::SingleLine) {
13544 cx.propagate();
13545 return;
13546 }
13547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.move_heads_with(|map, head, _| {
13550 (
13551 movement::end_of_paragraph(map, head, 1),
13552 SelectionGoal::None,
13553 )
13554 });
13555 })
13556 }
13557
13558 pub fn move_to_start_of_excerpt(
13559 &mut self,
13560 _: &MoveToStartOfExcerpt,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 if matches!(self.mode, EditorMode::SingleLine) {
13565 cx.propagate();
13566 return;
13567 }
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_with(|map, selection| {
13571 selection.collapse_to(
13572 movement::start_of_excerpt(
13573 map,
13574 selection.head(),
13575 workspace::searchable::Direction::Prev,
13576 ),
13577 SelectionGoal::None,
13578 )
13579 });
13580 })
13581 }
13582
13583 pub fn move_to_start_of_next_excerpt(
13584 &mut self,
13585 _: &MoveToStartOfNextExcerpt,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 if matches!(self.mode, EditorMode::SingleLine) {
13590 cx.propagate();
13591 return;
13592 }
13593
13594 self.change_selections(Default::default(), window, cx, |s| {
13595 s.move_with(|map, selection| {
13596 selection.collapse_to(
13597 movement::start_of_excerpt(
13598 map,
13599 selection.head(),
13600 workspace::searchable::Direction::Next,
13601 ),
13602 SelectionGoal::None,
13603 )
13604 });
13605 })
13606 }
13607
13608 pub fn move_to_end_of_excerpt(
13609 &mut self,
13610 _: &MoveToEndOfExcerpt,
13611 window: &mut Window,
13612 cx: &mut Context<Self>,
13613 ) {
13614 if matches!(self.mode, EditorMode::SingleLine) {
13615 cx.propagate();
13616 return;
13617 }
13618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13619 self.change_selections(Default::default(), window, cx, |s| {
13620 s.move_with(|map, selection| {
13621 selection.collapse_to(
13622 movement::end_of_excerpt(
13623 map,
13624 selection.head(),
13625 workspace::searchable::Direction::Next,
13626 ),
13627 SelectionGoal::None,
13628 )
13629 });
13630 })
13631 }
13632
13633 pub fn move_to_end_of_previous_excerpt(
13634 &mut self,
13635 _: &MoveToEndOfPreviousExcerpt,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 if matches!(self.mode, EditorMode::SingleLine) {
13640 cx.propagate();
13641 return;
13642 }
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13644 self.change_selections(Default::default(), window, cx, |s| {
13645 s.move_with(|map, selection| {
13646 selection.collapse_to(
13647 movement::end_of_excerpt(
13648 map,
13649 selection.head(),
13650 workspace::searchable::Direction::Prev,
13651 ),
13652 SelectionGoal::None,
13653 )
13654 });
13655 })
13656 }
13657
13658 pub fn select_to_start_of_excerpt(
13659 &mut self,
13660 _: &SelectToStartOfExcerpt,
13661 window: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 if matches!(self.mode, EditorMode::SingleLine) {
13665 cx.propagate();
13666 return;
13667 }
13668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13669 self.change_selections(Default::default(), window, cx, |s| {
13670 s.move_heads_with(|map, head, _| {
13671 (
13672 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13673 SelectionGoal::None,
13674 )
13675 });
13676 })
13677 }
13678
13679 pub fn select_to_start_of_next_excerpt(
13680 &mut self,
13681 _: &SelectToStartOfNextExcerpt,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 if matches!(self.mode, EditorMode::SingleLine) {
13686 cx.propagate();
13687 return;
13688 }
13689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13690 self.change_selections(Default::default(), window, cx, |s| {
13691 s.move_heads_with(|map, head, _| {
13692 (
13693 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13694 SelectionGoal::None,
13695 )
13696 });
13697 })
13698 }
13699
13700 pub fn select_to_end_of_excerpt(
13701 &mut self,
13702 _: &SelectToEndOfExcerpt,
13703 window: &mut Window,
13704 cx: &mut Context<Self>,
13705 ) {
13706 if matches!(self.mode, EditorMode::SingleLine) {
13707 cx.propagate();
13708 return;
13709 }
13710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13711 self.change_selections(Default::default(), window, cx, |s| {
13712 s.move_heads_with(|map, head, _| {
13713 (
13714 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13715 SelectionGoal::None,
13716 )
13717 });
13718 })
13719 }
13720
13721 pub fn select_to_end_of_previous_excerpt(
13722 &mut self,
13723 _: &SelectToEndOfPreviousExcerpt,
13724 window: &mut Window,
13725 cx: &mut Context<Self>,
13726 ) {
13727 if matches!(self.mode, EditorMode::SingleLine) {
13728 cx.propagate();
13729 return;
13730 }
13731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13732 self.change_selections(Default::default(), window, cx, |s| {
13733 s.move_heads_with(|map, head, _| {
13734 (
13735 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13736 SelectionGoal::None,
13737 )
13738 });
13739 })
13740 }
13741
13742 pub fn move_to_beginning(
13743 &mut self,
13744 _: &MoveToBeginning,
13745 window: &mut Window,
13746 cx: &mut Context<Self>,
13747 ) {
13748 if matches!(self.mode, EditorMode::SingleLine) {
13749 cx.propagate();
13750 return;
13751 }
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.select_ranges(vec![0..0]);
13755 });
13756 }
13757
13758 pub fn select_to_beginning(
13759 &mut self,
13760 _: &SelectToBeginning,
13761 window: &mut Window,
13762 cx: &mut Context<Self>,
13763 ) {
13764 let mut selection = self.selections.last::<Point>(cx);
13765 selection.set_head(Point::zero(), SelectionGoal::None);
13766 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13767 self.change_selections(Default::default(), window, cx, |s| {
13768 s.select(vec![selection]);
13769 });
13770 }
13771
13772 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13773 if matches!(self.mode, EditorMode::SingleLine) {
13774 cx.propagate();
13775 return;
13776 }
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778 let cursor = self.buffer.read(cx).read(cx).len();
13779 self.change_selections(Default::default(), window, cx, |s| {
13780 s.select_ranges(vec![cursor..cursor])
13781 });
13782 }
13783
13784 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13785 self.nav_history = nav_history;
13786 }
13787
13788 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13789 self.nav_history.as_ref()
13790 }
13791
13792 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13793 self.push_to_nav_history(
13794 self.selections.newest_anchor().head(),
13795 None,
13796 false,
13797 true,
13798 cx,
13799 );
13800 }
13801
13802 fn push_to_nav_history(
13803 &mut self,
13804 cursor_anchor: Anchor,
13805 new_position: Option<Point>,
13806 is_deactivate: bool,
13807 always: bool,
13808 cx: &mut Context<Self>,
13809 ) {
13810 if let Some(nav_history) = self.nav_history.as_mut() {
13811 let buffer = self.buffer.read(cx).read(cx);
13812 let cursor_position = cursor_anchor.to_point(&buffer);
13813 let scroll_state = self.scroll_manager.anchor();
13814 let scroll_top_row = scroll_state.top_row(&buffer);
13815 drop(buffer);
13816
13817 if let Some(new_position) = new_position {
13818 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13819 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13820 return;
13821 }
13822 }
13823
13824 nav_history.push(
13825 Some(NavigationData {
13826 cursor_anchor,
13827 cursor_position,
13828 scroll_anchor: scroll_state,
13829 scroll_top_row,
13830 }),
13831 cx,
13832 );
13833 cx.emit(EditorEvent::PushedToNavHistory {
13834 anchor: cursor_anchor,
13835 is_deactivate,
13836 })
13837 }
13838 }
13839
13840 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13842 let buffer = self.buffer.read(cx).snapshot(cx);
13843 let mut selection = self.selections.first::<usize>(cx);
13844 selection.set_head(buffer.len(), SelectionGoal::None);
13845 self.change_selections(Default::default(), window, cx, |s| {
13846 s.select(vec![selection]);
13847 });
13848 }
13849
13850 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13852 let end = self.buffer.read(cx).read(cx).len();
13853 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13854 s.select_ranges(vec![0..end]);
13855 });
13856 }
13857
13858 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13860 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13861 let mut selections = self.selections.all::<Point>(cx);
13862 let max_point = display_map.buffer_snapshot.max_point();
13863 for selection in &mut selections {
13864 let rows = selection.spanned_rows(true, &display_map);
13865 selection.start = Point::new(rows.start.0, 0);
13866 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13867 selection.reversed = false;
13868 }
13869 self.change_selections(Default::default(), window, cx, |s| {
13870 s.select(selections);
13871 });
13872 }
13873
13874 pub fn split_selection_into_lines(
13875 &mut self,
13876 action: &SplitSelectionIntoLines,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 let selections = self
13881 .selections
13882 .all::<Point>(cx)
13883 .into_iter()
13884 .map(|selection| selection.start..selection.end)
13885 .collect::<Vec<_>>();
13886 self.unfold_ranges(&selections, true, true, cx);
13887
13888 let mut new_selection_ranges = Vec::new();
13889 {
13890 let buffer = self.buffer.read(cx).read(cx);
13891 for selection in selections {
13892 for row in selection.start.row..selection.end.row {
13893 let line_start = Point::new(row, 0);
13894 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13895
13896 if action.keep_selections {
13897 // Keep the selection range for each line
13898 let selection_start = if row == selection.start.row {
13899 selection.start
13900 } else {
13901 line_start
13902 };
13903 new_selection_ranges.push(selection_start..line_end);
13904 } else {
13905 // Collapse to cursor at end of line
13906 new_selection_ranges.push(line_end..line_end);
13907 }
13908 }
13909
13910 let is_multiline_selection = selection.start.row != selection.end.row;
13911 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13912 // so this action feels more ergonomic when paired with other selection operations
13913 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13914 if !should_skip_last {
13915 if action.keep_selections {
13916 if is_multiline_selection {
13917 let line_start = Point::new(selection.end.row, 0);
13918 new_selection_ranges.push(line_start..selection.end);
13919 } else {
13920 new_selection_ranges.push(selection.start..selection.end);
13921 }
13922 } else {
13923 new_selection_ranges.push(selection.end..selection.end);
13924 }
13925 }
13926 }
13927 }
13928 self.change_selections(Default::default(), window, cx, |s| {
13929 s.select_ranges(new_selection_ranges);
13930 });
13931 }
13932
13933 pub fn add_selection_above(
13934 &mut self,
13935 _: &AddSelectionAbove,
13936 window: &mut Window,
13937 cx: &mut Context<Self>,
13938 ) {
13939 self.add_selection(true, window, cx);
13940 }
13941
13942 pub fn add_selection_below(
13943 &mut self,
13944 _: &AddSelectionBelow,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) {
13948 self.add_selection(false, window, cx);
13949 }
13950
13951 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13952 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13953
13954 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13955 let all_selections = self.selections.all::<Point>(cx);
13956 let text_layout_details = self.text_layout_details(window);
13957
13958 let (mut columnar_selections, new_selections_to_columnarize) = {
13959 if let Some(state) = self.add_selections_state.as_ref() {
13960 let columnar_selection_ids: HashSet<_> = state
13961 .groups
13962 .iter()
13963 .flat_map(|group| group.stack.iter())
13964 .copied()
13965 .collect();
13966
13967 all_selections
13968 .into_iter()
13969 .partition(|s| columnar_selection_ids.contains(&s.id))
13970 } else {
13971 (Vec::new(), all_selections)
13972 }
13973 };
13974
13975 let mut state = self
13976 .add_selections_state
13977 .take()
13978 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13979
13980 for selection in new_selections_to_columnarize {
13981 let range = selection.display_range(&display_map).sorted();
13982 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13983 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13984 let positions = start_x.min(end_x)..start_x.max(end_x);
13985 let mut stack = Vec::new();
13986 for row in range.start.row().0..=range.end.row().0 {
13987 if let Some(selection) = self.selections.build_columnar_selection(
13988 &display_map,
13989 DisplayRow(row),
13990 &positions,
13991 selection.reversed,
13992 &text_layout_details,
13993 ) {
13994 stack.push(selection.id);
13995 columnar_selections.push(selection);
13996 }
13997 }
13998 if !stack.is_empty() {
13999 if above {
14000 stack.reverse();
14001 }
14002 state.groups.push(AddSelectionsGroup { above, stack });
14003 }
14004 }
14005
14006 let mut final_selections = Vec::new();
14007 let end_row = if above {
14008 DisplayRow(0)
14009 } else {
14010 display_map.max_point().row()
14011 };
14012
14013 let mut last_added_item_per_group = HashMap::default();
14014 for group in state.groups.iter_mut() {
14015 if let Some(last_id) = group.stack.last() {
14016 last_added_item_per_group.insert(*last_id, group);
14017 }
14018 }
14019
14020 for selection in columnar_selections {
14021 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14022 if above == group.above {
14023 let range = selection.display_range(&display_map).sorted();
14024 debug_assert_eq!(range.start.row(), range.end.row());
14025 let mut row = range.start.row();
14026 let positions =
14027 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14028 px(start)..px(end)
14029 } else {
14030 let start_x =
14031 display_map.x_for_display_point(range.start, &text_layout_details);
14032 let end_x =
14033 display_map.x_for_display_point(range.end, &text_layout_details);
14034 start_x.min(end_x)..start_x.max(end_x)
14035 };
14036
14037 let mut maybe_new_selection = None;
14038 while row != end_row {
14039 if above {
14040 row.0 -= 1;
14041 } else {
14042 row.0 += 1;
14043 }
14044 if let Some(new_selection) = self.selections.build_columnar_selection(
14045 &display_map,
14046 row,
14047 &positions,
14048 selection.reversed,
14049 &text_layout_details,
14050 ) {
14051 maybe_new_selection = Some(new_selection);
14052 break;
14053 }
14054 }
14055
14056 if let Some(new_selection) = maybe_new_selection {
14057 group.stack.push(new_selection.id);
14058 if above {
14059 final_selections.push(new_selection);
14060 final_selections.push(selection);
14061 } else {
14062 final_selections.push(selection);
14063 final_selections.push(new_selection);
14064 }
14065 } else {
14066 final_selections.push(selection);
14067 }
14068 } else {
14069 group.stack.pop();
14070 }
14071 } else {
14072 final_selections.push(selection);
14073 }
14074 }
14075
14076 self.change_selections(Default::default(), window, cx, |s| {
14077 s.select(final_selections);
14078 });
14079
14080 let final_selection_ids: HashSet<_> = self
14081 .selections
14082 .all::<Point>(cx)
14083 .iter()
14084 .map(|s| s.id)
14085 .collect();
14086 state.groups.retain_mut(|group| {
14087 // selections might get merged above so we remove invalid items from stacks
14088 group.stack.retain(|id| final_selection_ids.contains(id));
14089
14090 // single selection in stack can be treated as initial state
14091 group.stack.len() > 1
14092 });
14093
14094 if !state.groups.is_empty() {
14095 self.add_selections_state = Some(state);
14096 }
14097 }
14098
14099 fn select_match_ranges(
14100 &mut self,
14101 range: Range<usize>,
14102 reversed: bool,
14103 replace_newest: bool,
14104 auto_scroll: Option<Autoscroll>,
14105 window: &mut Window,
14106 cx: &mut Context<Editor>,
14107 ) {
14108 self.unfold_ranges(
14109 std::slice::from_ref(&range),
14110 false,
14111 auto_scroll.is_some(),
14112 cx,
14113 );
14114 let effects = if let Some(scroll) = auto_scroll {
14115 SelectionEffects::scroll(scroll)
14116 } else {
14117 SelectionEffects::no_scroll()
14118 };
14119 self.change_selections(effects, window, cx, |s| {
14120 if replace_newest {
14121 s.delete(s.newest_anchor().id);
14122 }
14123 if reversed {
14124 s.insert_range(range.end..range.start);
14125 } else {
14126 s.insert_range(range);
14127 }
14128 });
14129 }
14130
14131 pub fn select_next_match_internal(
14132 &mut self,
14133 display_map: &DisplaySnapshot,
14134 replace_newest: bool,
14135 autoscroll: Option<Autoscroll>,
14136 window: &mut Window,
14137 cx: &mut Context<Self>,
14138 ) -> Result<()> {
14139 let buffer = &display_map.buffer_snapshot;
14140 let mut selections = self.selections.all::<usize>(cx);
14141 if let Some(mut select_next_state) = self.select_next_state.take() {
14142 let query = &select_next_state.query;
14143 if !select_next_state.done {
14144 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14145 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14146 let mut next_selected_range = None;
14147
14148 let bytes_after_last_selection =
14149 buffer.bytes_in_range(last_selection.end..buffer.len());
14150 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14151 let query_matches = query
14152 .stream_find_iter(bytes_after_last_selection)
14153 .map(|result| (last_selection.end, result))
14154 .chain(
14155 query
14156 .stream_find_iter(bytes_before_first_selection)
14157 .map(|result| (0, result)),
14158 );
14159
14160 for (start_offset, query_match) in query_matches {
14161 let query_match = query_match.unwrap(); // can only fail due to I/O
14162 let offset_range =
14163 start_offset + query_match.start()..start_offset + query_match.end();
14164
14165 if !select_next_state.wordwise
14166 || (!buffer.is_inside_word(offset_range.start, false)
14167 && !buffer.is_inside_word(offset_range.end, false))
14168 {
14169 // TODO: This is n^2, because we might check all the selections
14170 if !selections
14171 .iter()
14172 .any(|selection| selection.range().overlaps(&offset_range))
14173 {
14174 next_selected_range = Some(offset_range);
14175 break;
14176 }
14177 }
14178 }
14179
14180 if let Some(next_selected_range) = next_selected_range {
14181 self.select_match_ranges(
14182 next_selected_range,
14183 last_selection.reversed,
14184 replace_newest,
14185 autoscroll,
14186 window,
14187 cx,
14188 );
14189 } else {
14190 select_next_state.done = true;
14191 }
14192 }
14193
14194 self.select_next_state = Some(select_next_state);
14195 } else {
14196 let mut only_carets = true;
14197 let mut same_text_selected = true;
14198 let mut selected_text = None;
14199
14200 let mut selections_iter = selections.iter().peekable();
14201 while let Some(selection) = selections_iter.next() {
14202 if selection.start != selection.end {
14203 only_carets = false;
14204 }
14205
14206 if same_text_selected {
14207 if selected_text.is_none() {
14208 selected_text =
14209 Some(buffer.text_for_range(selection.range()).collect::<String>());
14210 }
14211
14212 if let Some(next_selection) = selections_iter.peek() {
14213 if next_selection.range().len() == selection.range().len() {
14214 let next_selected_text = buffer
14215 .text_for_range(next_selection.range())
14216 .collect::<String>();
14217 if Some(next_selected_text) != selected_text {
14218 same_text_selected = false;
14219 selected_text = None;
14220 }
14221 } else {
14222 same_text_selected = false;
14223 selected_text = None;
14224 }
14225 }
14226 }
14227 }
14228
14229 if only_carets {
14230 for selection in &mut selections {
14231 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14232 selection.start = word_range.start;
14233 selection.end = word_range.end;
14234 selection.goal = SelectionGoal::None;
14235 selection.reversed = false;
14236 self.select_match_ranges(
14237 selection.start..selection.end,
14238 selection.reversed,
14239 replace_newest,
14240 autoscroll,
14241 window,
14242 cx,
14243 );
14244 }
14245
14246 if selections.len() == 1 {
14247 let selection = selections
14248 .last()
14249 .expect("ensured that there's only one selection");
14250 let query = buffer
14251 .text_for_range(selection.start..selection.end)
14252 .collect::<String>();
14253 let is_empty = query.is_empty();
14254 let select_state = SelectNextState {
14255 query: AhoCorasick::new(&[query])?,
14256 wordwise: true,
14257 done: is_empty,
14258 };
14259 self.select_next_state = Some(select_state);
14260 } else {
14261 self.select_next_state = None;
14262 }
14263 } else if let Some(selected_text) = selected_text {
14264 self.select_next_state = Some(SelectNextState {
14265 query: AhoCorasick::new(&[selected_text])?,
14266 wordwise: false,
14267 done: false,
14268 });
14269 self.select_next_match_internal(
14270 display_map,
14271 replace_newest,
14272 autoscroll,
14273 window,
14274 cx,
14275 )?;
14276 }
14277 }
14278 Ok(())
14279 }
14280
14281 pub fn select_all_matches(
14282 &mut self,
14283 _action: &SelectAllMatches,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) -> Result<()> {
14287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14288
14289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14290
14291 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14292 let Some(select_next_state) = self.select_next_state.as_mut() else {
14293 return Ok(());
14294 };
14295 if select_next_state.done {
14296 return Ok(());
14297 }
14298
14299 let mut new_selections = Vec::new();
14300
14301 let reversed = self.selections.oldest::<usize>(cx).reversed;
14302 let buffer = &display_map.buffer_snapshot;
14303 let query_matches = select_next_state
14304 .query
14305 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14306
14307 for query_match in query_matches.into_iter() {
14308 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14309 let offset_range = if reversed {
14310 query_match.end()..query_match.start()
14311 } else {
14312 query_match.start()..query_match.end()
14313 };
14314
14315 if !select_next_state.wordwise
14316 || (!buffer.is_inside_word(offset_range.start, false)
14317 && !buffer.is_inside_word(offset_range.end, false))
14318 {
14319 new_selections.push(offset_range.start..offset_range.end);
14320 }
14321 }
14322
14323 select_next_state.done = true;
14324
14325 if new_selections.is_empty() {
14326 log::error!("bug: new_selections is empty in select_all_matches");
14327 return Ok(());
14328 }
14329
14330 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14331 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14332 selections.select_ranges(new_selections)
14333 });
14334
14335 Ok(())
14336 }
14337
14338 pub fn select_next(
14339 &mut self,
14340 action: &SelectNext,
14341 window: &mut Window,
14342 cx: &mut Context<Self>,
14343 ) -> Result<()> {
14344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14346 self.select_next_match_internal(
14347 &display_map,
14348 action.replace_newest,
14349 Some(Autoscroll::newest()),
14350 window,
14351 cx,
14352 )?;
14353 Ok(())
14354 }
14355
14356 pub fn select_previous(
14357 &mut self,
14358 action: &SelectPrevious,
14359 window: &mut Window,
14360 cx: &mut Context<Self>,
14361 ) -> Result<()> {
14362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14364 let buffer = &display_map.buffer_snapshot;
14365 let mut selections = self.selections.all::<usize>(cx);
14366 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14367 let query = &select_prev_state.query;
14368 if !select_prev_state.done {
14369 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14370 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14371 let mut next_selected_range = None;
14372 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14373 let bytes_before_last_selection =
14374 buffer.reversed_bytes_in_range(0..last_selection.start);
14375 let bytes_after_first_selection =
14376 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14377 let query_matches = query
14378 .stream_find_iter(bytes_before_last_selection)
14379 .map(|result| (last_selection.start, result))
14380 .chain(
14381 query
14382 .stream_find_iter(bytes_after_first_selection)
14383 .map(|result| (buffer.len(), result)),
14384 );
14385 for (end_offset, query_match) in query_matches {
14386 let query_match = query_match.unwrap(); // can only fail due to I/O
14387 let offset_range =
14388 end_offset - query_match.end()..end_offset - query_match.start();
14389
14390 if !select_prev_state.wordwise
14391 || (!buffer.is_inside_word(offset_range.start, false)
14392 && !buffer.is_inside_word(offset_range.end, false))
14393 {
14394 next_selected_range = Some(offset_range);
14395 break;
14396 }
14397 }
14398
14399 if let Some(next_selected_range) = next_selected_range {
14400 self.select_match_ranges(
14401 next_selected_range,
14402 last_selection.reversed,
14403 action.replace_newest,
14404 Some(Autoscroll::newest()),
14405 window,
14406 cx,
14407 );
14408 } else {
14409 select_prev_state.done = true;
14410 }
14411 }
14412
14413 self.select_prev_state = Some(select_prev_state);
14414 } else {
14415 let mut only_carets = true;
14416 let mut same_text_selected = true;
14417 let mut selected_text = None;
14418
14419 let mut selections_iter = selections.iter().peekable();
14420 while let Some(selection) = selections_iter.next() {
14421 if selection.start != selection.end {
14422 only_carets = false;
14423 }
14424
14425 if same_text_selected {
14426 if selected_text.is_none() {
14427 selected_text =
14428 Some(buffer.text_for_range(selection.range()).collect::<String>());
14429 }
14430
14431 if let Some(next_selection) = selections_iter.peek() {
14432 if next_selection.range().len() == selection.range().len() {
14433 let next_selected_text = buffer
14434 .text_for_range(next_selection.range())
14435 .collect::<String>();
14436 if Some(next_selected_text) != selected_text {
14437 same_text_selected = false;
14438 selected_text = None;
14439 }
14440 } else {
14441 same_text_selected = false;
14442 selected_text = None;
14443 }
14444 }
14445 }
14446 }
14447
14448 if only_carets {
14449 for selection in &mut selections {
14450 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14451 selection.start = word_range.start;
14452 selection.end = word_range.end;
14453 selection.goal = SelectionGoal::None;
14454 selection.reversed = false;
14455 self.select_match_ranges(
14456 selection.start..selection.end,
14457 selection.reversed,
14458 action.replace_newest,
14459 Some(Autoscroll::newest()),
14460 window,
14461 cx,
14462 );
14463 }
14464 if selections.len() == 1 {
14465 let selection = selections
14466 .last()
14467 .expect("ensured that there's only one selection");
14468 let query = buffer
14469 .text_for_range(selection.start..selection.end)
14470 .collect::<String>();
14471 let is_empty = query.is_empty();
14472 let select_state = SelectNextState {
14473 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14474 wordwise: true,
14475 done: is_empty,
14476 };
14477 self.select_prev_state = Some(select_state);
14478 } else {
14479 self.select_prev_state = None;
14480 }
14481 } else if let Some(selected_text) = selected_text {
14482 self.select_prev_state = Some(SelectNextState {
14483 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14484 wordwise: false,
14485 done: false,
14486 });
14487 self.select_previous(action, window, cx)?;
14488 }
14489 }
14490 Ok(())
14491 }
14492
14493 pub fn find_next_match(
14494 &mut self,
14495 _: &FindNextMatch,
14496 window: &mut Window,
14497 cx: &mut Context<Self>,
14498 ) -> Result<()> {
14499 let selections = self.selections.disjoint_anchors();
14500 match selections.first() {
14501 Some(first) if selections.len() >= 2 => {
14502 self.change_selections(Default::default(), window, cx, |s| {
14503 s.select_ranges([first.range()]);
14504 });
14505 }
14506 _ => self.select_next(
14507 &SelectNext {
14508 replace_newest: true,
14509 },
14510 window,
14511 cx,
14512 )?,
14513 }
14514 Ok(())
14515 }
14516
14517 pub fn find_previous_match(
14518 &mut self,
14519 _: &FindPreviousMatch,
14520 window: &mut Window,
14521 cx: &mut Context<Self>,
14522 ) -> Result<()> {
14523 let selections = self.selections.disjoint_anchors();
14524 match selections.last() {
14525 Some(last) if selections.len() >= 2 => {
14526 self.change_selections(Default::default(), window, cx, |s| {
14527 s.select_ranges([last.range()]);
14528 });
14529 }
14530 _ => self.select_previous(
14531 &SelectPrevious {
14532 replace_newest: true,
14533 },
14534 window,
14535 cx,
14536 )?,
14537 }
14538 Ok(())
14539 }
14540
14541 pub fn toggle_comments(
14542 &mut self,
14543 action: &ToggleComments,
14544 window: &mut Window,
14545 cx: &mut Context<Self>,
14546 ) {
14547 if self.read_only(cx) {
14548 return;
14549 }
14550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14551 let text_layout_details = &self.text_layout_details(window);
14552 self.transact(window, cx, |this, window, cx| {
14553 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14554 let mut edits = Vec::new();
14555 let mut selection_edit_ranges = Vec::new();
14556 let mut last_toggled_row = None;
14557 let snapshot = this.buffer.read(cx).read(cx);
14558 let empty_str: Arc<str> = Arc::default();
14559 let mut suffixes_inserted = Vec::new();
14560 let ignore_indent = action.ignore_indent;
14561
14562 fn comment_prefix_range(
14563 snapshot: &MultiBufferSnapshot,
14564 row: MultiBufferRow,
14565 comment_prefix: &str,
14566 comment_prefix_whitespace: &str,
14567 ignore_indent: bool,
14568 ) -> Range<Point> {
14569 let indent_size = if ignore_indent {
14570 0
14571 } else {
14572 snapshot.indent_size_for_line(row).len
14573 };
14574
14575 let start = Point::new(row.0, indent_size);
14576
14577 let mut line_bytes = snapshot
14578 .bytes_in_range(start..snapshot.max_point())
14579 .flatten()
14580 .copied();
14581
14582 // If this line currently begins with the line comment prefix, then record
14583 // the range containing the prefix.
14584 if line_bytes
14585 .by_ref()
14586 .take(comment_prefix.len())
14587 .eq(comment_prefix.bytes())
14588 {
14589 // Include any whitespace that matches the comment prefix.
14590 let matching_whitespace_len = line_bytes
14591 .zip(comment_prefix_whitespace.bytes())
14592 .take_while(|(a, b)| a == b)
14593 .count() as u32;
14594 let end = Point::new(
14595 start.row,
14596 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14597 );
14598 start..end
14599 } else {
14600 start..start
14601 }
14602 }
14603
14604 fn comment_suffix_range(
14605 snapshot: &MultiBufferSnapshot,
14606 row: MultiBufferRow,
14607 comment_suffix: &str,
14608 comment_suffix_has_leading_space: bool,
14609 ) -> Range<Point> {
14610 let end = Point::new(row.0, snapshot.line_len(row));
14611 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14612
14613 let mut line_end_bytes = snapshot
14614 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14615 .flatten()
14616 .copied();
14617
14618 let leading_space_len = if suffix_start_column > 0
14619 && line_end_bytes.next() == Some(b' ')
14620 && comment_suffix_has_leading_space
14621 {
14622 1
14623 } else {
14624 0
14625 };
14626
14627 // If this line currently begins with the line comment prefix, then record
14628 // the range containing the prefix.
14629 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14630 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14631 start..end
14632 } else {
14633 end..end
14634 }
14635 }
14636
14637 // TODO: Handle selections that cross excerpts
14638 for selection in &mut selections {
14639 let start_column = snapshot
14640 .indent_size_for_line(MultiBufferRow(selection.start.row))
14641 .len;
14642 let language = if let Some(language) =
14643 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14644 {
14645 language
14646 } else {
14647 continue;
14648 };
14649
14650 selection_edit_ranges.clear();
14651
14652 // If multiple selections contain a given row, avoid processing that
14653 // row more than once.
14654 let mut start_row = MultiBufferRow(selection.start.row);
14655 if last_toggled_row == Some(start_row) {
14656 start_row = start_row.next_row();
14657 }
14658 let end_row =
14659 if selection.end.row > selection.start.row && selection.end.column == 0 {
14660 MultiBufferRow(selection.end.row - 1)
14661 } else {
14662 MultiBufferRow(selection.end.row)
14663 };
14664 last_toggled_row = Some(end_row);
14665
14666 if start_row > end_row {
14667 continue;
14668 }
14669
14670 // If the language has line comments, toggle those.
14671 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14672
14673 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14674 if ignore_indent {
14675 full_comment_prefixes = full_comment_prefixes
14676 .into_iter()
14677 .map(|s| Arc::from(s.trim_end()))
14678 .collect();
14679 }
14680
14681 if !full_comment_prefixes.is_empty() {
14682 let first_prefix = full_comment_prefixes
14683 .first()
14684 .expect("prefixes is non-empty");
14685 let prefix_trimmed_lengths = full_comment_prefixes
14686 .iter()
14687 .map(|p| p.trim_end_matches(' ').len())
14688 .collect::<SmallVec<[usize; 4]>>();
14689
14690 let mut all_selection_lines_are_comments = true;
14691
14692 for row in start_row.0..=end_row.0 {
14693 let row = MultiBufferRow(row);
14694 if start_row < end_row && snapshot.is_line_blank(row) {
14695 continue;
14696 }
14697
14698 let prefix_range = full_comment_prefixes
14699 .iter()
14700 .zip(prefix_trimmed_lengths.iter().copied())
14701 .map(|(prefix, trimmed_prefix_len)| {
14702 comment_prefix_range(
14703 snapshot.deref(),
14704 row,
14705 &prefix[..trimmed_prefix_len],
14706 &prefix[trimmed_prefix_len..],
14707 ignore_indent,
14708 )
14709 })
14710 .max_by_key(|range| range.end.column - range.start.column)
14711 .expect("prefixes is non-empty");
14712
14713 if prefix_range.is_empty() {
14714 all_selection_lines_are_comments = false;
14715 }
14716
14717 selection_edit_ranges.push(prefix_range);
14718 }
14719
14720 if all_selection_lines_are_comments {
14721 edits.extend(
14722 selection_edit_ranges
14723 .iter()
14724 .cloned()
14725 .map(|range| (range, empty_str.clone())),
14726 );
14727 } else {
14728 let min_column = selection_edit_ranges
14729 .iter()
14730 .map(|range| range.start.column)
14731 .min()
14732 .unwrap_or(0);
14733 edits.extend(selection_edit_ranges.iter().map(|range| {
14734 let position = Point::new(range.start.row, min_column);
14735 (position..position, first_prefix.clone())
14736 }));
14737 }
14738 } else if let Some(BlockCommentConfig {
14739 start: full_comment_prefix,
14740 end: comment_suffix,
14741 ..
14742 }) = language.block_comment()
14743 {
14744 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14745 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14746 let prefix_range = comment_prefix_range(
14747 snapshot.deref(),
14748 start_row,
14749 comment_prefix,
14750 comment_prefix_whitespace,
14751 ignore_indent,
14752 );
14753 let suffix_range = comment_suffix_range(
14754 snapshot.deref(),
14755 end_row,
14756 comment_suffix.trim_start_matches(' '),
14757 comment_suffix.starts_with(' '),
14758 );
14759
14760 if prefix_range.is_empty() || suffix_range.is_empty() {
14761 edits.push((
14762 prefix_range.start..prefix_range.start,
14763 full_comment_prefix.clone(),
14764 ));
14765 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14766 suffixes_inserted.push((end_row, comment_suffix.len()));
14767 } else {
14768 edits.push((prefix_range, empty_str.clone()));
14769 edits.push((suffix_range, empty_str.clone()));
14770 }
14771 } else {
14772 continue;
14773 }
14774 }
14775
14776 drop(snapshot);
14777 this.buffer.update(cx, |buffer, cx| {
14778 buffer.edit(edits, None, cx);
14779 });
14780
14781 // Adjust selections so that they end before any comment suffixes that
14782 // were inserted.
14783 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14784 let mut selections = this.selections.all::<Point>(cx);
14785 let snapshot = this.buffer.read(cx).read(cx);
14786 for selection in &mut selections {
14787 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14788 match row.cmp(&MultiBufferRow(selection.end.row)) {
14789 Ordering::Less => {
14790 suffixes_inserted.next();
14791 continue;
14792 }
14793 Ordering::Greater => break,
14794 Ordering::Equal => {
14795 if selection.end.column == snapshot.line_len(row) {
14796 if selection.is_empty() {
14797 selection.start.column -= suffix_len as u32;
14798 }
14799 selection.end.column -= suffix_len as u32;
14800 }
14801 break;
14802 }
14803 }
14804 }
14805 }
14806
14807 drop(snapshot);
14808 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14809
14810 let selections = this.selections.all::<Point>(cx);
14811 let selections_on_single_row = selections.windows(2).all(|selections| {
14812 selections[0].start.row == selections[1].start.row
14813 && selections[0].end.row == selections[1].end.row
14814 && selections[0].start.row == selections[0].end.row
14815 });
14816 let selections_selecting = selections
14817 .iter()
14818 .any(|selection| selection.start != selection.end);
14819 let advance_downwards = action.advance_downwards
14820 && selections_on_single_row
14821 && !selections_selecting
14822 && !matches!(this.mode, EditorMode::SingleLine);
14823
14824 if advance_downwards {
14825 let snapshot = this.buffer.read(cx).snapshot(cx);
14826
14827 this.change_selections(Default::default(), window, cx, |s| {
14828 s.move_cursors_with(|display_snapshot, display_point, _| {
14829 let mut point = display_point.to_point(display_snapshot);
14830 point.row += 1;
14831 point = snapshot.clip_point(point, Bias::Left);
14832 let display_point = point.to_display_point(display_snapshot);
14833 let goal = SelectionGoal::HorizontalPosition(
14834 display_snapshot
14835 .x_for_display_point(display_point, text_layout_details)
14836 .into(),
14837 );
14838 (display_point, goal)
14839 })
14840 });
14841 }
14842 });
14843 }
14844
14845 pub fn select_enclosing_symbol(
14846 &mut self,
14847 _: &SelectEnclosingSymbol,
14848 window: &mut Window,
14849 cx: &mut Context<Self>,
14850 ) {
14851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14852
14853 let buffer = self.buffer.read(cx).snapshot(cx);
14854 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14855
14856 fn update_selection(
14857 selection: &Selection<usize>,
14858 buffer_snap: &MultiBufferSnapshot,
14859 ) -> Option<Selection<usize>> {
14860 let cursor = selection.head();
14861 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14862 for symbol in symbols.iter().rev() {
14863 let start = symbol.range.start.to_offset(buffer_snap);
14864 let end = symbol.range.end.to_offset(buffer_snap);
14865 let new_range = start..end;
14866 if start < selection.start || end > selection.end {
14867 return Some(Selection {
14868 id: selection.id,
14869 start: new_range.start,
14870 end: new_range.end,
14871 goal: SelectionGoal::None,
14872 reversed: selection.reversed,
14873 });
14874 }
14875 }
14876 None
14877 }
14878
14879 let mut selected_larger_symbol = false;
14880 let new_selections = old_selections
14881 .iter()
14882 .map(|selection| match update_selection(selection, &buffer) {
14883 Some(new_selection) => {
14884 if new_selection.range() != selection.range() {
14885 selected_larger_symbol = true;
14886 }
14887 new_selection
14888 }
14889 None => selection.clone(),
14890 })
14891 .collect::<Vec<_>>();
14892
14893 if selected_larger_symbol {
14894 self.change_selections(Default::default(), window, cx, |s| {
14895 s.select(new_selections);
14896 });
14897 }
14898 }
14899
14900 pub fn select_larger_syntax_node(
14901 &mut self,
14902 _: &SelectLargerSyntaxNode,
14903 window: &mut Window,
14904 cx: &mut Context<Self>,
14905 ) {
14906 let Some(visible_row_count) = self.visible_row_count() else {
14907 return;
14908 };
14909 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14910 if old_selections.is_empty() {
14911 return;
14912 }
14913
14914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14915
14916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14917 let buffer = self.buffer.read(cx).snapshot(cx);
14918
14919 let mut selected_larger_node = false;
14920 let mut new_selections = old_selections
14921 .iter()
14922 .map(|selection| {
14923 let old_range = selection.start..selection.end;
14924
14925 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14926 // manually select word at selection
14927 if ["string_content", "inline"].contains(&node.kind()) {
14928 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14929 // ignore if word is already selected
14930 if !word_range.is_empty() && old_range != word_range {
14931 let (last_word_range, _) =
14932 buffer.surrounding_word(old_range.end, false);
14933 // only select word if start and end point belongs to same word
14934 if word_range == last_word_range {
14935 selected_larger_node = true;
14936 return Selection {
14937 id: selection.id,
14938 start: word_range.start,
14939 end: word_range.end,
14940 goal: SelectionGoal::None,
14941 reversed: selection.reversed,
14942 };
14943 }
14944 }
14945 }
14946 }
14947
14948 let mut new_range = old_range.clone();
14949 while let Some((_node, containing_range)) =
14950 buffer.syntax_ancestor(new_range.clone())
14951 {
14952 new_range = match containing_range {
14953 MultiOrSingleBufferOffsetRange::Single(_) => break,
14954 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14955 };
14956 if !display_map.intersects_fold(new_range.start)
14957 && !display_map.intersects_fold(new_range.end)
14958 {
14959 break;
14960 }
14961 }
14962
14963 selected_larger_node |= new_range != old_range;
14964 Selection {
14965 id: selection.id,
14966 start: new_range.start,
14967 end: new_range.end,
14968 goal: SelectionGoal::None,
14969 reversed: selection.reversed,
14970 }
14971 })
14972 .collect::<Vec<_>>();
14973
14974 if !selected_larger_node {
14975 return; // don't put this call in the history
14976 }
14977
14978 // scroll based on transformation done to the last selection created by the user
14979 let (last_old, last_new) = old_selections
14980 .last()
14981 .zip(new_selections.last().cloned())
14982 .expect("old_selections isn't empty");
14983
14984 // revert selection
14985 let is_selection_reversed = {
14986 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14987 new_selections.last_mut().expect("checked above").reversed =
14988 should_newest_selection_be_reversed;
14989 should_newest_selection_be_reversed
14990 };
14991
14992 if selected_larger_node {
14993 self.select_syntax_node_history.disable_clearing = true;
14994 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14995 s.select(new_selections.clone());
14996 });
14997 self.select_syntax_node_history.disable_clearing = false;
14998 }
14999
15000 let start_row = last_new.start.to_display_point(&display_map).row().0;
15001 let end_row = last_new.end.to_display_point(&display_map).row().0;
15002 let selection_height = end_row - start_row + 1;
15003 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15004
15005 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15006 let scroll_behavior = if fits_on_the_screen {
15007 self.request_autoscroll(Autoscroll::fit(), cx);
15008 SelectSyntaxNodeScrollBehavior::FitSelection
15009 } else if is_selection_reversed {
15010 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15011 SelectSyntaxNodeScrollBehavior::CursorTop
15012 } else {
15013 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15014 SelectSyntaxNodeScrollBehavior::CursorBottom
15015 };
15016
15017 self.select_syntax_node_history.push((
15018 old_selections,
15019 scroll_behavior,
15020 is_selection_reversed,
15021 ));
15022 }
15023
15024 pub fn select_smaller_syntax_node(
15025 &mut self,
15026 _: &SelectSmallerSyntaxNode,
15027 window: &mut Window,
15028 cx: &mut Context<Self>,
15029 ) {
15030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15031
15032 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15033 self.select_syntax_node_history.pop()
15034 {
15035 if let Some(selection) = selections.last_mut() {
15036 selection.reversed = is_selection_reversed;
15037 }
15038
15039 self.select_syntax_node_history.disable_clearing = true;
15040 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15041 s.select(selections.to_vec());
15042 });
15043 self.select_syntax_node_history.disable_clearing = false;
15044
15045 match scroll_behavior {
15046 SelectSyntaxNodeScrollBehavior::CursorTop => {
15047 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15048 }
15049 SelectSyntaxNodeScrollBehavior::FitSelection => {
15050 self.request_autoscroll(Autoscroll::fit(), cx);
15051 }
15052 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15053 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15054 }
15055 }
15056 }
15057 }
15058
15059 pub fn unwrap_syntax_node(
15060 &mut self,
15061 _: &UnwrapSyntaxNode,
15062 window: &mut Window,
15063 cx: &mut Context<Self>,
15064 ) {
15065 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15066
15067 let buffer = self.buffer.read(cx).snapshot(cx);
15068 let selections = self
15069 .selections
15070 .all::<usize>(cx)
15071 .into_iter()
15072 // subtracting the offset requires sorting
15073 .sorted_by_key(|i| i.start);
15074
15075 let full_edits = selections
15076 .into_iter()
15077 .filter_map(|selection| {
15078 // Only requires two branches once if-let-chains stabilize (#53667)
15079 let child = if !selection.is_empty() {
15080 selection.range()
15081 } else if let Some((_, ancestor_range)) =
15082 buffer.syntax_ancestor(selection.start..selection.end)
15083 {
15084 match ancestor_range {
15085 MultiOrSingleBufferOffsetRange::Single(range) => range,
15086 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15087 }
15088 } else {
15089 selection.range()
15090 };
15091
15092 let mut parent = child.clone();
15093 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15094 parent = match ancestor_range {
15095 MultiOrSingleBufferOffsetRange::Single(range) => range,
15096 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15097 };
15098 if parent.start < child.start || parent.end > child.end {
15099 break;
15100 }
15101 }
15102
15103 if parent == child {
15104 return None;
15105 }
15106 let text = buffer.text_for_range(child).collect::<String>();
15107 Some((selection.id, parent, text))
15108 })
15109 .collect::<Vec<_>>();
15110
15111 self.transact(window, cx, |this, window, cx| {
15112 this.buffer.update(cx, |buffer, cx| {
15113 buffer.edit(
15114 full_edits
15115 .iter()
15116 .map(|(_, p, t)| (p.clone(), t.clone()))
15117 .collect::<Vec<_>>(),
15118 None,
15119 cx,
15120 );
15121 });
15122 this.change_selections(Default::default(), window, cx, |s| {
15123 let mut offset = 0;
15124 let mut selections = vec![];
15125 for (id, parent, text) in full_edits {
15126 let start = parent.start - offset;
15127 offset += parent.len() - text.len();
15128 selections.push(Selection {
15129 id,
15130 start,
15131 end: start + text.len(),
15132 reversed: false,
15133 goal: Default::default(),
15134 });
15135 }
15136 s.select(selections);
15137 });
15138 });
15139 }
15140
15141 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15142 if !EditorSettings::get_global(cx).gutter.runnables {
15143 self.clear_tasks();
15144 return Task::ready(());
15145 }
15146 let project = self.project().map(Entity::downgrade);
15147 let task_sources = self.lsp_task_sources(cx);
15148 let multi_buffer = self.buffer.downgrade();
15149 cx.spawn_in(window, async move |editor, cx| {
15150 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15151 let Some(project) = project.and_then(|p| p.upgrade()) else {
15152 return;
15153 };
15154 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15155 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15156 }) else {
15157 return;
15158 };
15159
15160 let hide_runnables = project
15161 .update(cx, |project, _| project.is_via_collab())
15162 .unwrap_or(true);
15163 if hide_runnables {
15164 return;
15165 }
15166 let new_rows =
15167 cx.background_spawn({
15168 let snapshot = display_snapshot.clone();
15169 async move {
15170 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15171 }
15172 })
15173 .await;
15174 let Ok(lsp_tasks) =
15175 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15176 else {
15177 return;
15178 };
15179 let lsp_tasks = lsp_tasks.await;
15180
15181 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15182 lsp_tasks
15183 .into_iter()
15184 .flat_map(|(kind, tasks)| {
15185 tasks.into_iter().filter_map(move |(location, task)| {
15186 Some((kind.clone(), location?, task))
15187 })
15188 })
15189 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15190 let buffer = location.target.buffer;
15191 let buffer_snapshot = buffer.read(cx).snapshot();
15192 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15193 |(excerpt_id, snapshot, _)| {
15194 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15195 display_snapshot
15196 .buffer_snapshot
15197 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15198 } else {
15199 None
15200 }
15201 },
15202 );
15203 if let Some(offset) = offset {
15204 let task_buffer_range =
15205 location.target.range.to_point(&buffer_snapshot);
15206 let context_buffer_range =
15207 task_buffer_range.to_offset(&buffer_snapshot);
15208 let context_range = BufferOffset(context_buffer_range.start)
15209 ..BufferOffset(context_buffer_range.end);
15210
15211 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15212 .or_insert_with(|| RunnableTasks {
15213 templates: Vec::new(),
15214 offset,
15215 column: task_buffer_range.start.column,
15216 extra_variables: HashMap::default(),
15217 context_range,
15218 })
15219 .templates
15220 .push((kind, task.original_task().clone()));
15221 }
15222
15223 acc
15224 })
15225 }) else {
15226 return;
15227 };
15228
15229 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15230 buffer.language_settings(cx).tasks.prefer_lsp
15231 }) else {
15232 return;
15233 };
15234
15235 let rows = Self::runnable_rows(
15236 project,
15237 display_snapshot,
15238 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15239 new_rows,
15240 cx.clone(),
15241 )
15242 .await;
15243 editor
15244 .update(cx, |editor, _| {
15245 editor.clear_tasks();
15246 for (key, mut value) in rows {
15247 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15248 value.templates.extend(lsp_tasks.templates);
15249 }
15250
15251 editor.insert_tasks(key, value);
15252 }
15253 for (key, value) in lsp_tasks_by_rows {
15254 editor.insert_tasks(key, value);
15255 }
15256 })
15257 .ok();
15258 })
15259 }
15260 fn fetch_runnable_ranges(
15261 snapshot: &DisplaySnapshot,
15262 range: Range<Anchor>,
15263 ) -> Vec<language::RunnableRange> {
15264 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15265 }
15266
15267 fn runnable_rows(
15268 project: Entity<Project>,
15269 snapshot: DisplaySnapshot,
15270 prefer_lsp: bool,
15271 runnable_ranges: Vec<RunnableRange>,
15272 cx: AsyncWindowContext,
15273 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15274 cx.spawn(async move |cx| {
15275 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15276 for mut runnable in runnable_ranges {
15277 let Some(tasks) = cx
15278 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15279 .ok()
15280 else {
15281 continue;
15282 };
15283 let mut tasks = tasks.await;
15284
15285 if prefer_lsp {
15286 tasks.retain(|(task_kind, _)| {
15287 !matches!(task_kind, TaskSourceKind::Language { .. })
15288 });
15289 }
15290 if tasks.is_empty() {
15291 continue;
15292 }
15293
15294 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15295 let Some(row) = snapshot
15296 .buffer_snapshot
15297 .buffer_line_for_row(MultiBufferRow(point.row))
15298 .map(|(_, range)| range.start.row)
15299 else {
15300 continue;
15301 };
15302
15303 let context_range =
15304 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15305 runnable_rows.push((
15306 (runnable.buffer_id, row),
15307 RunnableTasks {
15308 templates: tasks,
15309 offset: snapshot
15310 .buffer_snapshot
15311 .anchor_before(runnable.run_range.start),
15312 context_range,
15313 column: point.column,
15314 extra_variables: runnable.extra_captures,
15315 },
15316 ));
15317 }
15318 runnable_rows
15319 })
15320 }
15321
15322 fn templates_with_tags(
15323 project: &Entity<Project>,
15324 runnable: &mut Runnable,
15325 cx: &mut App,
15326 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15327 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15328 let (worktree_id, file) = project
15329 .buffer_for_id(runnable.buffer, cx)
15330 .and_then(|buffer| buffer.read(cx).file())
15331 .map(|file| (file.worktree_id(cx), file.clone()))
15332 .unzip();
15333
15334 (
15335 project.task_store().read(cx).task_inventory().cloned(),
15336 worktree_id,
15337 file,
15338 )
15339 });
15340
15341 let tags = mem::take(&mut runnable.tags);
15342 let language = runnable.language.clone();
15343 cx.spawn(async move |cx| {
15344 let mut templates_with_tags = Vec::new();
15345 if let Some(inventory) = inventory {
15346 for RunnableTag(tag) in tags {
15347 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15348 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15349 }) else {
15350 return templates_with_tags;
15351 };
15352 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15353 move |(_, template)| {
15354 template.tags.iter().any(|source_tag| source_tag == &tag)
15355 },
15356 ));
15357 }
15358 }
15359 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15360
15361 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15362 // Strongest source wins; if we have worktree tag binding, prefer that to
15363 // global and language bindings;
15364 // if we have a global binding, prefer that to language binding.
15365 let first_mismatch = templates_with_tags
15366 .iter()
15367 .position(|(tag_source, _)| tag_source != leading_tag_source);
15368 if let Some(index) = first_mismatch {
15369 templates_with_tags.truncate(index);
15370 }
15371 }
15372
15373 templates_with_tags
15374 })
15375 }
15376
15377 pub fn move_to_enclosing_bracket(
15378 &mut self,
15379 _: &MoveToEnclosingBracket,
15380 window: &mut Window,
15381 cx: &mut Context<Self>,
15382 ) {
15383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15384 self.change_selections(Default::default(), window, cx, |s| {
15385 s.move_offsets_with(|snapshot, selection| {
15386 let Some(enclosing_bracket_ranges) =
15387 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15388 else {
15389 return;
15390 };
15391
15392 let mut best_length = usize::MAX;
15393 let mut best_inside = false;
15394 let mut best_in_bracket_range = false;
15395 let mut best_destination = None;
15396 for (open, close) in enclosing_bracket_ranges {
15397 let close = close.to_inclusive();
15398 let length = close.end() - open.start;
15399 let inside = selection.start >= open.end && selection.end <= *close.start();
15400 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15401 || close.contains(&selection.head());
15402
15403 // If best is next to a bracket and current isn't, skip
15404 if !in_bracket_range && best_in_bracket_range {
15405 continue;
15406 }
15407
15408 // Prefer smaller lengths unless best is inside and current isn't
15409 if length > best_length && (best_inside || !inside) {
15410 continue;
15411 }
15412
15413 best_length = length;
15414 best_inside = inside;
15415 best_in_bracket_range = in_bracket_range;
15416 best_destination = Some(
15417 if close.contains(&selection.start) && close.contains(&selection.end) {
15418 if inside { open.end } else { open.start }
15419 } else if inside {
15420 *close.start()
15421 } else {
15422 *close.end()
15423 },
15424 );
15425 }
15426
15427 if let Some(destination) = best_destination {
15428 selection.collapse_to(destination, SelectionGoal::None);
15429 }
15430 })
15431 });
15432 }
15433
15434 pub fn undo_selection(
15435 &mut self,
15436 _: &UndoSelection,
15437 window: &mut Window,
15438 cx: &mut Context<Self>,
15439 ) {
15440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15441 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15442 self.selection_history.mode = SelectionHistoryMode::Undoing;
15443 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15444 this.end_selection(window, cx);
15445 this.change_selections(
15446 SelectionEffects::scroll(Autoscroll::newest()),
15447 window,
15448 cx,
15449 |s| s.select_anchors(entry.selections.to_vec()),
15450 );
15451 });
15452 self.selection_history.mode = SelectionHistoryMode::Normal;
15453
15454 self.select_next_state = entry.select_next_state;
15455 self.select_prev_state = entry.select_prev_state;
15456 self.add_selections_state = entry.add_selections_state;
15457 }
15458 }
15459
15460 pub fn redo_selection(
15461 &mut self,
15462 _: &RedoSelection,
15463 window: &mut Window,
15464 cx: &mut Context<Self>,
15465 ) {
15466 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15467 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15468 self.selection_history.mode = SelectionHistoryMode::Redoing;
15469 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15470 this.end_selection(window, cx);
15471 this.change_selections(
15472 SelectionEffects::scroll(Autoscroll::newest()),
15473 window,
15474 cx,
15475 |s| s.select_anchors(entry.selections.to_vec()),
15476 );
15477 });
15478 self.selection_history.mode = SelectionHistoryMode::Normal;
15479
15480 self.select_next_state = entry.select_next_state;
15481 self.select_prev_state = entry.select_prev_state;
15482 self.add_selections_state = entry.add_selections_state;
15483 }
15484 }
15485
15486 pub fn expand_excerpts(
15487 &mut self,
15488 action: &ExpandExcerpts,
15489 _: &mut Window,
15490 cx: &mut Context<Self>,
15491 ) {
15492 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15493 }
15494
15495 pub fn expand_excerpts_down(
15496 &mut self,
15497 action: &ExpandExcerptsDown,
15498 _: &mut Window,
15499 cx: &mut Context<Self>,
15500 ) {
15501 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15502 }
15503
15504 pub fn expand_excerpts_up(
15505 &mut self,
15506 action: &ExpandExcerptsUp,
15507 _: &mut Window,
15508 cx: &mut Context<Self>,
15509 ) {
15510 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15511 }
15512
15513 pub fn expand_excerpts_for_direction(
15514 &mut self,
15515 lines: u32,
15516 direction: ExpandExcerptDirection,
15517
15518 cx: &mut Context<Self>,
15519 ) {
15520 let selections = self.selections.disjoint_anchors();
15521
15522 let lines = if lines == 0 {
15523 EditorSettings::get_global(cx).expand_excerpt_lines
15524 } else {
15525 lines
15526 };
15527
15528 self.buffer.update(cx, |buffer, cx| {
15529 let snapshot = buffer.snapshot(cx);
15530 let mut excerpt_ids = selections
15531 .iter()
15532 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15533 .collect::<Vec<_>>();
15534 excerpt_ids.sort();
15535 excerpt_ids.dedup();
15536 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15537 })
15538 }
15539
15540 pub fn expand_excerpt(
15541 &mut self,
15542 excerpt: ExcerptId,
15543 direction: ExpandExcerptDirection,
15544 window: &mut Window,
15545 cx: &mut Context<Self>,
15546 ) {
15547 let current_scroll_position = self.scroll_position(cx);
15548 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15549 let mut should_scroll_up = false;
15550
15551 if direction == ExpandExcerptDirection::Down {
15552 let multi_buffer = self.buffer.read(cx);
15553 let snapshot = multi_buffer.snapshot(cx);
15554 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15555 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15556 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15557 {
15558 let buffer_snapshot = buffer.read(cx).snapshot();
15559 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15560 let last_row = buffer_snapshot.max_point().row;
15561 let lines_below = last_row.saturating_sub(excerpt_end_row);
15562 should_scroll_up = lines_below >= lines_to_expand;
15563 }
15564 }
15565
15566 self.buffer.update(cx, |buffer, cx| {
15567 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15568 });
15569
15570 if should_scroll_up {
15571 let new_scroll_position =
15572 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15573 self.set_scroll_position(new_scroll_position, window, cx);
15574 }
15575 }
15576
15577 pub fn go_to_singleton_buffer_point(
15578 &mut self,
15579 point: Point,
15580 window: &mut Window,
15581 cx: &mut Context<Self>,
15582 ) {
15583 self.go_to_singleton_buffer_range(point..point, window, cx);
15584 }
15585
15586 pub fn go_to_singleton_buffer_range(
15587 &mut self,
15588 range: Range<Point>,
15589 window: &mut Window,
15590 cx: &mut Context<Self>,
15591 ) {
15592 let multibuffer = self.buffer().read(cx);
15593 let Some(buffer) = multibuffer.as_singleton() else {
15594 return;
15595 };
15596 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15597 return;
15598 };
15599 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15600 return;
15601 };
15602 self.change_selections(
15603 SelectionEffects::default().nav_history(true),
15604 window,
15605 cx,
15606 |s| s.select_anchor_ranges([start..end]),
15607 );
15608 }
15609
15610 pub fn go_to_diagnostic(
15611 &mut self,
15612 action: &GoToDiagnostic,
15613 window: &mut Window,
15614 cx: &mut Context<Self>,
15615 ) {
15616 if !self.diagnostics_enabled() {
15617 return;
15618 }
15619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15620 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15621 }
15622
15623 pub fn go_to_prev_diagnostic(
15624 &mut self,
15625 action: &GoToPreviousDiagnostic,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) {
15629 if !self.diagnostics_enabled() {
15630 return;
15631 }
15632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15633 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15634 }
15635
15636 pub fn go_to_diagnostic_impl(
15637 &mut self,
15638 direction: Direction,
15639 severity: GoToDiagnosticSeverityFilter,
15640 window: &mut Window,
15641 cx: &mut Context<Self>,
15642 ) {
15643 let buffer = self.buffer.read(cx).snapshot(cx);
15644 let selection = self.selections.newest::<usize>(cx);
15645
15646 let mut active_group_id = None;
15647 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15648 && active_group.active_range.start.to_offset(&buffer) == selection.start
15649 {
15650 active_group_id = Some(active_group.group_id);
15651 }
15652
15653 fn filtered(
15654 snapshot: EditorSnapshot,
15655 severity: GoToDiagnosticSeverityFilter,
15656 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15657 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15658 diagnostics
15659 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15660 .filter(|entry| entry.range.start != entry.range.end)
15661 .filter(|entry| !entry.diagnostic.is_unnecessary)
15662 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15663 }
15664
15665 let snapshot = self.snapshot(window, cx);
15666 let before = filtered(
15667 snapshot.clone(),
15668 severity,
15669 buffer
15670 .diagnostics_in_range(0..selection.start)
15671 .filter(|entry| entry.range.start <= selection.start),
15672 );
15673 let after = filtered(
15674 snapshot,
15675 severity,
15676 buffer
15677 .diagnostics_in_range(selection.start..buffer.len())
15678 .filter(|entry| entry.range.start >= selection.start),
15679 );
15680
15681 let mut found: Option<DiagnosticEntry<usize>> = None;
15682 if direction == Direction::Prev {
15683 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15684 {
15685 for diagnostic in prev_diagnostics.into_iter().rev() {
15686 if diagnostic.range.start != selection.start
15687 || active_group_id
15688 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15689 {
15690 found = Some(diagnostic);
15691 break 'outer;
15692 }
15693 }
15694 }
15695 } else {
15696 for diagnostic in after.chain(before) {
15697 if diagnostic.range.start != selection.start
15698 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15699 {
15700 found = Some(diagnostic);
15701 break;
15702 }
15703 }
15704 }
15705 let Some(next_diagnostic) = found else {
15706 return;
15707 };
15708
15709 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15710 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15711 return;
15712 };
15713 self.change_selections(Default::default(), window, cx, |s| {
15714 s.select_ranges(vec![
15715 next_diagnostic.range.start..next_diagnostic.range.start,
15716 ])
15717 });
15718 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15719 self.refresh_edit_prediction(false, true, window, cx);
15720 }
15721
15722 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15723 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15724 let snapshot = self.snapshot(window, cx);
15725 let selection = self.selections.newest::<Point>(cx);
15726 self.go_to_hunk_before_or_after_position(
15727 &snapshot,
15728 selection.head(),
15729 Direction::Next,
15730 window,
15731 cx,
15732 );
15733 }
15734
15735 pub fn go_to_hunk_before_or_after_position(
15736 &mut self,
15737 snapshot: &EditorSnapshot,
15738 position: Point,
15739 direction: Direction,
15740 window: &mut Window,
15741 cx: &mut Context<Editor>,
15742 ) {
15743 let row = if direction == Direction::Next {
15744 self.hunk_after_position(snapshot, position)
15745 .map(|hunk| hunk.row_range.start)
15746 } else {
15747 self.hunk_before_position(snapshot, position)
15748 };
15749
15750 if let Some(row) = row {
15751 let destination = Point::new(row.0, 0);
15752 let autoscroll = Autoscroll::center();
15753
15754 self.unfold_ranges(&[destination..destination], false, false, cx);
15755 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15756 s.select_ranges([destination..destination]);
15757 });
15758 }
15759 }
15760
15761 fn hunk_after_position(
15762 &mut self,
15763 snapshot: &EditorSnapshot,
15764 position: Point,
15765 ) -> Option<MultiBufferDiffHunk> {
15766 snapshot
15767 .buffer_snapshot
15768 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15769 .find(|hunk| hunk.row_range.start.0 > position.row)
15770 .or_else(|| {
15771 snapshot
15772 .buffer_snapshot
15773 .diff_hunks_in_range(Point::zero()..position)
15774 .find(|hunk| hunk.row_range.end.0 < position.row)
15775 })
15776 }
15777
15778 fn go_to_prev_hunk(
15779 &mut self,
15780 _: &GoToPreviousHunk,
15781 window: &mut Window,
15782 cx: &mut Context<Self>,
15783 ) {
15784 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15785 let snapshot = self.snapshot(window, cx);
15786 let selection = self.selections.newest::<Point>(cx);
15787 self.go_to_hunk_before_or_after_position(
15788 &snapshot,
15789 selection.head(),
15790 Direction::Prev,
15791 window,
15792 cx,
15793 );
15794 }
15795
15796 fn hunk_before_position(
15797 &mut self,
15798 snapshot: &EditorSnapshot,
15799 position: Point,
15800 ) -> Option<MultiBufferRow> {
15801 snapshot
15802 .buffer_snapshot
15803 .diff_hunk_before(position)
15804 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15805 }
15806
15807 fn go_to_next_change(
15808 &mut self,
15809 _: &GoToNextChange,
15810 window: &mut Window,
15811 cx: &mut Context<Self>,
15812 ) {
15813 if let Some(selections) = self
15814 .change_list
15815 .next_change(1, Direction::Next)
15816 .map(|s| s.to_vec())
15817 {
15818 self.change_selections(Default::default(), window, cx, |s| {
15819 let map = s.display_map();
15820 s.select_display_ranges(selections.iter().map(|a| {
15821 let point = a.to_display_point(&map);
15822 point..point
15823 }))
15824 })
15825 }
15826 }
15827
15828 fn go_to_previous_change(
15829 &mut self,
15830 _: &GoToPreviousChange,
15831 window: &mut Window,
15832 cx: &mut Context<Self>,
15833 ) {
15834 if let Some(selections) = self
15835 .change_list
15836 .next_change(1, Direction::Prev)
15837 .map(|s| s.to_vec())
15838 {
15839 self.change_selections(Default::default(), window, cx, |s| {
15840 let map = s.display_map();
15841 s.select_display_ranges(selections.iter().map(|a| {
15842 let point = a.to_display_point(&map);
15843 point..point
15844 }))
15845 })
15846 }
15847 }
15848
15849 fn go_to_line<T: 'static>(
15850 &mut self,
15851 position: Anchor,
15852 highlight_color: Option<Hsla>,
15853 window: &mut Window,
15854 cx: &mut Context<Self>,
15855 ) {
15856 let snapshot = self.snapshot(window, cx).display_snapshot;
15857 let position = position.to_point(&snapshot.buffer_snapshot);
15858 let start = snapshot
15859 .buffer_snapshot
15860 .clip_point(Point::new(position.row, 0), Bias::Left);
15861 let end = start + Point::new(1, 0);
15862 let start = snapshot.buffer_snapshot.anchor_before(start);
15863 let end = snapshot.buffer_snapshot.anchor_before(end);
15864
15865 self.highlight_rows::<T>(
15866 start..end,
15867 highlight_color
15868 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15869 Default::default(),
15870 cx,
15871 );
15872
15873 if self.buffer.read(cx).is_singleton() {
15874 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15875 }
15876 }
15877
15878 pub fn go_to_definition(
15879 &mut self,
15880 _: &GoToDefinition,
15881 window: &mut Window,
15882 cx: &mut Context<Self>,
15883 ) -> Task<Result<Navigated>> {
15884 let definition =
15885 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15886 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15887 cx.spawn_in(window, async move |editor, cx| {
15888 if definition.await? == Navigated::Yes {
15889 return Ok(Navigated::Yes);
15890 }
15891 match fallback_strategy {
15892 GoToDefinitionFallback::None => Ok(Navigated::No),
15893 GoToDefinitionFallback::FindAllReferences => {
15894 match editor.update_in(cx, |editor, window, cx| {
15895 editor.find_all_references(&FindAllReferences, window, cx)
15896 })? {
15897 Some(references) => references.await,
15898 None => Ok(Navigated::No),
15899 }
15900 }
15901 }
15902 })
15903 }
15904
15905 pub fn go_to_declaration(
15906 &mut self,
15907 _: &GoToDeclaration,
15908 window: &mut Window,
15909 cx: &mut Context<Self>,
15910 ) -> Task<Result<Navigated>> {
15911 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15912 }
15913
15914 pub fn go_to_declaration_split(
15915 &mut self,
15916 _: &GoToDeclaration,
15917 window: &mut Window,
15918 cx: &mut Context<Self>,
15919 ) -> Task<Result<Navigated>> {
15920 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15921 }
15922
15923 pub fn go_to_implementation(
15924 &mut self,
15925 _: &GoToImplementation,
15926 window: &mut Window,
15927 cx: &mut Context<Self>,
15928 ) -> Task<Result<Navigated>> {
15929 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15930 }
15931
15932 pub fn go_to_implementation_split(
15933 &mut self,
15934 _: &GoToImplementationSplit,
15935 window: &mut Window,
15936 cx: &mut Context<Self>,
15937 ) -> Task<Result<Navigated>> {
15938 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15939 }
15940
15941 pub fn go_to_type_definition(
15942 &mut self,
15943 _: &GoToTypeDefinition,
15944 window: &mut Window,
15945 cx: &mut Context<Self>,
15946 ) -> Task<Result<Navigated>> {
15947 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15948 }
15949
15950 pub fn go_to_definition_split(
15951 &mut self,
15952 _: &GoToDefinitionSplit,
15953 window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) -> Task<Result<Navigated>> {
15956 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15957 }
15958
15959 pub fn go_to_type_definition_split(
15960 &mut self,
15961 _: &GoToTypeDefinitionSplit,
15962 window: &mut Window,
15963 cx: &mut Context<Self>,
15964 ) -> Task<Result<Navigated>> {
15965 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15966 }
15967
15968 fn go_to_definition_of_kind(
15969 &mut self,
15970 kind: GotoDefinitionKind,
15971 split: bool,
15972 window: &mut Window,
15973 cx: &mut Context<Self>,
15974 ) -> Task<Result<Navigated>> {
15975 let Some(provider) = self.semantics_provider.clone() else {
15976 return Task::ready(Ok(Navigated::No));
15977 };
15978 let head = self.selections.newest::<usize>(cx).head();
15979 let buffer = self.buffer.read(cx);
15980 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
15981 return Task::ready(Ok(Navigated::No));
15982 };
15983 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15984 return Task::ready(Ok(Navigated::No));
15985 };
15986
15987 cx.spawn_in(window, async move |editor, cx| {
15988 let Some(definitions) = definitions.await? else {
15989 return Ok(Navigated::No);
15990 };
15991 let navigated = editor
15992 .update_in(cx, |editor, window, cx| {
15993 editor.navigate_to_hover_links(
15994 Some(kind),
15995 definitions
15996 .into_iter()
15997 .filter(|location| {
15998 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15999 })
16000 .map(HoverLink::Text)
16001 .collect::<Vec<_>>(),
16002 split,
16003 window,
16004 cx,
16005 )
16006 })?
16007 .await?;
16008 anyhow::Ok(navigated)
16009 })
16010 }
16011
16012 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16013 let selection = self.selections.newest_anchor();
16014 let head = selection.head();
16015 let tail = selection.tail();
16016
16017 let Some((buffer, start_position)) =
16018 self.buffer.read(cx).text_anchor_for_position(head, cx)
16019 else {
16020 return;
16021 };
16022
16023 let end_position = if head != tail {
16024 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16025 return;
16026 };
16027 Some(pos)
16028 } else {
16029 None
16030 };
16031
16032 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16033 let url = if let Some(end_pos) = end_position {
16034 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16035 } else {
16036 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16037 };
16038
16039 if let Some(url) = url {
16040 editor.update(cx, |_, cx| {
16041 cx.open_url(&url);
16042 })
16043 } else {
16044 Ok(())
16045 }
16046 });
16047
16048 url_finder.detach();
16049 }
16050
16051 pub fn open_selected_filename(
16052 &mut self,
16053 _: &OpenSelectedFilename,
16054 window: &mut Window,
16055 cx: &mut Context<Self>,
16056 ) {
16057 let Some(workspace) = self.workspace() else {
16058 return;
16059 };
16060
16061 let position = self.selections.newest_anchor().head();
16062
16063 let Some((buffer, buffer_position)) =
16064 self.buffer.read(cx).text_anchor_for_position(position, cx)
16065 else {
16066 return;
16067 };
16068
16069 let project = self.project.clone();
16070
16071 cx.spawn_in(window, async move |_, cx| {
16072 let result = find_file(&buffer, project, buffer_position, cx).await;
16073
16074 if let Some((_, path)) = result {
16075 workspace
16076 .update_in(cx, |workspace, window, cx| {
16077 workspace.open_resolved_path(path, window, cx)
16078 })?
16079 .await?;
16080 }
16081 anyhow::Ok(())
16082 })
16083 .detach();
16084 }
16085
16086 pub(crate) fn navigate_to_hover_links(
16087 &mut self,
16088 kind: Option<GotoDefinitionKind>,
16089 definitions: Vec<HoverLink>,
16090 split: bool,
16091 window: &mut Window,
16092 cx: &mut Context<Editor>,
16093 ) -> Task<Result<Navigated>> {
16094 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16095 let mut first_url_or_file = None;
16096 let definitions: Vec<_> = definitions
16097 .into_iter()
16098 .filter_map(|def| match def {
16099 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16100 HoverLink::InlayHint(lsp_location, server_id) => {
16101 let computation =
16102 self.compute_target_location(lsp_location, server_id, window, cx);
16103 Some(cx.background_spawn(computation))
16104 }
16105 HoverLink::Url(url) => {
16106 first_url_or_file = Some(Either::Left(url));
16107 None
16108 }
16109 HoverLink::File(path) => {
16110 first_url_or_file = Some(Either::Right(path));
16111 None
16112 }
16113 })
16114 .collect();
16115
16116 let workspace = self.workspace();
16117
16118 cx.spawn_in(window, async move |editor, acx| {
16119 let mut locations: Vec<Location> = future::join_all(definitions)
16120 .await
16121 .into_iter()
16122 .filter_map(|location| location.transpose())
16123 .collect::<Result<_>>()
16124 .context("location tasks")?;
16125
16126 if locations.len() > 1 {
16127 let Some(workspace) = workspace else {
16128 return Ok(Navigated::No);
16129 };
16130
16131 let tab_kind = match kind {
16132 Some(GotoDefinitionKind::Implementation) => "Implementations",
16133 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16134 Some(GotoDefinitionKind::Declaration) => "Declarations",
16135 Some(GotoDefinitionKind::Type) => "Types",
16136 };
16137 let title = editor
16138 .update_in(acx, |_, _, cx| {
16139 let target = locations
16140 .iter()
16141 .map(|location| {
16142 location
16143 .buffer
16144 .read(cx)
16145 .text_for_range(location.range.clone())
16146 .collect::<String>()
16147 })
16148 .filter(|text| !text.contains('\n'))
16149 .unique()
16150 .take(3)
16151 .join(", ");
16152 if target.is_empty() {
16153 tab_kind.to_owned()
16154 } else {
16155 format!("{tab_kind} for {target}")
16156 }
16157 })
16158 .context("buffer title")?;
16159
16160 let opened = workspace
16161 .update_in(acx, |workspace, window, cx| {
16162 Self::open_locations_in_multibuffer(
16163 workspace,
16164 locations,
16165 title,
16166 split,
16167 MultibufferSelectionMode::First,
16168 window,
16169 cx,
16170 )
16171 })
16172 .is_ok();
16173
16174 anyhow::Ok(Navigated::from_bool(opened))
16175 } else if locations.is_empty() {
16176 // If there is one definition, just open it directly
16177 match first_url_or_file {
16178 Some(Either::Left(url)) => {
16179 acx.update(|_, cx| cx.open_url(&url))?;
16180 Ok(Navigated::Yes)
16181 }
16182 Some(Either::Right(path)) => {
16183 let Some(workspace) = workspace else {
16184 return Ok(Navigated::No);
16185 };
16186
16187 workspace
16188 .update_in(acx, |workspace, window, cx| {
16189 workspace.open_resolved_path(path, window, cx)
16190 })?
16191 .await?;
16192 Ok(Navigated::Yes)
16193 }
16194 None => Ok(Navigated::No),
16195 }
16196 } else {
16197 let Some(workspace) = workspace else {
16198 return Ok(Navigated::No);
16199 };
16200
16201 let target = locations.pop().unwrap();
16202 editor.update_in(acx, |editor, window, cx| {
16203 let pane = workspace.read(cx).active_pane().clone();
16204
16205 let range = target.range.to_point(target.buffer.read(cx));
16206 let range = editor.range_for_match(&range);
16207 let range = collapse_multiline_range(range);
16208
16209 if !split
16210 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16211 {
16212 editor.go_to_singleton_buffer_range(range, window, cx);
16213 } else {
16214 window.defer(cx, move |window, cx| {
16215 let target_editor: Entity<Self> =
16216 workspace.update(cx, |workspace, cx| {
16217 let pane = if split {
16218 workspace.adjacent_pane(window, cx)
16219 } else {
16220 workspace.active_pane().clone()
16221 };
16222
16223 workspace.open_project_item(
16224 pane,
16225 target.buffer.clone(),
16226 true,
16227 true,
16228 window,
16229 cx,
16230 )
16231 });
16232 target_editor.update(cx, |target_editor, cx| {
16233 // When selecting a definition in a different buffer, disable the nav history
16234 // to avoid creating a history entry at the previous cursor location.
16235 pane.update(cx, |pane, _| pane.disable_history());
16236 target_editor.go_to_singleton_buffer_range(range, window, cx);
16237 pane.update(cx, |pane, _| pane.enable_history());
16238 });
16239 });
16240 }
16241 Navigated::Yes
16242 })
16243 }
16244 })
16245 }
16246
16247 fn compute_target_location(
16248 &self,
16249 lsp_location: lsp::Location,
16250 server_id: LanguageServerId,
16251 window: &mut Window,
16252 cx: &mut Context<Self>,
16253 ) -> Task<anyhow::Result<Option<Location>>> {
16254 let Some(project) = self.project.clone() else {
16255 return Task::ready(Ok(None));
16256 };
16257
16258 cx.spawn_in(window, async move |editor, cx| {
16259 let location_task = editor.update(cx, |_, cx| {
16260 project.update(cx, |project, cx| {
16261 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16262 })
16263 })?;
16264 let location = Some({
16265 let target_buffer_handle = location_task.await.context("open local buffer")?;
16266 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16267 let target_start = target_buffer
16268 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16269 let target_end = target_buffer
16270 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16271 target_buffer.anchor_after(target_start)
16272 ..target_buffer.anchor_before(target_end)
16273 })?;
16274 Location {
16275 buffer: target_buffer_handle,
16276 range,
16277 }
16278 });
16279 Ok(location)
16280 })
16281 }
16282
16283 pub fn find_all_references(
16284 &mut self,
16285 _: &FindAllReferences,
16286 window: &mut Window,
16287 cx: &mut Context<Self>,
16288 ) -> Option<Task<Result<Navigated>>> {
16289 let selection = self.selections.newest::<usize>(cx);
16290 let multi_buffer = self.buffer.read(cx);
16291 let head = selection.head();
16292
16293 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16294 let head_anchor = multi_buffer_snapshot.anchor_at(
16295 head,
16296 if head < selection.tail() {
16297 Bias::Right
16298 } else {
16299 Bias::Left
16300 },
16301 );
16302
16303 match self
16304 .find_all_references_task_sources
16305 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16306 {
16307 Ok(_) => {
16308 log::info!(
16309 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16310 );
16311 return None;
16312 }
16313 Err(i) => {
16314 self.find_all_references_task_sources.insert(i, head_anchor);
16315 }
16316 }
16317
16318 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16319 let workspace = self.workspace()?;
16320 let project = workspace.read(cx).project().clone();
16321 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16322 Some(cx.spawn_in(window, async move |editor, cx| {
16323 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16324 if let Ok(i) = editor
16325 .find_all_references_task_sources
16326 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16327 {
16328 editor.find_all_references_task_sources.remove(i);
16329 }
16330 });
16331
16332 let Some(locations) = references.await? else {
16333 return anyhow::Ok(Navigated::No);
16334 };
16335 if locations.is_empty() {
16336 return anyhow::Ok(Navigated::No);
16337 }
16338
16339 workspace.update_in(cx, |workspace, window, cx| {
16340 let target = locations
16341 .iter()
16342 .map(|location| {
16343 location
16344 .buffer
16345 .read(cx)
16346 .text_for_range(location.range.clone())
16347 .collect::<String>()
16348 })
16349 .filter(|text| !text.contains('\n'))
16350 .unique()
16351 .take(3)
16352 .join(", ");
16353 let title = if target.is_empty() {
16354 "References".to_owned()
16355 } else {
16356 format!("References to {target}")
16357 };
16358 Self::open_locations_in_multibuffer(
16359 workspace,
16360 locations,
16361 title,
16362 false,
16363 MultibufferSelectionMode::First,
16364 window,
16365 cx,
16366 );
16367 Navigated::Yes
16368 })
16369 }))
16370 }
16371
16372 /// Opens a multibuffer with the given project locations in it
16373 pub fn open_locations_in_multibuffer(
16374 workspace: &mut Workspace,
16375 mut locations: Vec<Location>,
16376 title: String,
16377 split: bool,
16378 multibuffer_selection_mode: MultibufferSelectionMode,
16379 window: &mut Window,
16380 cx: &mut Context<Workspace>,
16381 ) {
16382 if locations.is_empty() {
16383 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16384 return;
16385 }
16386
16387 // If there are multiple definitions, open them in a multibuffer
16388 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16389 let mut locations = locations.into_iter().peekable();
16390 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16391 let capability = workspace.project().read(cx).capability();
16392
16393 let excerpt_buffer = cx.new(|cx| {
16394 let mut multibuffer = MultiBuffer::new(capability);
16395 while let Some(location) = locations.next() {
16396 let buffer = location.buffer.read(cx);
16397 let mut ranges_for_buffer = Vec::new();
16398 let range = location.range.to_point(buffer);
16399 ranges_for_buffer.push(range.clone());
16400
16401 while let Some(next_location) = locations.peek() {
16402 if next_location.buffer == location.buffer {
16403 ranges_for_buffer.push(next_location.range.to_point(buffer));
16404 locations.next();
16405 } else {
16406 break;
16407 }
16408 }
16409
16410 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16411 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16412 PathKey::for_buffer(&location.buffer, cx),
16413 location.buffer.clone(),
16414 ranges_for_buffer,
16415 multibuffer_context_lines(cx),
16416 cx,
16417 );
16418 ranges.extend(new_ranges)
16419 }
16420
16421 multibuffer.with_title(title)
16422 });
16423
16424 let editor = cx.new(|cx| {
16425 Editor::for_multibuffer(
16426 excerpt_buffer,
16427 Some(workspace.project().clone()),
16428 window,
16429 cx,
16430 )
16431 });
16432 editor.update(cx, |editor, cx| {
16433 match multibuffer_selection_mode {
16434 MultibufferSelectionMode::First => {
16435 if let Some(first_range) = ranges.first() {
16436 editor.change_selections(
16437 SelectionEffects::no_scroll(),
16438 window,
16439 cx,
16440 |selections| {
16441 selections.clear_disjoint();
16442 selections
16443 .select_anchor_ranges(std::iter::once(first_range.clone()));
16444 },
16445 );
16446 }
16447 editor.highlight_background::<Self>(
16448 &ranges,
16449 |theme| theme.colors().editor_highlighted_line_background,
16450 cx,
16451 );
16452 }
16453 MultibufferSelectionMode::All => {
16454 editor.change_selections(
16455 SelectionEffects::no_scroll(),
16456 window,
16457 cx,
16458 |selections| {
16459 selections.clear_disjoint();
16460 selections.select_anchor_ranges(ranges);
16461 },
16462 );
16463 }
16464 }
16465 editor.register_buffers_with_language_servers(cx);
16466 });
16467
16468 let item = Box::new(editor);
16469 let item_id = item.item_id();
16470
16471 if split {
16472 workspace.split_item(SplitDirection::Right, item, window, cx);
16473 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16474 let (preview_item_id, preview_item_idx) =
16475 workspace.active_pane().read_with(cx, |pane, _| {
16476 (pane.preview_item_id(), pane.preview_item_idx())
16477 });
16478
16479 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16480
16481 if let Some(preview_item_id) = preview_item_id {
16482 workspace.active_pane().update(cx, |pane, cx| {
16483 pane.remove_item(preview_item_id, false, false, window, cx);
16484 });
16485 }
16486 } else {
16487 workspace.add_item_to_active_pane(item, None, true, window, cx);
16488 }
16489 workspace.active_pane().update(cx, |pane, cx| {
16490 pane.set_preview_item_id(Some(item_id), cx);
16491 });
16492 }
16493
16494 pub fn rename(
16495 &mut self,
16496 _: &Rename,
16497 window: &mut Window,
16498 cx: &mut Context<Self>,
16499 ) -> Option<Task<Result<()>>> {
16500 use language::ToOffset as _;
16501
16502 let provider = self.semantics_provider.clone()?;
16503 let selection = self.selections.newest_anchor().clone();
16504 let (cursor_buffer, cursor_buffer_position) = self
16505 .buffer
16506 .read(cx)
16507 .text_anchor_for_position(selection.head(), cx)?;
16508 let (tail_buffer, cursor_buffer_position_end) = self
16509 .buffer
16510 .read(cx)
16511 .text_anchor_for_position(selection.tail(), cx)?;
16512 if tail_buffer != cursor_buffer {
16513 return None;
16514 }
16515
16516 let snapshot = cursor_buffer.read(cx).snapshot();
16517 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16518 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16519 let prepare_rename = provider
16520 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16521 .unwrap_or_else(|| Task::ready(Ok(None)));
16522 drop(snapshot);
16523
16524 Some(cx.spawn_in(window, async move |this, cx| {
16525 let rename_range = if let Some(range) = prepare_rename.await? {
16526 Some(range)
16527 } else {
16528 this.update(cx, |this, cx| {
16529 let buffer = this.buffer.read(cx).snapshot(cx);
16530 let mut buffer_highlights = this
16531 .document_highlights_for_position(selection.head(), &buffer)
16532 .filter(|highlight| {
16533 highlight.start.excerpt_id == selection.head().excerpt_id
16534 && highlight.end.excerpt_id == selection.head().excerpt_id
16535 });
16536 buffer_highlights
16537 .next()
16538 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16539 })?
16540 };
16541 if let Some(rename_range) = rename_range {
16542 this.update_in(cx, |this, window, cx| {
16543 let snapshot = cursor_buffer.read(cx).snapshot();
16544 let rename_buffer_range = rename_range.to_offset(&snapshot);
16545 let cursor_offset_in_rename_range =
16546 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16547 let cursor_offset_in_rename_range_end =
16548 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16549
16550 this.take_rename(false, window, cx);
16551 let buffer = this.buffer.read(cx).read(cx);
16552 let cursor_offset = selection.head().to_offset(&buffer);
16553 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16554 let rename_end = rename_start + rename_buffer_range.len();
16555 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16556 let mut old_highlight_id = None;
16557 let old_name: Arc<str> = buffer
16558 .chunks(rename_start..rename_end, true)
16559 .map(|chunk| {
16560 if old_highlight_id.is_none() {
16561 old_highlight_id = chunk.syntax_highlight_id;
16562 }
16563 chunk.text
16564 })
16565 .collect::<String>()
16566 .into();
16567
16568 drop(buffer);
16569
16570 // Position the selection in the rename editor so that it matches the current selection.
16571 this.show_local_selections = false;
16572 let rename_editor = cx.new(|cx| {
16573 let mut editor = Editor::single_line(window, cx);
16574 editor.buffer.update(cx, |buffer, cx| {
16575 buffer.edit([(0..0, old_name.clone())], None, cx)
16576 });
16577 let rename_selection_range = match cursor_offset_in_rename_range
16578 .cmp(&cursor_offset_in_rename_range_end)
16579 {
16580 Ordering::Equal => {
16581 editor.select_all(&SelectAll, window, cx);
16582 return editor;
16583 }
16584 Ordering::Less => {
16585 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16586 }
16587 Ordering::Greater => {
16588 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16589 }
16590 };
16591 if rename_selection_range.end > old_name.len() {
16592 editor.select_all(&SelectAll, window, cx);
16593 } else {
16594 editor.change_selections(Default::default(), window, cx, |s| {
16595 s.select_ranges([rename_selection_range]);
16596 });
16597 }
16598 editor
16599 });
16600 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16601 if e == &EditorEvent::Focused {
16602 cx.emit(EditorEvent::FocusedIn)
16603 }
16604 })
16605 .detach();
16606
16607 let write_highlights =
16608 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16609 let read_highlights =
16610 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16611 let ranges = write_highlights
16612 .iter()
16613 .flat_map(|(_, ranges)| ranges.iter())
16614 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16615 .cloned()
16616 .collect();
16617
16618 this.highlight_text::<Rename>(
16619 ranges,
16620 HighlightStyle {
16621 fade_out: Some(0.6),
16622 ..Default::default()
16623 },
16624 cx,
16625 );
16626 let rename_focus_handle = rename_editor.focus_handle(cx);
16627 window.focus(&rename_focus_handle);
16628 let block_id = this.insert_blocks(
16629 [BlockProperties {
16630 style: BlockStyle::Flex,
16631 placement: BlockPlacement::Below(range.start),
16632 height: Some(1),
16633 render: Arc::new({
16634 let rename_editor = rename_editor.clone();
16635 move |cx: &mut BlockContext| {
16636 let mut text_style = cx.editor_style.text.clone();
16637 if let Some(highlight_style) = old_highlight_id
16638 .and_then(|h| h.style(&cx.editor_style.syntax))
16639 {
16640 text_style = text_style.highlight(highlight_style);
16641 }
16642 div()
16643 .block_mouse_except_scroll()
16644 .pl(cx.anchor_x)
16645 .child(EditorElement::new(
16646 &rename_editor,
16647 EditorStyle {
16648 background: cx.theme().system().transparent,
16649 local_player: cx.editor_style.local_player,
16650 text: text_style,
16651 scrollbar_width: cx.editor_style.scrollbar_width,
16652 syntax: cx.editor_style.syntax.clone(),
16653 status: cx.editor_style.status.clone(),
16654 inlay_hints_style: HighlightStyle {
16655 font_weight: Some(FontWeight::BOLD),
16656 ..make_inlay_hints_style(cx.app)
16657 },
16658 edit_prediction_styles: make_suggestion_styles(
16659 cx.app,
16660 ),
16661 ..EditorStyle::default()
16662 },
16663 ))
16664 .into_any_element()
16665 }
16666 }),
16667 priority: 0,
16668 }],
16669 Some(Autoscroll::fit()),
16670 cx,
16671 )[0];
16672 this.pending_rename = Some(RenameState {
16673 range,
16674 old_name,
16675 editor: rename_editor,
16676 block_id,
16677 });
16678 })?;
16679 }
16680
16681 Ok(())
16682 }))
16683 }
16684
16685 pub fn confirm_rename(
16686 &mut self,
16687 _: &ConfirmRename,
16688 window: &mut Window,
16689 cx: &mut Context<Self>,
16690 ) -> Option<Task<Result<()>>> {
16691 let rename = self.take_rename(false, window, cx)?;
16692 let workspace = self.workspace()?.downgrade();
16693 let (buffer, start) = self
16694 .buffer
16695 .read(cx)
16696 .text_anchor_for_position(rename.range.start, cx)?;
16697 let (end_buffer, _) = self
16698 .buffer
16699 .read(cx)
16700 .text_anchor_for_position(rename.range.end, cx)?;
16701 if buffer != end_buffer {
16702 return None;
16703 }
16704
16705 let old_name = rename.old_name;
16706 let new_name = rename.editor.read(cx).text(cx);
16707
16708 let rename = self.semantics_provider.as_ref()?.perform_rename(
16709 &buffer,
16710 start,
16711 new_name.clone(),
16712 cx,
16713 )?;
16714
16715 Some(cx.spawn_in(window, async move |editor, cx| {
16716 let project_transaction = rename.await?;
16717 Self::open_project_transaction(
16718 &editor,
16719 workspace,
16720 project_transaction,
16721 format!("Rename: {} → {}", old_name, new_name),
16722 cx,
16723 )
16724 .await?;
16725
16726 editor.update(cx, |editor, cx| {
16727 editor.refresh_document_highlights(cx);
16728 })?;
16729 Ok(())
16730 }))
16731 }
16732
16733 fn take_rename(
16734 &mut self,
16735 moving_cursor: bool,
16736 window: &mut Window,
16737 cx: &mut Context<Self>,
16738 ) -> Option<RenameState> {
16739 let rename = self.pending_rename.take()?;
16740 if rename.editor.focus_handle(cx).is_focused(window) {
16741 window.focus(&self.focus_handle);
16742 }
16743
16744 self.remove_blocks(
16745 [rename.block_id].into_iter().collect(),
16746 Some(Autoscroll::fit()),
16747 cx,
16748 );
16749 self.clear_highlights::<Rename>(cx);
16750 self.show_local_selections = true;
16751
16752 if moving_cursor {
16753 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16754 editor.selections.newest::<usize>(cx).head()
16755 });
16756
16757 // Update the selection to match the position of the selection inside
16758 // the rename editor.
16759 let snapshot = self.buffer.read(cx).read(cx);
16760 let rename_range = rename.range.to_offset(&snapshot);
16761 let cursor_in_editor = snapshot
16762 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16763 .min(rename_range.end);
16764 drop(snapshot);
16765
16766 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16767 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16768 });
16769 } else {
16770 self.refresh_document_highlights(cx);
16771 }
16772
16773 Some(rename)
16774 }
16775
16776 pub fn pending_rename(&self) -> Option<&RenameState> {
16777 self.pending_rename.as_ref()
16778 }
16779
16780 fn format(
16781 &mut self,
16782 _: &Format,
16783 window: &mut Window,
16784 cx: &mut Context<Self>,
16785 ) -> Option<Task<Result<()>>> {
16786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16787
16788 let project = match &self.project {
16789 Some(project) => project.clone(),
16790 None => return None,
16791 };
16792
16793 Some(self.perform_format(
16794 project,
16795 FormatTrigger::Manual,
16796 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16797 window,
16798 cx,
16799 ))
16800 }
16801
16802 fn format_selections(
16803 &mut self,
16804 _: &FormatSelections,
16805 window: &mut Window,
16806 cx: &mut Context<Self>,
16807 ) -> Option<Task<Result<()>>> {
16808 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16809
16810 let project = match &self.project {
16811 Some(project) => project.clone(),
16812 None => return None,
16813 };
16814
16815 let ranges = self
16816 .selections
16817 .all_adjusted(cx)
16818 .into_iter()
16819 .map(|selection| selection.range())
16820 .collect_vec();
16821
16822 Some(self.perform_format(
16823 project,
16824 FormatTrigger::Manual,
16825 FormatTarget::Ranges(ranges),
16826 window,
16827 cx,
16828 ))
16829 }
16830
16831 fn perform_format(
16832 &mut self,
16833 project: Entity<Project>,
16834 trigger: FormatTrigger,
16835 target: FormatTarget,
16836 window: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) -> Task<Result<()>> {
16839 let buffer = self.buffer.clone();
16840 let (buffers, target) = match target {
16841 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16842 FormatTarget::Ranges(selection_ranges) => {
16843 let multi_buffer = buffer.read(cx);
16844 let snapshot = multi_buffer.read(cx);
16845 let mut buffers = HashSet::default();
16846 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16847 BTreeMap::new();
16848 for selection_range in selection_ranges {
16849 for (buffer, buffer_range, _) in
16850 snapshot.range_to_buffer_ranges(selection_range)
16851 {
16852 let buffer_id = buffer.remote_id();
16853 let start = buffer.anchor_before(buffer_range.start);
16854 let end = buffer.anchor_after(buffer_range.end);
16855 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16856 buffer_id_to_ranges
16857 .entry(buffer_id)
16858 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16859 .or_insert_with(|| vec![start..end]);
16860 }
16861 }
16862 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16863 }
16864 };
16865
16866 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16867 let selections_prev = transaction_id_prev
16868 .and_then(|transaction_id_prev| {
16869 // default to selections as they were after the last edit, if we have them,
16870 // instead of how they are now.
16871 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16872 // will take you back to where you made the last edit, instead of staying where you scrolled
16873 self.selection_history
16874 .transaction(transaction_id_prev)
16875 .map(|t| t.0.clone())
16876 })
16877 .unwrap_or_else(|| self.selections.disjoint_anchors());
16878
16879 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16880 let format = project.update(cx, |project, cx| {
16881 project.format(buffers, target, true, trigger, cx)
16882 });
16883
16884 cx.spawn_in(window, async move |editor, cx| {
16885 let transaction = futures::select_biased! {
16886 transaction = format.log_err().fuse() => transaction,
16887 () = timeout => {
16888 log::warn!("timed out waiting for formatting");
16889 None
16890 }
16891 };
16892
16893 buffer
16894 .update(cx, |buffer, cx| {
16895 if let Some(transaction) = transaction
16896 && !buffer.is_singleton()
16897 {
16898 buffer.push_transaction(&transaction.0, cx);
16899 }
16900 cx.notify();
16901 })
16902 .ok();
16903
16904 if let Some(transaction_id_now) =
16905 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16906 {
16907 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16908 if has_new_transaction {
16909 _ = editor.update(cx, |editor, _| {
16910 editor
16911 .selection_history
16912 .insert_transaction(transaction_id_now, selections_prev);
16913 });
16914 }
16915 }
16916
16917 Ok(())
16918 })
16919 }
16920
16921 fn organize_imports(
16922 &mut self,
16923 _: &OrganizeImports,
16924 window: &mut Window,
16925 cx: &mut Context<Self>,
16926 ) -> Option<Task<Result<()>>> {
16927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16928 let project = match &self.project {
16929 Some(project) => project.clone(),
16930 None => return None,
16931 };
16932 Some(self.perform_code_action_kind(
16933 project,
16934 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16935 window,
16936 cx,
16937 ))
16938 }
16939
16940 fn perform_code_action_kind(
16941 &mut self,
16942 project: Entity<Project>,
16943 kind: CodeActionKind,
16944 window: &mut Window,
16945 cx: &mut Context<Self>,
16946 ) -> Task<Result<()>> {
16947 let buffer = self.buffer.clone();
16948 let buffers = buffer.read(cx).all_buffers();
16949 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16950 let apply_action = project.update(cx, |project, cx| {
16951 project.apply_code_action_kind(buffers, kind, true, cx)
16952 });
16953 cx.spawn_in(window, async move |_, cx| {
16954 let transaction = futures::select_biased! {
16955 () = timeout => {
16956 log::warn!("timed out waiting for executing code action");
16957 None
16958 }
16959 transaction = apply_action.log_err().fuse() => transaction,
16960 };
16961 buffer
16962 .update(cx, |buffer, cx| {
16963 // check if we need this
16964 if let Some(transaction) = transaction
16965 && !buffer.is_singleton()
16966 {
16967 buffer.push_transaction(&transaction.0, cx);
16968 }
16969 cx.notify();
16970 })
16971 .ok();
16972 Ok(())
16973 })
16974 }
16975
16976 pub fn restart_language_server(
16977 &mut self,
16978 _: &RestartLanguageServer,
16979 _: &mut Window,
16980 cx: &mut Context<Self>,
16981 ) {
16982 if let Some(project) = self.project.clone() {
16983 self.buffer.update(cx, |multi_buffer, cx| {
16984 project.update(cx, |project, cx| {
16985 project.restart_language_servers_for_buffers(
16986 multi_buffer.all_buffers().into_iter().collect(),
16987 HashSet::default(),
16988 cx,
16989 );
16990 });
16991 })
16992 }
16993 }
16994
16995 pub fn stop_language_server(
16996 &mut self,
16997 _: &StopLanguageServer,
16998 _: &mut Window,
16999 cx: &mut Context<Self>,
17000 ) {
17001 if let Some(project) = self.project.clone() {
17002 self.buffer.update(cx, |multi_buffer, cx| {
17003 project.update(cx, |project, cx| {
17004 project.stop_language_servers_for_buffers(
17005 multi_buffer.all_buffers().into_iter().collect(),
17006 HashSet::default(),
17007 cx,
17008 );
17009 cx.emit(project::Event::RefreshInlayHints);
17010 });
17011 });
17012 }
17013 }
17014
17015 fn cancel_language_server_work(
17016 workspace: &mut Workspace,
17017 _: &actions::CancelLanguageServerWork,
17018 _: &mut Window,
17019 cx: &mut Context<Workspace>,
17020 ) {
17021 let project = workspace.project();
17022 let buffers = workspace
17023 .active_item(cx)
17024 .and_then(|item| item.act_as::<Editor>(cx))
17025 .map_or(HashSet::default(), |editor| {
17026 editor.read(cx).buffer.read(cx).all_buffers()
17027 });
17028 project.update(cx, |project, cx| {
17029 project.cancel_language_server_work_for_buffers(buffers, cx);
17030 });
17031 }
17032
17033 fn show_character_palette(
17034 &mut self,
17035 _: &ShowCharacterPalette,
17036 window: &mut Window,
17037 _: &mut Context<Self>,
17038 ) {
17039 window.show_character_palette();
17040 }
17041
17042 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17043 if !self.diagnostics_enabled() {
17044 return;
17045 }
17046
17047 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17048 let buffer = self.buffer.read(cx).snapshot(cx);
17049 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17050 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17051 let is_valid = buffer
17052 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17053 .any(|entry| {
17054 entry.diagnostic.is_primary
17055 && !entry.range.is_empty()
17056 && entry.range.start == primary_range_start
17057 && entry.diagnostic.message == active_diagnostics.active_message
17058 });
17059
17060 if !is_valid {
17061 self.dismiss_diagnostics(cx);
17062 }
17063 }
17064 }
17065
17066 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17067 match &self.active_diagnostics {
17068 ActiveDiagnostic::Group(group) => Some(group),
17069 _ => None,
17070 }
17071 }
17072
17073 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17074 if !self.diagnostics_enabled() {
17075 return;
17076 }
17077 self.dismiss_diagnostics(cx);
17078 self.active_diagnostics = ActiveDiagnostic::All;
17079 }
17080
17081 fn activate_diagnostics(
17082 &mut self,
17083 buffer_id: BufferId,
17084 diagnostic: DiagnosticEntry<usize>,
17085 window: &mut Window,
17086 cx: &mut Context<Self>,
17087 ) {
17088 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17089 return;
17090 }
17091 self.dismiss_diagnostics(cx);
17092 let snapshot = self.snapshot(window, cx);
17093 let buffer = self.buffer.read(cx).snapshot(cx);
17094 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17095 return;
17096 };
17097
17098 let diagnostic_group = buffer
17099 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17100 .collect::<Vec<_>>();
17101
17102 let blocks =
17103 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17104
17105 let blocks = self.display_map.update(cx, |display_map, cx| {
17106 display_map.insert_blocks(blocks, cx).into_iter().collect()
17107 });
17108 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17109 active_range: buffer.anchor_before(diagnostic.range.start)
17110 ..buffer.anchor_after(diagnostic.range.end),
17111 active_message: diagnostic.diagnostic.message.clone(),
17112 group_id: diagnostic.diagnostic.group_id,
17113 blocks,
17114 });
17115 cx.notify();
17116 }
17117
17118 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17119 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17120 return;
17121 };
17122
17123 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17124 if let ActiveDiagnostic::Group(group) = prev {
17125 self.display_map.update(cx, |display_map, cx| {
17126 display_map.remove_blocks(group.blocks, cx);
17127 });
17128 cx.notify();
17129 }
17130 }
17131
17132 /// Disable inline diagnostics rendering for this editor.
17133 pub fn disable_inline_diagnostics(&mut self) {
17134 self.inline_diagnostics_enabled = false;
17135 self.inline_diagnostics_update = Task::ready(());
17136 self.inline_diagnostics.clear();
17137 }
17138
17139 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17140 self.diagnostics_enabled = false;
17141 self.dismiss_diagnostics(cx);
17142 self.inline_diagnostics_update = Task::ready(());
17143 self.inline_diagnostics.clear();
17144 }
17145
17146 pub fn disable_word_completions(&mut self) {
17147 self.word_completions_enabled = false;
17148 }
17149
17150 pub fn diagnostics_enabled(&self) -> bool {
17151 self.diagnostics_enabled && self.mode.is_full()
17152 }
17153
17154 pub fn inline_diagnostics_enabled(&self) -> bool {
17155 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17156 }
17157
17158 pub fn show_inline_diagnostics(&self) -> bool {
17159 self.show_inline_diagnostics
17160 }
17161
17162 pub fn toggle_inline_diagnostics(
17163 &mut self,
17164 _: &ToggleInlineDiagnostics,
17165 window: &mut Window,
17166 cx: &mut Context<Editor>,
17167 ) {
17168 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17169 self.refresh_inline_diagnostics(false, window, cx);
17170 }
17171
17172 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17173 self.diagnostics_max_severity = severity;
17174 self.display_map.update(cx, |display_map, _| {
17175 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17176 });
17177 }
17178
17179 pub fn toggle_diagnostics(
17180 &mut self,
17181 _: &ToggleDiagnostics,
17182 window: &mut Window,
17183 cx: &mut Context<Editor>,
17184 ) {
17185 if !self.diagnostics_enabled() {
17186 return;
17187 }
17188
17189 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17190 EditorSettings::get_global(cx)
17191 .diagnostics_max_severity
17192 .filter(|severity| severity != &DiagnosticSeverity::Off)
17193 .unwrap_or(DiagnosticSeverity::Hint)
17194 } else {
17195 DiagnosticSeverity::Off
17196 };
17197 self.set_max_diagnostics_severity(new_severity, cx);
17198 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17199 self.active_diagnostics = ActiveDiagnostic::None;
17200 self.inline_diagnostics_update = Task::ready(());
17201 self.inline_diagnostics.clear();
17202 } else {
17203 self.refresh_inline_diagnostics(false, window, cx);
17204 }
17205
17206 cx.notify();
17207 }
17208
17209 pub fn toggle_minimap(
17210 &mut self,
17211 _: &ToggleMinimap,
17212 window: &mut Window,
17213 cx: &mut Context<Editor>,
17214 ) {
17215 if self.supports_minimap(cx) {
17216 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17217 }
17218 }
17219
17220 fn refresh_inline_diagnostics(
17221 &mut self,
17222 debounce: bool,
17223 window: &mut Window,
17224 cx: &mut Context<Self>,
17225 ) {
17226 let max_severity = ProjectSettings::get_global(cx)
17227 .diagnostics
17228 .inline
17229 .max_severity
17230 .unwrap_or(self.diagnostics_max_severity);
17231
17232 if !self.inline_diagnostics_enabled()
17233 || !self.show_inline_diagnostics
17234 || max_severity == DiagnosticSeverity::Off
17235 {
17236 self.inline_diagnostics_update = Task::ready(());
17237 self.inline_diagnostics.clear();
17238 return;
17239 }
17240
17241 let debounce_ms = ProjectSettings::get_global(cx)
17242 .diagnostics
17243 .inline
17244 .update_debounce_ms;
17245 let debounce = if debounce && debounce_ms > 0 {
17246 Some(Duration::from_millis(debounce_ms))
17247 } else {
17248 None
17249 };
17250 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17251 if let Some(debounce) = debounce {
17252 cx.background_executor().timer(debounce).await;
17253 }
17254 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17255 editor
17256 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17257 .ok()
17258 }) else {
17259 return;
17260 };
17261
17262 let new_inline_diagnostics = cx
17263 .background_spawn(async move {
17264 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17265 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17266 let message = diagnostic_entry
17267 .diagnostic
17268 .message
17269 .split_once('\n')
17270 .map(|(line, _)| line)
17271 .map(SharedString::new)
17272 .unwrap_or_else(|| {
17273 SharedString::from(diagnostic_entry.diagnostic.message)
17274 });
17275 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17276 let (Ok(i) | Err(i)) = inline_diagnostics
17277 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17278 inline_diagnostics.insert(
17279 i,
17280 (
17281 start_anchor,
17282 InlineDiagnostic {
17283 message,
17284 group_id: diagnostic_entry.diagnostic.group_id,
17285 start: diagnostic_entry.range.start.to_point(&snapshot),
17286 is_primary: diagnostic_entry.diagnostic.is_primary,
17287 severity: diagnostic_entry.diagnostic.severity,
17288 },
17289 ),
17290 );
17291 }
17292 inline_diagnostics
17293 })
17294 .await;
17295
17296 editor
17297 .update(cx, |editor, cx| {
17298 editor.inline_diagnostics = new_inline_diagnostics;
17299 cx.notify();
17300 })
17301 .ok();
17302 });
17303 }
17304
17305 fn pull_diagnostics(
17306 &mut self,
17307 buffer_id: Option<BufferId>,
17308 window: &Window,
17309 cx: &mut Context<Self>,
17310 ) -> Option<()> {
17311 if !self.mode().is_full() {
17312 return None;
17313 }
17314 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17315 .diagnostics
17316 .lsp_pull_diagnostics;
17317 if !pull_diagnostics_settings.enabled {
17318 return None;
17319 }
17320 let project = self.project()?.downgrade();
17321 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17322 let mut buffers = self.buffer.read(cx).all_buffers();
17323 if let Some(buffer_id) = buffer_id {
17324 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17325 }
17326
17327 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17328 cx.background_executor().timer(debounce).await;
17329
17330 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17331 buffers
17332 .into_iter()
17333 .filter_map(|buffer| {
17334 project
17335 .update(cx, |project, cx| {
17336 project.lsp_store().update(cx, |lsp_store, cx| {
17337 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17338 })
17339 })
17340 .ok()
17341 })
17342 .collect::<FuturesUnordered<_>>()
17343 }) else {
17344 return;
17345 };
17346
17347 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17348 match pull_task {
17349 Ok(()) => {
17350 if editor
17351 .update_in(cx, |editor, window, cx| {
17352 editor.update_diagnostics_state(window, cx);
17353 })
17354 .is_err()
17355 {
17356 return;
17357 }
17358 }
17359 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17360 }
17361 }
17362 });
17363
17364 Some(())
17365 }
17366
17367 pub fn set_selections_from_remote(
17368 &mut self,
17369 selections: Vec<Selection<Anchor>>,
17370 pending_selection: Option<Selection<Anchor>>,
17371 window: &mut Window,
17372 cx: &mut Context<Self>,
17373 ) {
17374 let old_cursor_position = self.selections.newest_anchor().head();
17375 self.selections.change_with(cx, |s| {
17376 s.select_anchors(selections);
17377 if let Some(pending_selection) = pending_selection {
17378 s.set_pending(pending_selection, SelectMode::Character);
17379 } else {
17380 s.clear_pending();
17381 }
17382 });
17383 self.selections_did_change(
17384 false,
17385 &old_cursor_position,
17386 SelectionEffects::default(),
17387 window,
17388 cx,
17389 );
17390 }
17391
17392 pub fn transact(
17393 &mut self,
17394 window: &mut Window,
17395 cx: &mut Context<Self>,
17396 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17397 ) -> Option<TransactionId> {
17398 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17399 this.start_transaction_at(Instant::now(), window, cx);
17400 update(this, window, cx);
17401 this.end_transaction_at(Instant::now(), cx)
17402 })
17403 }
17404
17405 pub fn start_transaction_at(
17406 &mut self,
17407 now: Instant,
17408 window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) -> Option<TransactionId> {
17411 self.end_selection(window, cx);
17412 if let Some(tx_id) = self
17413 .buffer
17414 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17415 {
17416 self.selection_history
17417 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17418 cx.emit(EditorEvent::TransactionBegun {
17419 transaction_id: tx_id,
17420 });
17421 Some(tx_id)
17422 } else {
17423 None
17424 }
17425 }
17426
17427 pub fn end_transaction_at(
17428 &mut self,
17429 now: Instant,
17430 cx: &mut Context<Self>,
17431 ) -> Option<TransactionId> {
17432 if let Some(transaction_id) = self
17433 .buffer
17434 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17435 {
17436 if let Some((_, end_selections)) =
17437 self.selection_history.transaction_mut(transaction_id)
17438 {
17439 *end_selections = Some(self.selections.disjoint_anchors());
17440 } else {
17441 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17442 }
17443
17444 cx.emit(EditorEvent::Edited { transaction_id });
17445 Some(transaction_id)
17446 } else {
17447 None
17448 }
17449 }
17450
17451 pub fn modify_transaction_selection_history(
17452 &mut self,
17453 transaction_id: TransactionId,
17454 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17455 ) -> bool {
17456 self.selection_history
17457 .transaction_mut(transaction_id)
17458 .map(modify)
17459 .is_some()
17460 }
17461
17462 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17463 if self.selection_mark_mode {
17464 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17465 s.move_with(|_, sel| {
17466 sel.collapse_to(sel.head(), SelectionGoal::None);
17467 });
17468 })
17469 }
17470 self.selection_mark_mode = true;
17471 cx.notify();
17472 }
17473
17474 pub fn swap_selection_ends(
17475 &mut self,
17476 _: &actions::SwapSelectionEnds,
17477 window: &mut Window,
17478 cx: &mut Context<Self>,
17479 ) {
17480 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17481 s.move_with(|_, sel| {
17482 if sel.start != sel.end {
17483 sel.reversed = !sel.reversed
17484 }
17485 });
17486 });
17487 self.request_autoscroll(Autoscroll::newest(), cx);
17488 cx.notify();
17489 }
17490
17491 pub fn toggle_focus(
17492 workspace: &mut Workspace,
17493 _: &actions::ToggleFocus,
17494 window: &mut Window,
17495 cx: &mut Context<Workspace>,
17496 ) {
17497 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17498 return;
17499 };
17500 workspace.activate_item(&item, true, true, window, cx);
17501 }
17502
17503 pub fn toggle_fold(
17504 &mut self,
17505 _: &actions::ToggleFold,
17506 window: &mut Window,
17507 cx: &mut Context<Self>,
17508 ) {
17509 if self.is_singleton(cx) {
17510 let selection = self.selections.newest::<Point>(cx);
17511
17512 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17513 let range = if selection.is_empty() {
17514 let point = selection.head().to_display_point(&display_map);
17515 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17516 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17517 .to_point(&display_map);
17518 start..end
17519 } else {
17520 selection.range()
17521 };
17522 if display_map.folds_in_range(range).next().is_some() {
17523 self.unfold_lines(&Default::default(), window, cx)
17524 } else {
17525 self.fold(&Default::default(), window, cx)
17526 }
17527 } else {
17528 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17529 let buffer_ids: HashSet<_> = self
17530 .selections
17531 .disjoint_anchor_ranges()
17532 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17533 .collect();
17534
17535 let should_unfold = buffer_ids
17536 .iter()
17537 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17538
17539 for buffer_id in buffer_ids {
17540 if should_unfold {
17541 self.unfold_buffer(buffer_id, cx);
17542 } else {
17543 self.fold_buffer(buffer_id, cx);
17544 }
17545 }
17546 }
17547 }
17548
17549 pub fn toggle_fold_recursive(
17550 &mut self,
17551 _: &actions::ToggleFoldRecursive,
17552 window: &mut Window,
17553 cx: &mut Context<Self>,
17554 ) {
17555 let selection = self.selections.newest::<Point>(cx);
17556
17557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17558 let range = if selection.is_empty() {
17559 let point = selection.head().to_display_point(&display_map);
17560 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17561 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17562 .to_point(&display_map);
17563 start..end
17564 } else {
17565 selection.range()
17566 };
17567 if display_map.folds_in_range(range).next().is_some() {
17568 self.unfold_recursive(&Default::default(), window, cx)
17569 } else {
17570 self.fold_recursive(&Default::default(), window, cx)
17571 }
17572 }
17573
17574 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17575 if self.is_singleton(cx) {
17576 let mut to_fold = Vec::new();
17577 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17578 let selections = self.selections.all_adjusted(cx);
17579
17580 for selection in selections {
17581 let range = selection.range().sorted();
17582 let buffer_start_row = range.start.row;
17583
17584 if range.start.row != range.end.row {
17585 let mut found = false;
17586 let mut row = range.start.row;
17587 while row <= range.end.row {
17588 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17589 {
17590 found = true;
17591 row = crease.range().end.row + 1;
17592 to_fold.push(crease);
17593 } else {
17594 row += 1
17595 }
17596 }
17597 if found {
17598 continue;
17599 }
17600 }
17601
17602 for row in (0..=range.start.row).rev() {
17603 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17604 && crease.range().end.row >= buffer_start_row
17605 {
17606 to_fold.push(crease);
17607 if row <= range.start.row {
17608 break;
17609 }
17610 }
17611 }
17612 }
17613
17614 self.fold_creases(to_fold, true, window, cx);
17615 } else {
17616 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17617 let buffer_ids = self
17618 .selections
17619 .disjoint_anchor_ranges()
17620 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17621 .collect::<HashSet<_>>();
17622 for buffer_id in buffer_ids {
17623 self.fold_buffer(buffer_id, cx);
17624 }
17625 }
17626 }
17627
17628 pub fn toggle_fold_all(
17629 &mut self,
17630 _: &actions::ToggleFoldAll,
17631 window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) {
17634 if self.buffer.read(cx).is_singleton() {
17635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17636 let has_folds = display_map
17637 .folds_in_range(0..display_map.buffer_snapshot.len())
17638 .next()
17639 .is_some();
17640
17641 if has_folds {
17642 self.unfold_all(&actions::UnfoldAll, window, cx);
17643 } else {
17644 self.fold_all(&actions::FoldAll, window, cx);
17645 }
17646 } else {
17647 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17648 let should_unfold = buffer_ids
17649 .iter()
17650 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17651
17652 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17653 editor
17654 .update_in(cx, |editor, _, cx| {
17655 for buffer_id in buffer_ids {
17656 if should_unfold {
17657 editor.unfold_buffer(buffer_id, cx);
17658 } else {
17659 editor.fold_buffer(buffer_id, cx);
17660 }
17661 }
17662 })
17663 .ok();
17664 });
17665 }
17666 }
17667
17668 fn fold_at_level(
17669 &mut self,
17670 fold_at: &FoldAtLevel,
17671 window: &mut Window,
17672 cx: &mut Context<Self>,
17673 ) {
17674 if !self.buffer.read(cx).is_singleton() {
17675 return;
17676 }
17677
17678 let fold_at_level = fold_at.0;
17679 let snapshot = self.buffer.read(cx).snapshot(cx);
17680 let mut to_fold = Vec::new();
17681 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17682
17683 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17684 while start_row < end_row {
17685 match self
17686 .snapshot(window, cx)
17687 .crease_for_buffer_row(MultiBufferRow(start_row))
17688 {
17689 Some(crease) => {
17690 let nested_start_row = crease.range().start.row + 1;
17691 let nested_end_row = crease.range().end.row;
17692
17693 if current_level < fold_at_level {
17694 stack.push((nested_start_row, nested_end_row, current_level + 1));
17695 } else if current_level == fold_at_level {
17696 to_fold.push(crease);
17697 }
17698
17699 start_row = nested_end_row + 1;
17700 }
17701 None => start_row += 1,
17702 }
17703 }
17704 }
17705
17706 self.fold_creases(to_fold, true, window, cx);
17707 }
17708
17709 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17710 if self.buffer.read(cx).is_singleton() {
17711 let mut fold_ranges = Vec::new();
17712 let snapshot = self.buffer.read(cx).snapshot(cx);
17713
17714 for row in 0..snapshot.max_row().0 {
17715 if let Some(foldable_range) = self
17716 .snapshot(window, cx)
17717 .crease_for_buffer_row(MultiBufferRow(row))
17718 {
17719 fold_ranges.push(foldable_range);
17720 }
17721 }
17722
17723 self.fold_creases(fold_ranges, true, window, cx);
17724 } else {
17725 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17726 editor
17727 .update_in(cx, |editor, _, cx| {
17728 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17729 editor.fold_buffer(buffer_id, cx);
17730 }
17731 })
17732 .ok();
17733 });
17734 }
17735 }
17736
17737 pub fn fold_function_bodies(
17738 &mut self,
17739 _: &actions::FoldFunctionBodies,
17740 window: &mut Window,
17741 cx: &mut Context<Self>,
17742 ) {
17743 let snapshot = self.buffer.read(cx).snapshot(cx);
17744
17745 let ranges = snapshot
17746 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17747 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17748 .collect::<Vec<_>>();
17749
17750 let creases = ranges
17751 .into_iter()
17752 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17753 .collect();
17754
17755 self.fold_creases(creases, true, window, cx);
17756 }
17757
17758 pub fn fold_recursive(
17759 &mut self,
17760 _: &actions::FoldRecursive,
17761 window: &mut Window,
17762 cx: &mut Context<Self>,
17763 ) {
17764 let mut to_fold = Vec::new();
17765 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17766 let selections = self.selections.all_adjusted(cx);
17767
17768 for selection in selections {
17769 let range = selection.range().sorted();
17770 let buffer_start_row = range.start.row;
17771
17772 if range.start.row != range.end.row {
17773 let mut found = false;
17774 for row in range.start.row..=range.end.row {
17775 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17776 found = true;
17777 to_fold.push(crease);
17778 }
17779 }
17780 if found {
17781 continue;
17782 }
17783 }
17784
17785 for row in (0..=range.start.row).rev() {
17786 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17787 if crease.range().end.row >= buffer_start_row {
17788 to_fold.push(crease);
17789 } else {
17790 break;
17791 }
17792 }
17793 }
17794 }
17795
17796 self.fold_creases(to_fold, true, window, cx);
17797 }
17798
17799 pub fn fold_at(
17800 &mut self,
17801 buffer_row: MultiBufferRow,
17802 window: &mut Window,
17803 cx: &mut Context<Self>,
17804 ) {
17805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17806
17807 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17808 let autoscroll = self
17809 .selections
17810 .all::<Point>(cx)
17811 .iter()
17812 .any(|selection| crease.range().overlaps(&selection.range()));
17813
17814 self.fold_creases(vec![crease], autoscroll, window, cx);
17815 }
17816 }
17817
17818 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17819 if self.is_singleton(cx) {
17820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17821 let buffer = &display_map.buffer_snapshot;
17822 let selections = self.selections.all::<Point>(cx);
17823 let ranges = selections
17824 .iter()
17825 .map(|s| {
17826 let range = s.display_range(&display_map).sorted();
17827 let mut start = range.start.to_point(&display_map);
17828 let mut end = range.end.to_point(&display_map);
17829 start.column = 0;
17830 end.column = buffer.line_len(MultiBufferRow(end.row));
17831 start..end
17832 })
17833 .collect::<Vec<_>>();
17834
17835 self.unfold_ranges(&ranges, true, true, cx);
17836 } else {
17837 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17838 let buffer_ids = self
17839 .selections
17840 .disjoint_anchor_ranges()
17841 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17842 .collect::<HashSet<_>>();
17843 for buffer_id in buffer_ids {
17844 self.unfold_buffer(buffer_id, cx);
17845 }
17846 }
17847 }
17848
17849 pub fn unfold_recursive(
17850 &mut self,
17851 _: &UnfoldRecursive,
17852 _window: &mut Window,
17853 cx: &mut Context<Self>,
17854 ) {
17855 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17856 let selections = self.selections.all::<Point>(cx);
17857 let ranges = selections
17858 .iter()
17859 .map(|s| {
17860 let mut range = s.display_range(&display_map).sorted();
17861 *range.start.column_mut() = 0;
17862 *range.end.column_mut() = display_map.line_len(range.end.row());
17863 let start = range.start.to_point(&display_map);
17864 let end = range.end.to_point(&display_map);
17865 start..end
17866 })
17867 .collect::<Vec<_>>();
17868
17869 self.unfold_ranges(&ranges, true, true, cx);
17870 }
17871
17872 pub fn unfold_at(
17873 &mut self,
17874 buffer_row: MultiBufferRow,
17875 _window: &mut Window,
17876 cx: &mut Context<Self>,
17877 ) {
17878 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17879
17880 let intersection_range = Point::new(buffer_row.0, 0)
17881 ..Point::new(
17882 buffer_row.0,
17883 display_map.buffer_snapshot.line_len(buffer_row),
17884 );
17885
17886 let autoscroll = self
17887 .selections
17888 .all::<Point>(cx)
17889 .iter()
17890 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17891
17892 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17893 }
17894
17895 pub fn unfold_all(
17896 &mut self,
17897 _: &actions::UnfoldAll,
17898 _window: &mut Window,
17899 cx: &mut Context<Self>,
17900 ) {
17901 if self.buffer.read(cx).is_singleton() {
17902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17903 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17904 } else {
17905 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17906 editor
17907 .update(cx, |editor, cx| {
17908 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17909 editor.unfold_buffer(buffer_id, cx);
17910 }
17911 })
17912 .ok();
17913 });
17914 }
17915 }
17916
17917 pub fn fold_selected_ranges(
17918 &mut self,
17919 _: &FoldSelectedRanges,
17920 window: &mut Window,
17921 cx: &mut Context<Self>,
17922 ) {
17923 let selections = self.selections.all_adjusted(cx);
17924 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17925 let ranges = selections
17926 .into_iter()
17927 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17928 .collect::<Vec<_>>();
17929 self.fold_creases(ranges, true, window, cx);
17930 }
17931
17932 pub fn fold_ranges<T: ToOffset + Clone>(
17933 &mut self,
17934 ranges: Vec<Range<T>>,
17935 auto_scroll: bool,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) {
17939 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17940 let ranges = ranges
17941 .into_iter()
17942 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17943 .collect::<Vec<_>>();
17944 self.fold_creases(ranges, auto_scroll, window, cx);
17945 }
17946
17947 pub fn fold_creases<T: ToOffset + Clone>(
17948 &mut self,
17949 creases: Vec<Crease<T>>,
17950 auto_scroll: bool,
17951 _window: &mut Window,
17952 cx: &mut Context<Self>,
17953 ) {
17954 if creases.is_empty() {
17955 return;
17956 }
17957
17958 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17959
17960 if auto_scroll {
17961 self.request_autoscroll(Autoscroll::fit(), cx);
17962 }
17963
17964 cx.notify();
17965
17966 self.scrollbar_marker_state.dirty = true;
17967 self.folds_did_change(cx);
17968 }
17969
17970 /// Removes any folds whose ranges intersect any of the given ranges.
17971 pub fn unfold_ranges<T: ToOffset + Clone>(
17972 &mut self,
17973 ranges: &[Range<T>],
17974 inclusive: bool,
17975 auto_scroll: bool,
17976 cx: &mut Context<Self>,
17977 ) {
17978 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17979 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17980 });
17981 self.folds_did_change(cx);
17982 }
17983
17984 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17985 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17986 return;
17987 }
17988 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17989 self.display_map.update(cx, |display_map, cx| {
17990 display_map.fold_buffers([buffer_id], cx)
17991 });
17992 cx.emit(EditorEvent::BufferFoldToggled {
17993 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17994 folded: true,
17995 });
17996 cx.notify();
17997 }
17998
17999 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18000 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18001 return;
18002 }
18003 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18004 self.display_map.update(cx, |display_map, cx| {
18005 display_map.unfold_buffers([buffer_id], cx);
18006 });
18007 cx.emit(EditorEvent::BufferFoldToggled {
18008 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18009 folded: false,
18010 });
18011 cx.notify();
18012 }
18013
18014 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18015 self.display_map.read(cx).is_buffer_folded(buffer)
18016 }
18017
18018 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18019 self.display_map.read(cx).folded_buffers()
18020 }
18021
18022 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18023 self.display_map.update(cx, |display_map, cx| {
18024 display_map.disable_header_for_buffer(buffer_id, cx);
18025 });
18026 cx.notify();
18027 }
18028
18029 /// Removes any folds with the given ranges.
18030 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18031 &mut self,
18032 ranges: &[Range<T>],
18033 type_id: TypeId,
18034 auto_scroll: bool,
18035 cx: &mut Context<Self>,
18036 ) {
18037 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18038 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18039 });
18040 self.folds_did_change(cx);
18041 }
18042
18043 fn remove_folds_with<T: ToOffset + Clone>(
18044 &mut self,
18045 ranges: &[Range<T>],
18046 auto_scroll: bool,
18047 cx: &mut Context<Self>,
18048 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18049 ) {
18050 if ranges.is_empty() {
18051 return;
18052 }
18053
18054 let mut buffers_affected = HashSet::default();
18055 let multi_buffer = self.buffer().read(cx);
18056 for range in ranges {
18057 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18058 buffers_affected.insert(buffer.read(cx).remote_id());
18059 };
18060 }
18061
18062 self.display_map.update(cx, update);
18063
18064 if auto_scroll {
18065 self.request_autoscroll(Autoscroll::fit(), cx);
18066 }
18067
18068 cx.notify();
18069 self.scrollbar_marker_state.dirty = true;
18070 self.active_indent_guides_state.dirty = true;
18071 }
18072
18073 pub fn update_renderer_widths(
18074 &mut self,
18075 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18076 cx: &mut Context<Self>,
18077 ) -> bool {
18078 self.display_map
18079 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18080 }
18081
18082 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18083 self.display_map.read(cx).fold_placeholder.clone()
18084 }
18085
18086 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18087 self.buffer.update(cx, |buffer, cx| {
18088 buffer.set_all_diff_hunks_expanded(cx);
18089 });
18090 }
18091
18092 pub fn expand_all_diff_hunks(
18093 &mut self,
18094 _: &ExpandAllDiffHunks,
18095 _window: &mut Window,
18096 cx: &mut Context<Self>,
18097 ) {
18098 self.buffer.update(cx, |buffer, cx| {
18099 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18100 });
18101 }
18102
18103 pub fn toggle_selected_diff_hunks(
18104 &mut self,
18105 _: &ToggleSelectedDiffHunks,
18106 _window: &mut Window,
18107 cx: &mut Context<Self>,
18108 ) {
18109 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18110 self.toggle_diff_hunks_in_ranges(ranges, cx);
18111 }
18112
18113 pub fn diff_hunks_in_ranges<'a>(
18114 &'a self,
18115 ranges: &'a [Range<Anchor>],
18116 buffer: &'a MultiBufferSnapshot,
18117 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18118 ranges.iter().flat_map(move |range| {
18119 let end_excerpt_id = range.end.excerpt_id;
18120 let range = range.to_point(buffer);
18121 let mut peek_end = range.end;
18122 if range.end.row < buffer.max_row().0 {
18123 peek_end = Point::new(range.end.row + 1, 0);
18124 }
18125 buffer
18126 .diff_hunks_in_range(range.start..peek_end)
18127 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18128 })
18129 }
18130
18131 pub fn has_stageable_diff_hunks_in_ranges(
18132 &self,
18133 ranges: &[Range<Anchor>],
18134 snapshot: &MultiBufferSnapshot,
18135 ) -> bool {
18136 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18137 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18138 }
18139
18140 pub fn toggle_staged_selected_diff_hunks(
18141 &mut self,
18142 _: &::git::ToggleStaged,
18143 _: &mut Window,
18144 cx: &mut Context<Self>,
18145 ) {
18146 let snapshot = self.buffer.read(cx).snapshot(cx);
18147 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18148 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18149 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18150 }
18151
18152 pub fn set_render_diff_hunk_controls(
18153 &mut self,
18154 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18155 cx: &mut Context<Self>,
18156 ) {
18157 self.render_diff_hunk_controls = render_diff_hunk_controls;
18158 cx.notify();
18159 }
18160
18161 pub fn stage_and_next(
18162 &mut self,
18163 _: &::git::StageAndNext,
18164 window: &mut Window,
18165 cx: &mut Context<Self>,
18166 ) {
18167 self.do_stage_or_unstage_and_next(true, window, cx);
18168 }
18169
18170 pub fn unstage_and_next(
18171 &mut self,
18172 _: &::git::UnstageAndNext,
18173 window: &mut Window,
18174 cx: &mut Context<Self>,
18175 ) {
18176 self.do_stage_or_unstage_and_next(false, window, cx);
18177 }
18178
18179 pub fn stage_or_unstage_diff_hunks(
18180 &mut self,
18181 stage: bool,
18182 ranges: Vec<Range<Anchor>>,
18183 cx: &mut Context<Self>,
18184 ) {
18185 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18186 cx.spawn(async move |this, cx| {
18187 task.await?;
18188 this.update(cx, |this, cx| {
18189 let snapshot = this.buffer.read(cx).snapshot(cx);
18190 let chunk_by = this
18191 .diff_hunks_in_ranges(&ranges, &snapshot)
18192 .chunk_by(|hunk| hunk.buffer_id);
18193 for (buffer_id, hunks) in &chunk_by {
18194 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18195 }
18196 })
18197 })
18198 .detach_and_log_err(cx);
18199 }
18200
18201 fn save_buffers_for_ranges_if_needed(
18202 &mut self,
18203 ranges: &[Range<Anchor>],
18204 cx: &mut Context<Editor>,
18205 ) -> Task<Result<()>> {
18206 let multibuffer = self.buffer.read(cx);
18207 let snapshot = multibuffer.read(cx);
18208 let buffer_ids: HashSet<_> = ranges
18209 .iter()
18210 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18211 .collect();
18212 drop(snapshot);
18213
18214 let mut buffers = HashSet::default();
18215 for buffer_id in buffer_ids {
18216 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18217 let buffer = buffer_entity.read(cx);
18218 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18219 {
18220 buffers.insert(buffer_entity);
18221 }
18222 }
18223 }
18224
18225 if let Some(project) = &self.project {
18226 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18227 } else {
18228 Task::ready(Ok(()))
18229 }
18230 }
18231
18232 fn do_stage_or_unstage_and_next(
18233 &mut self,
18234 stage: bool,
18235 window: &mut Window,
18236 cx: &mut Context<Self>,
18237 ) {
18238 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18239
18240 if ranges.iter().any(|range| range.start != range.end) {
18241 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18242 return;
18243 }
18244
18245 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18246 let snapshot = self.snapshot(window, cx);
18247 let position = self.selections.newest::<Point>(cx).head();
18248 let mut row = snapshot
18249 .buffer_snapshot
18250 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18251 .find(|hunk| hunk.row_range.start.0 > position.row)
18252 .map(|hunk| hunk.row_range.start);
18253
18254 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18255 // Outside of the project diff editor, wrap around to the beginning.
18256 if !all_diff_hunks_expanded {
18257 row = row.or_else(|| {
18258 snapshot
18259 .buffer_snapshot
18260 .diff_hunks_in_range(Point::zero()..position)
18261 .find(|hunk| hunk.row_range.end.0 < position.row)
18262 .map(|hunk| hunk.row_range.start)
18263 });
18264 }
18265
18266 if let Some(row) = row {
18267 let destination = Point::new(row.0, 0);
18268 let autoscroll = Autoscroll::center();
18269
18270 self.unfold_ranges(&[destination..destination], false, false, cx);
18271 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18272 s.select_ranges([destination..destination]);
18273 });
18274 }
18275 }
18276
18277 fn do_stage_or_unstage(
18278 &self,
18279 stage: bool,
18280 buffer_id: BufferId,
18281 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18282 cx: &mut App,
18283 ) -> Option<()> {
18284 let project = self.project()?;
18285 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18286 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18287 let buffer_snapshot = buffer.read(cx).snapshot();
18288 let file_exists = buffer_snapshot
18289 .file()
18290 .is_some_and(|file| file.disk_state().exists());
18291 diff.update(cx, |diff, cx| {
18292 diff.stage_or_unstage_hunks(
18293 stage,
18294 &hunks
18295 .map(|hunk| buffer_diff::DiffHunk {
18296 buffer_range: hunk.buffer_range,
18297 diff_base_byte_range: hunk.diff_base_byte_range,
18298 secondary_status: hunk.secondary_status,
18299 range: Point::zero()..Point::zero(), // unused
18300 })
18301 .collect::<Vec<_>>(),
18302 &buffer_snapshot,
18303 file_exists,
18304 cx,
18305 )
18306 });
18307 None
18308 }
18309
18310 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18311 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18312 self.buffer
18313 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18314 }
18315
18316 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18317 self.buffer.update(cx, |buffer, cx| {
18318 let ranges = vec![Anchor::min()..Anchor::max()];
18319 if !buffer.all_diff_hunks_expanded()
18320 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18321 {
18322 buffer.collapse_diff_hunks(ranges, cx);
18323 true
18324 } else {
18325 false
18326 }
18327 })
18328 }
18329
18330 fn toggle_diff_hunks_in_ranges(
18331 &mut self,
18332 ranges: Vec<Range<Anchor>>,
18333 cx: &mut Context<Editor>,
18334 ) {
18335 self.buffer.update(cx, |buffer, cx| {
18336 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18337 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18338 })
18339 }
18340
18341 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18342 self.buffer.update(cx, |buffer, cx| {
18343 let snapshot = buffer.snapshot(cx);
18344 let excerpt_id = range.end.excerpt_id;
18345 let point_range = range.to_point(&snapshot);
18346 let expand = !buffer.single_hunk_is_expanded(range, cx);
18347 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18348 })
18349 }
18350
18351 pub(crate) fn apply_all_diff_hunks(
18352 &mut self,
18353 _: &ApplyAllDiffHunks,
18354 window: &mut Window,
18355 cx: &mut Context<Self>,
18356 ) {
18357 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18358
18359 let buffers = self.buffer.read(cx).all_buffers();
18360 for branch_buffer in buffers {
18361 branch_buffer.update(cx, |branch_buffer, cx| {
18362 branch_buffer.merge_into_base(Vec::new(), cx);
18363 });
18364 }
18365
18366 if let Some(project) = self.project.clone() {
18367 self.save(
18368 SaveOptions {
18369 format: true,
18370 autosave: false,
18371 },
18372 project,
18373 window,
18374 cx,
18375 )
18376 .detach_and_log_err(cx);
18377 }
18378 }
18379
18380 pub(crate) fn apply_selected_diff_hunks(
18381 &mut self,
18382 _: &ApplyDiffHunk,
18383 window: &mut Window,
18384 cx: &mut Context<Self>,
18385 ) {
18386 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18387 let snapshot = self.snapshot(window, cx);
18388 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18389 let mut ranges_by_buffer = HashMap::default();
18390 self.transact(window, cx, |editor, _window, cx| {
18391 for hunk in hunks {
18392 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18393 ranges_by_buffer
18394 .entry(buffer.clone())
18395 .or_insert_with(Vec::new)
18396 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18397 }
18398 }
18399
18400 for (buffer, ranges) in ranges_by_buffer {
18401 buffer.update(cx, |buffer, cx| {
18402 buffer.merge_into_base(ranges, cx);
18403 });
18404 }
18405 });
18406
18407 if let Some(project) = self.project.clone() {
18408 self.save(
18409 SaveOptions {
18410 format: true,
18411 autosave: false,
18412 },
18413 project,
18414 window,
18415 cx,
18416 )
18417 .detach_and_log_err(cx);
18418 }
18419 }
18420
18421 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18422 if hovered != self.gutter_hovered {
18423 self.gutter_hovered = hovered;
18424 cx.notify();
18425 }
18426 }
18427
18428 pub fn insert_blocks(
18429 &mut self,
18430 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18431 autoscroll: Option<Autoscroll>,
18432 cx: &mut Context<Self>,
18433 ) -> Vec<CustomBlockId> {
18434 let blocks = self
18435 .display_map
18436 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18437 if let Some(autoscroll) = autoscroll {
18438 self.request_autoscroll(autoscroll, cx);
18439 }
18440 cx.notify();
18441 blocks
18442 }
18443
18444 pub fn resize_blocks(
18445 &mut self,
18446 heights: HashMap<CustomBlockId, u32>,
18447 autoscroll: Option<Autoscroll>,
18448 cx: &mut Context<Self>,
18449 ) {
18450 self.display_map
18451 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18452 if let Some(autoscroll) = autoscroll {
18453 self.request_autoscroll(autoscroll, cx);
18454 }
18455 cx.notify();
18456 }
18457
18458 pub fn replace_blocks(
18459 &mut self,
18460 renderers: HashMap<CustomBlockId, RenderBlock>,
18461 autoscroll: Option<Autoscroll>,
18462 cx: &mut Context<Self>,
18463 ) {
18464 self.display_map
18465 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18466 if let Some(autoscroll) = autoscroll {
18467 self.request_autoscroll(autoscroll, cx);
18468 }
18469 cx.notify();
18470 }
18471
18472 pub fn remove_blocks(
18473 &mut self,
18474 block_ids: HashSet<CustomBlockId>,
18475 autoscroll: Option<Autoscroll>,
18476 cx: &mut Context<Self>,
18477 ) {
18478 self.display_map.update(cx, |display_map, cx| {
18479 display_map.remove_blocks(block_ids, cx)
18480 });
18481 if let Some(autoscroll) = autoscroll {
18482 self.request_autoscroll(autoscroll, cx);
18483 }
18484 cx.notify();
18485 }
18486
18487 pub fn row_for_block(
18488 &self,
18489 block_id: CustomBlockId,
18490 cx: &mut Context<Self>,
18491 ) -> Option<DisplayRow> {
18492 self.display_map
18493 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18494 }
18495
18496 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18497 self.focused_block = Some(focused_block);
18498 }
18499
18500 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18501 self.focused_block.take()
18502 }
18503
18504 pub fn insert_creases(
18505 &mut self,
18506 creases: impl IntoIterator<Item = Crease<Anchor>>,
18507 cx: &mut Context<Self>,
18508 ) -> Vec<CreaseId> {
18509 self.display_map
18510 .update(cx, |map, cx| map.insert_creases(creases, cx))
18511 }
18512
18513 pub fn remove_creases(
18514 &mut self,
18515 ids: impl IntoIterator<Item = CreaseId>,
18516 cx: &mut Context<Self>,
18517 ) -> Vec<(CreaseId, Range<Anchor>)> {
18518 self.display_map
18519 .update(cx, |map, cx| map.remove_creases(ids, cx))
18520 }
18521
18522 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18523 self.display_map
18524 .update(cx, |map, cx| map.snapshot(cx))
18525 .longest_row()
18526 }
18527
18528 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18529 self.display_map
18530 .update(cx, |map, cx| map.snapshot(cx))
18531 .max_point()
18532 }
18533
18534 pub fn text(&self, cx: &App) -> String {
18535 self.buffer.read(cx).read(cx).text()
18536 }
18537
18538 pub fn is_empty(&self, cx: &App) -> bool {
18539 self.buffer.read(cx).read(cx).is_empty()
18540 }
18541
18542 pub fn text_option(&self, cx: &App) -> Option<String> {
18543 let text = self.text(cx);
18544 let text = text.trim();
18545
18546 if text.is_empty() {
18547 return None;
18548 }
18549
18550 Some(text.to_string())
18551 }
18552
18553 pub fn set_text(
18554 &mut self,
18555 text: impl Into<Arc<str>>,
18556 window: &mut Window,
18557 cx: &mut Context<Self>,
18558 ) {
18559 self.transact(window, cx, |this, _, cx| {
18560 this.buffer
18561 .read(cx)
18562 .as_singleton()
18563 .expect("you can only call set_text on editors for singleton buffers")
18564 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18565 });
18566 }
18567
18568 pub fn display_text(&self, cx: &mut App) -> String {
18569 self.display_map
18570 .update(cx, |map, cx| map.snapshot(cx))
18571 .text()
18572 }
18573
18574 fn create_minimap(
18575 &self,
18576 minimap_settings: MinimapSettings,
18577 window: &mut Window,
18578 cx: &mut Context<Self>,
18579 ) -> Option<Entity<Self>> {
18580 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18581 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18582 }
18583
18584 fn initialize_new_minimap(
18585 &self,
18586 minimap_settings: MinimapSettings,
18587 window: &mut Window,
18588 cx: &mut Context<Self>,
18589 ) -> Entity<Self> {
18590 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18591
18592 let mut minimap = Editor::new_internal(
18593 EditorMode::Minimap {
18594 parent: cx.weak_entity(),
18595 },
18596 self.buffer.clone(),
18597 None,
18598 Some(self.display_map.clone()),
18599 window,
18600 cx,
18601 );
18602 minimap.scroll_manager.clone_state(&self.scroll_manager);
18603 minimap.set_text_style_refinement(TextStyleRefinement {
18604 font_size: Some(MINIMAP_FONT_SIZE),
18605 font_weight: Some(MINIMAP_FONT_WEIGHT),
18606 ..Default::default()
18607 });
18608 minimap.update_minimap_configuration(minimap_settings, cx);
18609 cx.new(|_| minimap)
18610 }
18611
18612 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18613 let current_line_highlight = minimap_settings
18614 .current_line_highlight
18615 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18616 self.set_current_line_highlight(Some(current_line_highlight));
18617 }
18618
18619 pub fn minimap(&self) -> Option<&Entity<Self>> {
18620 self.minimap
18621 .as_ref()
18622 .filter(|_| self.minimap_visibility.visible())
18623 }
18624
18625 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18626 let mut wrap_guides = smallvec![];
18627
18628 if self.show_wrap_guides == Some(false) {
18629 return wrap_guides;
18630 }
18631
18632 let settings = self.buffer.read(cx).language_settings(cx);
18633 if settings.show_wrap_guides {
18634 match self.soft_wrap_mode(cx) {
18635 SoftWrap::Column(soft_wrap) => {
18636 wrap_guides.push((soft_wrap as usize, true));
18637 }
18638 SoftWrap::Bounded(soft_wrap) => {
18639 wrap_guides.push((soft_wrap as usize, true));
18640 }
18641 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18642 }
18643 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18644 }
18645
18646 wrap_guides
18647 }
18648
18649 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18650 let settings = self.buffer.read(cx).language_settings(cx);
18651 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18652 match mode {
18653 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18654 SoftWrap::None
18655 }
18656 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18657 language_settings::SoftWrap::PreferredLineLength => {
18658 SoftWrap::Column(settings.preferred_line_length)
18659 }
18660 language_settings::SoftWrap::Bounded => {
18661 SoftWrap::Bounded(settings.preferred_line_length)
18662 }
18663 }
18664 }
18665
18666 pub fn set_soft_wrap_mode(
18667 &mut self,
18668 mode: language_settings::SoftWrap,
18669
18670 cx: &mut Context<Self>,
18671 ) {
18672 self.soft_wrap_mode_override = Some(mode);
18673 cx.notify();
18674 }
18675
18676 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18677 self.hard_wrap = hard_wrap;
18678 cx.notify();
18679 }
18680
18681 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18682 self.text_style_refinement = Some(style);
18683 }
18684
18685 /// called by the Element so we know what style we were most recently rendered with.
18686 pub(crate) fn set_style(
18687 &mut self,
18688 style: EditorStyle,
18689 window: &mut Window,
18690 cx: &mut Context<Self>,
18691 ) {
18692 // We intentionally do not inform the display map about the minimap style
18693 // so that wrapping is not recalculated and stays consistent for the editor
18694 // and its linked minimap.
18695 if !self.mode.is_minimap() {
18696 let rem_size = window.rem_size();
18697 self.display_map.update(cx, |map, cx| {
18698 map.set_font(
18699 style.text.font(),
18700 style.text.font_size.to_pixels(rem_size),
18701 cx,
18702 )
18703 });
18704 }
18705 self.style = Some(style);
18706 }
18707
18708 pub fn style(&self) -> Option<&EditorStyle> {
18709 self.style.as_ref()
18710 }
18711
18712 // Called by the element. This method is not designed to be called outside of the editor
18713 // element's layout code because it does not notify when rewrapping is computed synchronously.
18714 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18715 self.display_map
18716 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18717 }
18718
18719 pub fn set_soft_wrap(&mut self) {
18720 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18721 }
18722
18723 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18724 if self.soft_wrap_mode_override.is_some() {
18725 self.soft_wrap_mode_override.take();
18726 } else {
18727 let soft_wrap = match self.soft_wrap_mode(cx) {
18728 SoftWrap::GitDiff => return,
18729 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18730 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18731 language_settings::SoftWrap::None
18732 }
18733 };
18734 self.soft_wrap_mode_override = Some(soft_wrap);
18735 }
18736 cx.notify();
18737 }
18738
18739 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18740 let Some(workspace) = self.workspace() else {
18741 return;
18742 };
18743 let fs = workspace.read(cx).app_state().fs.clone();
18744 let current_show = TabBarSettings::get_global(cx).show;
18745 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18746 setting.show = Some(!current_show);
18747 });
18748 }
18749
18750 pub fn toggle_indent_guides(
18751 &mut self,
18752 _: &ToggleIndentGuides,
18753 _: &mut Window,
18754 cx: &mut Context<Self>,
18755 ) {
18756 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18757 self.buffer
18758 .read(cx)
18759 .language_settings(cx)
18760 .indent_guides
18761 .enabled
18762 });
18763 self.show_indent_guides = Some(!currently_enabled);
18764 cx.notify();
18765 }
18766
18767 fn should_show_indent_guides(&self) -> Option<bool> {
18768 self.show_indent_guides
18769 }
18770
18771 pub fn toggle_line_numbers(
18772 &mut self,
18773 _: &ToggleLineNumbers,
18774 _: &mut Window,
18775 cx: &mut Context<Self>,
18776 ) {
18777 let mut editor_settings = EditorSettings::get_global(cx).clone();
18778 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18779 EditorSettings::override_global(editor_settings, cx);
18780 }
18781
18782 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18783 if let Some(show_line_numbers) = self.show_line_numbers {
18784 return show_line_numbers;
18785 }
18786 EditorSettings::get_global(cx).gutter.line_numbers
18787 }
18788
18789 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18790 self.use_relative_line_numbers
18791 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18792 }
18793
18794 pub fn toggle_relative_line_numbers(
18795 &mut self,
18796 _: &ToggleRelativeLineNumbers,
18797 _: &mut Window,
18798 cx: &mut Context<Self>,
18799 ) {
18800 let is_relative = self.should_use_relative_line_numbers(cx);
18801 self.set_relative_line_number(Some(!is_relative), cx)
18802 }
18803
18804 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18805 self.use_relative_line_numbers = is_relative;
18806 cx.notify();
18807 }
18808
18809 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18810 self.show_gutter = show_gutter;
18811 cx.notify();
18812 }
18813
18814 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18815 self.show_scrollbars = ScrollbarAxes {
18816 horizontal: show,
18817 vertical: show,
18818 };
18819 cx.notify();
18820 }
18821
18822 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18823 self.show_scrollbars.vertical = show;
18824 cx.notify();
18825 }
18826
18827 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18828 self.show_scrollbars.horizontal = show;
18829 cx.notify();
18830 }
18831
18832 pub fn set_minimap_visibility(
18833 &mut self,
18834 minimap_visibility: MinimapVisibility,
18835 window: &mut Window,
18836 cx: &mut Context<Self>,
18837 ) {
18838 if self.minimap_visibility != minimap_visibility {
18839 if minimap_visibility.visible() && self.minimap.is_none() {
18840 let minimap_settings = EditorSettings::get_global(cx).minimap;
18841 self.minimap =
18842 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18843 }
18844 self.minimap_visibility = minimap_visibility;
18845 cx.notify();
18846 }
18847 }
18848
18849 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18850 self.set_show_scrollbars(false, cx);
18851 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18852 }
18853
18854 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18855 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18856 }
18857
18858 /// Normally the text in full mode and auto height editors is padded on the
18859 /// left side by roughly half a character width for improved hit testing.
18860 ///
18861 /// Use this method to disable this for cases where this is not wanted (e.g.
18862 /// if you want to align the editor text with some other text above or below)
18863 /// or if you want to add this padding to single-line editors.
18864 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18865 self.offset_content = offset_content;
18866 cx.notify();
18867 }
18868
18869 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18870 self.show_line_numbers = Some(show_line_numbers);
18871 cx.notify();
18872 }
18873
18874 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18875 self.disable_expand_excerpt_buttons = true;
18876 cx.notify();
18877 }
18878
18879 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18880 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18881 cx.notify();
18882 }
18883
18884 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18885 self.show_code_actions = Some(show_code_actions);
18886 cx.notify();
18887 }
18888
18889 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18890 self.show_runnables = Some(show_runnables);
18891 cx.notify();
18892 }
18893
18894 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18895 self.show_breakpoints = Some(show_breakpoints);
18896 cx.notify();
18897 }
18898
18899 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18900 if self.display_map.read(cx).masked != masked {
18901 self.display_map.update(cx, |map, _| map.masked = masked);
18902 }
18903 cx.notify()
18904 }
18905
18906 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18907 self.show_wrap_guides = Some(show_wrap_guides);
18908 cx.notify();
18909 }
18910
18911 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18912 self.show_indent_guides = Some(show_indent_guides);
18913 cx.notify();
18914 }
18915
18916 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18917 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18918 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
18919 && let Some(dir) = file.abs_path(cx).parent()
18920 {
18921 return Some(dir.to_owned());
18922 }
18923
18924 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18925 return Some(project_path.path.to_path_buf());
18926 }
18927 }
18928
18929 None
18930 }
18931
18932 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18933 self.active_excerpt(cx)?
18934 .1
18935 .read(cx)
18936 .file()
18937 .and_then(|f| f.as_local())
18938 }
18939
18940 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18941 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18942 let buffer = buffer.read(cx);
18943 if let Some(project_path) = buffer.project_path(cx) {
18944 let project = self.project()?.read(cx);
18945 project.absolute_path(&project_path, cx)
18946 } else {
18947 buffer
18948 .file()
18949 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18950 }
18951 })
18952 }
18953
18954 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18955 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18956 let project_path = buffer.read(cx).project_path(cx)?;
18957 let project = self.project()?.read(cx);
18958 let entry = project.entry_for_path(&project_path, cx)?;
18959 let path = entry.path.to_path_buf();
18960 Some(path)
18961 })
18962 }
18963
18964 pub fn reveal_in_finder(
18965 &mut self,
18966 _: &RevealInFileManager,
18967 _window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 if let Some(target) = self.target_file(cx) {
18971 cx.reveal_path(&target.abs_path(cx));
18972 }
18973 }
18974
18975 pub fn copy_path(
18976 &mut self,
18977 _: &zed_actions::workspace::CopyPath,
18978 _window: &mut Window,
18979 cx: &mut Context<Self>,
18980 ) {
18981 if let Some(path) = self.target_file_abs_path(cx)
18982 && let Some(path) = path.to_str()
18983 {
18984 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18985 }
18986 }
18987
18988 pub fn copy_relative_path(
18989 &mut self,
18990 _: &zed_actions::workspace::CopyRelativePath,
18991 _window: &mut Window,
18992 cx: &mut Context<Self>,
18993 ) {
18994 if let Some(path) = self.target_file_path(cx)
18995 && let Some(path) = path.to_str()
18996 {
18997 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18998 }
18999 }
19000
19001 /// Returns the project path for the editor's buffer, if any buffer is
19002 /// opened in the editor.
19003 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19004 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19005 buffer.read(cx).project_path(cx)
19006 } else {
19007 None
19008 }
19009 }
19010
19011 // Returns true if the editor handled a go-to-line request
19012 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19013 maybe!({
19014 let breakpoint_store = self.breakpoint_store.as_ref()?;
19015
19016 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19017 else {
19018 self.clear_row_highlights::<ActiveDebugLine>();
19019 return None;
19020 };
19021
19022 let position = active_stack_frame.position;
19023 let buffer_id = position.buffer_id?;
19024 let snapshot = self
19025 .project
19026 .as_ref()?
19027 .read(cx)
19028 .buffer_for_id(buffer_id, cx)?
19029 .read(cx)
19030 .snapshot();
19031
19032 let mut handled = false;
19033 for (id, ExcerptRange { context, .. }) in
19034 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19035 {
19036 if context.start.cmp(&position, &snapshot).is_ge()
19037 || context.end.cmp(&position, &snapshot).is_lt()
19038 {
19039 continue;
19040 }
19041 let snapshot = self.buffer.read(cx).snapshot(cx);
19042 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19043
19044 handled = true;
19045 self.clear_row_highlights::<ActiveDebugLine>();
19046
19047 self.go_to_line::<ActiveDebugLine>(
19048 multibuffer_anchor,
19049 Some(cx.theme().colors().editor_debugger_active_line_background),
19050 window,
19051 cx,
19052 );
19053
19054 cx.notify();
19055 }
19056
19057 handled.then_some(())
19058 })
19059 .is_some()
19060 }
19061
19062 pub fn copy_file_name_without_extension(
19063 &mut self,
19064 _: &CopyFileNameWithoutExtension,
19065 _: &mut Window,
19066 cx: &mut Context<Self>,
19067 ) {
19068 if let Some(file) = self.target_file(cx)
19069 && let Some(file_stem) = file.path().file_stem()
19070 && let Some(name) = file_stem.to_str()
19071 {
19072 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19073 }
19074 }
19075
19076 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19077 if let Some(file) = self.target_file(cx)
19078 && let Some(file_name) = file.path().file_name()
19079 && let Some(name) = file_name.to_str()
19080 {
19081 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19082 }
19083 }
19084
19085 pub fn toggle_git_blame(
19086 &mut self,
19087 _: &::git::Blame,
19088 window: &mut Window,
19089 cx: &mut Context<Self>,
19090 ) {
19091 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19092
19093 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19094 self.start_git_blame(true, window, cx);
19095 }
19096
19097 cx.notify();
19098 }
19099
19100 pub fn toggle_git_blame_inline(
19101 &mut self,
19102 _: &ToggleGitBlameInline,
19103 window: &mut Window,
19104 cx: &mut Context<Self>,
19105 ) {
19106 self.toggle_git_blame_inline_internal(true, window, cx);
19107 cx.notify();
19108 }
19109
19110 pub fn open_git_blame_commit(
19111 &mut self,
19112 _: &OpenGitBlameCommit,
19113 window: &mut Window,
19114 cx: &mut Context<Self>,
19115 ) {
19116 self.open_git_blame_commit_internal(window, cx);
19117 }
19118
19119 fn open_git_blame_commit_internal(
19120 &mut self,
19121 window: &mut Window,
19122 cx: &mut Context<Self>,
19123 ) -> Option<()> {
19124 let blame = self.blame.as_ref()?;
19125 let snapshot = self.snapshot(window, cx);
19126 let cursor = self.selections.newest::<Point>(cx).head();
19127 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19128 let (_, blame_entry) = blame
19129 .update(cx, |blame, cx| {
19130 blame
19131 .blame_for_rows(
19132 &[RowInfo {
19133 buffer_id: Some(buffer.remote_id()),
19134 buffer_row: Some(point.row),
19135 ..Default::default()
19136 }],
19137 cx,
19138 )
19139 .next()
19140 })
19141 .flatten()?;
19142 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19143 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19144 let workspace = self.workspace()?.downgrade();
19145 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19146 None
19147 }
19148
19149 pub fn git_blame_inline_enabled(&self) -> bool {
19150 self.git_blame_inline_enabled
19151 }
19152
19153 pub fn toggle_selection_menu(
19154 &mut self,
19155 _: &ToggleSelectionMenu,
19156 _: &mut Window,
19157 cx: &mut Context<Self>,
19158 ) {
19159 self.show_selection_menu = self
19160 .show_selection_menu
19161 .map(|show_selections_menu| !show_selections_menu)
19162 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19163
19164 cx.notify();
19165 }
19166
19167 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19168 self.show_selection_menu
19169 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19170 }
19171
19172 fn start_git_blame(
19173 &mut self,
19174 user_triggered: bool,
19175 window: &mut Window,
19176 cx: &mut Context<Self>,
19177 ) {
19178 if let Some(project) = self.project() {
19179 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19180 && buffer.read(cx).file().is_none()
19181 {
19182 return;
19183 }
19184
19185 let focused = self.focus_handle(cx).contains_focused(window, cx);
19186
19187 let project = project.clone();
19188 let blame = cx
19189 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19190 self.blame_subscription =
19191 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19192 self.blame = Some(blame);
19193 }
19194 }
19195
19196 fn toggle_git_blame_inline_internal(
19197 &mut self,
19198 user_triggered: bool,
19199 window: &mut Window,
19200 cx: &mut Context<Self>,
19201 ) {
19202 if self.git_blame_inline_enabled {
19203 self.git_blame_inline_enabled = false;
19204 self.show_git_blame_inline = false;
19205 self.show_git_blame_inline_delay_task.take();
19206 } else {
19207 self.git_blame_inline_enabled = true;
19208 self.start_git_blame_inline(user_triggered, window, cx);
19209 }
19210
19211 cx.notify();
19212 }
19213
19214 fn start_git_blame_inline(
19215 &mut self,
19216 user_triggered: bool,
19217 window: &mut Window,
19218 cx: &mut Context<Self>,
19219 ) {
19220 self.start_git_blame(user_triggered, window, cx);
19221
19222 if ProjectSettings::get_global(cx)
19223 .git
19224 .inline_blame_delay()
19225 .is_some()
19226 {
19227 self.start_inline_blame_timer(window, cx);
19228 } else {
19229 self.show_git_blame_inline = true
19230 }
19231 }
19232
19233 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19234 self.blame.as_ref()
19235 }
19236
19237 pub fn show_git_blame_gutter(&self) -> bool {
19238 self.show_git_blame_gutter
19239 }
19240
19241 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19242 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19243 }
19244
19245 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19246 self.show_git_blame_inline
19247 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19248 && !self.newest_selection_head_on_empty_line(cx)
19249 && self.has_blame_entries(cx)
19250 }
19251
19252 fn has_blame_entries(&self, cx: &App) -> bool {
19253 self.blame()
19254 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19255 }
19256
19257 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19258 let cursor_anchor = self.selections.newest_anchor().head();
19259
19260 let snapshot = self.buffer.read(cx).snapshot(cx);
19261 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19262
19263 snapshot.line_len(buffer_row) == 0
19264 }
19265
19266 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19267 let buffer_and_selection = maybe!({
19268 let selection = self.selections.newest::<Point>(cx);
19269 let selection_range = selection.range();
19270
19271 let multi_buffer = self.buffer().read(cx);
19272 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19273 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19274
19275 let (buffer, range, _) = if selection.reversed {
19276 buffer_ranges.first()
19277 } else {
19278 buffer_ranges.last()
19279 }?;
19280
19281 let selection = text::ToPoint::to_point(&range.start, buffer).row
19282 ..text::ToPoint::to_point(&range.end, buffer).row;
19283 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19284 });
19285
19286 let Some((buffer, selection)) = buffer_and_selection else {
19287 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19288 };
19289
19290 let Some(project) = self.project() else {
19291 return Task::ready(Err(anyhow!("editor does not have project")));
19292 };
19293
19294 project.update(cx, |project, cx| {
19295 project.get_permalink_to_line(&buffer, selection, cx)
19296 })
19297 }
19298
19299 pub fn copy_permalink_to_line(
19300 &mut self,
19301 _: &CopyPermalinkToLine,
19302 window: &mut Window,
19303 cx: &mut Context<Self>,
19304 ) {
19305 let permalink_task = self.get_permalink_to_line(cx);
19306 let workspace = self.workspace();
19307
19308 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19309 Ok(permalink) => {
19310 cx.update(|_, cx| {
19311 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19312 })
19313 .ok();
19314 }
19315 Err(err) => {
19316 let message = format!("Failed to copy permalink: {err}");
19317
19318 anyhow::Result::<()>::Err(err).log_err();
19319
19320 if let Some(workspace) = workspace {
19321 workspace
19322 .update_in(cx, |workspace, _, cx| {
19323 struct CopyPermalinkToLine;
19324
19325 workspace.show_toast(
19326 Toast::new(
19327 NotificationId::unique::<CopyPermalinkToLine>(),
19328 message,
19329 ),
19330 cx,
19331 )
19332 })
19333 .ok();
19334 }
19335 }
19336 })
19337 .detach();
19338 }
19339
19340 pub fn copy_file_location(
19341 &mut self,
19342 _: &CopyFileLocation,
19343 _: &mut Window,
19344 cx: &mut Context<Self>,
19345 ) {
19346 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19347 if let Some(file) = self.target_file(cx)
19348 && let Some(path) = file.path().to_str()
19349 {
19350 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19351 }
19352 }
19353
19354 pub fn open_permalink_to_line(
19355 &mut self,
19356 _: &OpenPermalinkToLine,
19357 window: &mut Window,
19358 cx: &mut Context<Self>,
19359 ) {
19360 let permalink_task = self.get_permalink_to_line(cx);
19361 let workspace = self.workspace();
19362
19363 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19364 Ok(permalink) => {
19365 cx.update(|_, cx| {
19366 cx.open_url(permalink.as_ref());
19367 })
19368 .ok();
19369 }
19370 Err(err) => {
19371 let message = format!("Failed to open permalink: {err}");
19372
19373 anyhow::Result::<()>::Err(err).log_err();
19374
19375 if let Some(workspace) = workspace {
19376 workspace
19377 .update(cx, |workspace, cx| {
19378 struct OpenPermalinkToLine;
19379
19380 workspace.show_toast(
19381 Toast::new(
19382 NotificationId::unique::<OpenPermalinkToLine>(),
19383 message,
19384 ),
19385 cx,
19386 )
19387 })
19388 .ok();
19389 }
19390 }
19391 })
19392 .detach();
19393 }
19394
19395 pub fn insert_uuid_v4(
19396 &mut self,
19397 _: &InsertUuidV4,
19398 window: &mut Window,
19399 cx: &mut Context<Self>,
19400 ) {
19401 self.insert_uuid(UuidVersion::V4, window, cx);
19402 }
19403
19404 pub fn insert_uuid_v7(
19405 &mut self,
19406 _: &InsertUuidV7,
19407 window: &mut Window,
19408 cx: &mut Context<Self>,
19409 ) {
19410 self.insert_uuid(UuidVersion::V7, window, cx);
19411 }
19412
19413 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19415 self.transact(window, cx, |this, window, cx| {
19416 let edits = this
19417 .selections
19418 .all::<Point>(cx)
19419 .into_iter()
19420 .map(|selection| {
19421 let uuid = match version {
19422 UuidVersion::V4 => uuid::Uuid::new_v4(),
19423 UuidVersion::V7 => uuid::Uuid::now_v7(),
19424 };
19425
19426 (selection.range(), uuid.to_string())
19427 });
19428 this.edit(edits, cx);
19429 this.refresh_edit_prediction(true, false, window, cx);
19430 });
19431 }
19432
19433 pub fn open_selections_in_multibuffer(
19434 &mut self,
19435 _: &OpenSelectionsInMultibuffer,
19436 window: &mut Window,
19437 cx: &mut Context<Self>,
19438 ) {
19439 let multibuffer = self.buffer.read(cx);
19440
19441 let Some(buffer) = multibuffer.as_singleton() else {
19442 return;
19443 };
19444
19445 let Some(workspace) = self.workspace() else {
19446 return;
19447 };
19448
19449 let title = multibuffer.title(cx).to_string();
19450
19451 let locations = self
19452 .selections
19453 .all_anchors(cx)
19454 .iter()
19455 .map(|selection| Location {
19456 buffer: buffer.clone(),
19457 range: selection.start.text_anchor..selection.end.text_anchor,
19458 })
19459 .collect::<Vec<_>>();
19460
19461 cx.spawn_in(window, async move |_, cx| {
19462 workspace.update_in(cx, |workspace, window, cx| {
19463 Self::open_locations_in_multibuffer(
19464 workspace,
19465 locations,
19466 format!("Selections for '{title}'"),
19467 false,
19468 MultibufferSelectionMode::All,
19469 window,
19470 cx,
19471 );
19472 })
19473 })
19474 .detach();
19475 }
19476
19477 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19478 /// last highlight added will be used.
19479 ///
19480 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19481 pub fn highlight_rows<T: 'static>(
19482 &mut self,
19483 range: Range<Anchor>,
19484 color: Hsla,
19485 options: RowHighlightOptions,
19486 cx: &mut Context<Self>,
19487 ) {
19488 let snapshot = self.buffer().read(cx).snapshot(cx);
19489 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19490 let ix = row_highlights.binary_search_by(|highlight| {
19491 Ordering::Equal
19492 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19493 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19494 });
19495
19496 if let Err(mut ix) = ix {
19497 let index = post_inc(&mut self.highlight_order);
19498
19499 // If this range intersects with the preceding highlight, then merge it with
19500 // the preceding highlight. Otherwise insert a new highlight.
19501 let mut merged = false;
19502 if ix > 0 {
19503 let prev_highlight = &mut row_highlights[ix - 1];
19504 if prev_highlight
19505 .range
19506 .end
19507 .cmp(&range.start, &snapshot)
19508 .is_ge()
19509 {
19510 ix -= 1;
19511 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19512 prev_highlight.range.end = range.end;
19513 }
19514 merged = true;
19515 prev_highlight.index = index;
19516 prev_highlight.color = color;
19517 prev_highlight.options = options;
19518 }
19519 }
19520
19521 if !merged {
19522 row_highlights.insert(
19523 ix,
19524 RowHighlight {
19525 range,
19526 index,
19527 color,
19528 options,
19529 type_id: TypeId::of::<T>(),
19530 },
19531 );
19532 }
19533
19534 // If any of the following highlights intersect with this one, merge them.
19535 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19536 let highlight = &row_highlights[ix];
19537 if next_highlight
19538 .range
19539 .start
19540 .cmp(&highlight.range.end, &snapshot)
19541 .is_le()
19542 {
19543 if next_highlight
19544 .range
19545 .end
19546 .cmp(&highlight.range.end, &snapshot)
19547 .is_gt()
19548 {
19549 row_highlights[ix].range.end = next_highlight.range.end;
19550 }
19551 row_highlights.remove(ix + 1);
19552 } else {
19553 break;
19554 }
19555 }
19556 }
19557 }
19558
19559 /// Remove any highlighted row ranges of the given type that intersect the
19560 /// given ranges.
19561 pub fn remove_highlighted_rows<T: 'static>(
19562 &mut self,
19563 ranges_to_remove: Vec<Range<Anchor>>,
19564 cx: &mut Context<Self>,
19565 ) {
19566 let snapshot = self.buffer().read(cx).snapshot(cx);
19567 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19568 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19569 row_highlights.retain(|highlight| {
19570 while let Some(range_to_remove) = ranges_to_remove.peek() {
19571 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19572 Ordering::Less | Ordering::Equal => {
19573 ranges_to_remove.next();
19574 }
19575 Ordering::Greater => {
19576 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19577 Ordering::Less | Ordering::Equal => {
19578 return false;
19579 }
19580 Ordering::Greater => break,
19581 }
19582 }
19583 }
19584 }
19585
19586 true
19587 })
19588 }
19589
19590 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19591 pub fn clear_row_highlights<T: 'static>(&mut self) {
19592 self.highlighted_rows.remove(&TypeId::of::<T>());
19593 }
19594
19595 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19596 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19597 self.highlighted_rows
19598 .get(&TypeId::of::<T>())
19599 .map_or(&[] as &[_], |vec| vec.as_slice())
19600 .iter()
19601 .map(|highlight| (highlight.range.clone(), highlight.color))
19602 }
19603
19604 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19605 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19606 /// Allows to ignore certain kinds of highlights.
19607 pub fn highlighted_display_rows(
19608 &self,
19609 window: &mut Window,
19610 cx: &mut App,
19611 ) -> BTreeMap<DisplayRow, LineHighlight> {
19612 let snapshot = self.snapshot(window, cx);
19613 let mut used_highlight_orders = HashMap::default();
19614 self.highlighted_rows
19615 .iter()
19616 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19617 .fold(
19618 BTreeMap::<DisplayRow, LineHighlight>::new(),
19619 |mut unique_rows, highlight| {
19620 let start = highlight.range.start.to_display_point(&snapshot);
19621 let end = highlight.range.end.to_display_point(&snapshot);
19622 let start_row = start.row().0;
19623 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19624 && end.column() == 0
19625 {
19626 end.row().0.saturating_sub(1)
19627 } else {
19628 end.row().0
19629 };
19630 for row in start_row..=end_row {
19631 let used_index =
19632 used_highlight_orders.entry(row).or_insert(highlight.index);
19633 if highlight.index >= *used_index {
19634 *used_index = highlight.index;
19635 unique_rows.insert(
19636 DisplayRow(row),
19637 LineHighlight {
19638 include_gutter: highlight.options.include_gutter,
19639 border: None,
19640 background: highlight.color.into(),
19641 type_id: Some(highlight.type_id),
19642 },
19643 );
19644 }
19645 }
19646 unique_rows
19647 },
19648 )
19649 }
19650
19651 pub fn highlighted_display_row_for_autoscroll(
19652 &self,
19653 snapshot: &DisplaySnapshot,
19654 ) -> Option<DisplayRow> {
19655 self.highlighted_rows
19656 .values()
19657 .flat_map(|highlighted_rows| highlighted_rows.iter())
19658 .filter_map(|highlight| {
19659 if highlight.options.autoscroll {
19660 Some(highlight.range.start.to_display_point(snapshot).row())
19661 } else {
19662 None
19663 }
19664 })
19665 .min()
19666 }
19667
19668 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19669 self.highlight_background::<SearchWithinRange>(
19670 ranges,
19671 |colors| colors.colors().editor_document_highlight_read_background,
19672 cx,
19673 )
19674 }
19675
19676 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19677 self.breadcrumb_header = Some(new_header);
19678 }
19679
19680 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19681 self.clear_background_highlights::<SearchWithinRange>(cx);
19682 }
19683
19684 pub fn highlight_background<T: 'static>(
19685 &mut self,
19686 ranges: &[Range<Anchor>],
19687 color_fetcher: fn(&Theme) -> Hsla,
19688 cx: &mut Context<Self>,
19689 ) {
19690 self.background_highlights.insert(
19691 HighlightKey::Type(TypeId::of::<T>()),
19692 (color_fetcher, Arc::from(ranges)),
19693 );
19694 self.scrollbar_marker_state.dirty = true;
19695 cx.notify();
19696 }
19697
19698 pub fn highlight_background_key<T: 'static>(
19699 &mut self,
19700 key: usize,
19701 ranges: &[Range<Anchor>],
19702 color_fetcher: fn(&Theme) -> Hsla,
19703 cx: &mut Context<Self>,
19704 ) {
19705 self.background_highlights.insert(
19706 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19707 (color_fetcher, Arc::from(ranges)),
19708 );
19709 self.scrollbar_marker_state.dirty = true;
19710 cx.notify();
19711 }
19712
19713 pub fn clear_background_highlights<T: 'static>(
19714 &mut self,
19715 cx: &mut Context<Self>,
19716 ) -> Option<BackgroundHighlight> {
19717 let text_highlights = self
19718 .background_highlights
19719 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19720 if !text_highlights.1.is_empty() {
19721 self.scrollbar_marker_state.dirty = true;
19722 cx.notify();
19723 }
19724 Some(text_highlights)
19725 }
19726
19727 pub fn highlight_gutter<T: 'static>(
19728 &mut self,
19729 ranges: impl Into<Vec<Range<Anchor>>>,
19730 color_fetcher: fn(&App) -> Hsla,
19731 cx: &mut Context<Self>,
19732 ) {
19733 self.gutter_highlights
19734 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19735 cx.notify();
19736 }
19737
19738 pub fn clear_gutter_highlights<T: 'static>(
19739 &mut self,
19740 cx: &mut Context<Self>,
19741 ) -> Option<GutterHighlight> {
19742 cx.notify();
19743 self.gutter_highlights.remove(&TypeId::of::<T>())
19744 }
19745
19746 pub fn insert_gutter_highlight<T: 'static>(
19747 &mut self,
19748 range: Range<Anchor>,
19749 color_fetcher: fn(&App) -> Hsla,
19750 cx: &mut Context<Self>,
19751 ) {
19752 let snapshot = self.buffer().read(cx).snapshot(cx);
19753 let mut highlights = self
19754 .gutter_highlights
19755 .remove(&TypeId::of::<T>())
19756 .map(|(_, highlights)| highlights)
19757 .unwrap_or_default();
19758 let ix = highlights.binary_search_by(|highlight| {
19759 Ordering::Equal
19760 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19761 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19762 });
19763 if let Err(ix) = ix {
19764 highlights.insert(ix, range);
19765 }
19766 self.gutter_highlights
19767 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19768 }
19769
19770 pub fn remove_gutter_highlights<T: 'static>(
19771 &mut self,
19772 ranges_to_remove: Vec<Range<Anchor>>,
19773 cx: &mut Context<Self>,
19774 ) {
19775 let snapshot = self.buffer().read(cx).snapshot(cx);
19776 let Some((color_fetcher, mut gutter_highlights)) =
19777 self.gutter_highlights.remove(&TypeId::of::<T>())
19778 else {
19779 return;
19780 };
19781 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19782 gutter_highlights.retain(|highlight| {
19783 while let Some(range_to_remove) = ranges_to_remove.peek() {
19784 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19785 Ordering::Less | Ordering::Equal => {
19786 ranges_to_remove.next();
19787 }
19788 Ordering::Greater => {
19789 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19790 Ordering::Less | Ordering::Equal => {
19791 return false;
19792 }
19793 Ordering::Greater => break,
19794 }
19795 }
19796 }
19797 }
19798
19799 true
19800 });
19801 self.gutter_highlights
19802 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19803 }
19804
19805 #[cfg(feature = "test-support")]
19806 pub fn all_text_highlights(
19807 &self,
19808 window: &mut Window,
19809 cx: &mut Context<Self>,
19810 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19811 let snapshot = self.snapshot(window, cx);
19812 self.display_map.update(cx, |display_map, _| {
19813 display_map
19814 .all_text_highlights()
19815 .map(|highlight| {
19816 let (style, ranges) = highlight.as_ref();
19817 (
19818 *style,
19819 ranges
19820 .iter()
19821 .map(|range| range.clone().to_display_points(&snapshot))
19822 .collect(),
19823 )
19824 })
19825 .collect()
19826 })
19827 }
19828
19829 #[cfg(feature = "test-support")]
19830 pub fn all_text_background_highlights(
19831 &self,
19832 window: &mut Window,
19833 cx: &mut Context<Self>,
19834 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19835 let snapshot = self.snapshot(window, cx);
19836 let buffer = &snapshot.buffer_snapshot;
19837 let start = buffer.anchor_before(0);
19838 let end = buffer.anchor_after(buffer.len());
19839 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
19840 }
19841
19842 #[cfg(any(test, feature = "test-support"))]
19843 pub fn sorted_background_highlights_in_range(
19844 &self,
19845 search_range: Range<Anchor>,
19846 display_snapshot: &DisplaySnapshot,
19847 theme: &Theme,
19848 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19849 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
19850 res.sort_by(|a, b| {
19851 a.0.start
19852 .cmp(&b.0.start)
19853 .then_with(|| a.0.end.cmp(&b.0.end))
19854 .then_with(|| a.1.cmp(&b.1))
19855 });
19856 res
19857 }
19858
19859 #[cfg(feature = "test-support")]
19860 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19861 let snapshot = self.buffer().read(cx).snapshot(cx);
19862
19863 let highlights = self
19864 .background_highlights
19865 .get(&HighlightKey::Type(TypeId::of::<
19866 items::BufferSearchHighlights,
19867 >()));
19868
19869 if let Some((_color, ranges)) = highlights {
19870 ranges
19871 .iter()
19872 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19873 .collect_vec()
19874 } else {
19875 vec![]
19876 }
19877 }
19878
19879 fn document_highlights_for_position<'a>(
19880 &'a self,
19881 position: Anchor,
19882 buffer: &'a MultiBufferSnapshot,
19883 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19884 let read_highlights = self
19885 .background_highlights
19886 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19887 .map(|h| &h.1);
19888 let write_highlights = self
19889 .background_highlights
19890 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19891 .map(|h| &h.1);
19892 let left_position = position.bias_left(buffer);
19893 let right_position = position.bias_right(buffer);
19894 read_highlights
19895 .into_iter()
19896 .chain(write_highlights)
19897 .flat_map(move |ranges| {
19898 let start_ix = match ranges.binary_search_by(|probe| {
19899 let cmp = probe.end.cmp(&left_position, buffer);
19900 if cmp.is_ge() {
19901 Ordering::Greater
19902 } else {
19903 Ordering::Less
19904 }
19905 }) {
19906 Ok(i) | Err(i) => i,
19907 };
19908
19909 ranges[start_ix..]
19910 .iter()
19911 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19912 })
19913 }
19914
19915 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19916 self.background_highlights
19917 .get(&HighlightKey::Type(TypeId::of::<T>()))
19918 .is_some_and(|(_, highlights)| !highlights.is_empty())
19919 }
19920
19921 /// Returns all background highlights for a given range.
19922 ///
19923 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
19924 pub fn background_highlights_in_range(
19925 &self,
19926 search_range: Range<Anchor>,
19927 display_snapshot: &DisplaySnapshot,
19928 theme: &Theme,
19929 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19930 let mut results = Vec::new();
19931 for (color_fetcher, ranges) in self.background_highlights.values() {
19932 let color = color_fetcher(theme);
19933 let start_ix = match ranges.binary_search_by(|probe| {
19934 let cmp = probe
19935 .end
19936 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19937 if cmp.is_gt() {
19938 Ordering::Greater
19939 } else {
19940 Ordering::Less
19941 }
19942 }) {
19943 Ok(i) | Err(i) => i,
19944 };
19945 for range in &ranges[start_ix..] {
19946 if range
19947 .start
19948 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19949 .is_ge()
19950 {
19951 break;
19952 }
19953
19954 let start = range.start.to_display_point(display_snapshot);
19955 let end = range.end.to_display_point(display_snapshot);
19956 results.push((start..end, color))
19957 }
19958 }
19959 results
19960 }
19961
19962 pub fn gutter_highlights_in_range(
19963 &self,
19964 search_range: Range<Anchor>,
19965 display_snapshot: &DisplaySnapshot,
19966 cx: &App,
19967 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19968 let mut results = Vec::new();
19969 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19970 let color = color_fetcher(cx);
19971 let start_ix = match ranges.binary_search_by(|probe| {
19972 let cmp = probe
19973 .end
19974 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19975 if cmp.is_gt() {
19976 Ordering::Greater
19977 } else {
19978 Ordering::Less
19979 }
19980 }) {
19981 Ok(i) | Err(i) => i,
19982 };
19983 for range in &ranges[start_ix..] {
19984 if range
19985 .start
19986 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19987 .is_ge()
19988 {
19989 break;
19990 }
19991
19992 let start = range.start.to_display_point(display_snapshot);
19993 let end = range.end.to_display_point(display_snapshot);
19994 results.push((start..end, color))
19995 }
19996 }
19997 results
19998 }
19999
20000 /// Get the text ranges corresponding to the redaction query
20001 pub fn redacted_ranges(
20002 &self,
20003 search_range: Range<Anchor>,
20004 display_snapshot: &DisplaySnapshot,
20005 cx: &App,
20006 ) -> Vec<Range<DisplayPoint>> {
20007 display_snapshot
20008 .buffer_snapshot
20009 .redacted_ranges(search_range, |file| {
20010 if let Some(file) = file {
20011 file.is_private()
20012 && EditorSettings::get(
20013 Some(SettingsLocation {
20014 worktree_id: file.worktree_id(cx),
20015 path: file.path().as_ref(),
20016 }),
20017 cx,
20018 )
20019 .redact_private_values
20020 } else {
20021 false
20022 }
20023 })
20024 .map(|range| {
20025 range.start.to_display_point(display_snapshot)
20026 ..range.end.to_display_point(display_snapshot)
20027 })
20028 .collect()
20029 }
20030
20031 pub fn highlight_text_key<T: 'static>(
20032 &mut self,
20033 key: usize,
20034 ranges: Vec<Range<Anchor>>,
20035 style: HighlightStyle,
20036 cx: &mut Context<Self>,
20037 ) {
20038 self.display_map.update(cx, |map, _| {
20039 map.highlight_text(
20040 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20041 ranges,
20042 style,
20043 );
20044 });
20045 cx.notify();
20046 }
20047
20048 pub fn highlight_text<T: 'static>(
20049 &mut self,
20050 ranges: Vec<Range<Anchor>>,
20051 style: HighlightStyle,
20052 cx: &mut Context<Self>,
20053 ) {
20054 self.display_map.update(cx, |map, _| {
20055 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20056 });
20057 cx.notify();
20058 }
20059
20060 pub(crate) fn highlight_inlays<T: 'static>(
20061 &mut self,
20062 highlights: Vec<InlayHighlight>,
20063 style: HighlightStyle,
20064 cx: &mut Context<Self>,
20065 ) {
20066 self.display_map.update(cx, |map, _| {
20067 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20068 });
20069 cx.notify();
20070 }
20071
20072 pub fn text_highlights<'a, T: 'static>(
20073 &'a self,
20074 cx: &'a App,
20075 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20076 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20077 }
20078
20079 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20080 let cleared = self
20081 .display_map
20082 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20083 if cleared {
20084 cx.notify();
20085 }
20086 }
20087
20088 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20089 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20090 && self.focus_handle.is_focused(window)
20091 }
20092
20093 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20094 self.show_cursor_when_unfocused = is_enabled;
20095 cx.notify();
20096 }
20097
20098 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20099 cx.notify();
20100 }
20101
20102 fn on_debug_session_event(
20103 &mut self,
20104 _session: Entity<Session>,
20105 event: &SessionEvent,
20106 cx: &mut Context<Self>,
20107 ) {
20108 if let SessionEvent::InvalidateInlineValue = event {
20109 self.refresh_inline_values(cx);
20110 }
20111 }
20112
20113 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20114 let Some(project) = self.project.clone() else {
20115 return;
20116 };
20117
20118 if !self.inline_value_cache.enabled {
20119 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20120 self.splice_inlays(&inlays, Vec::new(), cx);
20121 return;
20122 }
20123
20124 let current_execution_position = self
20125 .highlighted_rows
20126 .get(&TypeId::of::<ActiveDebugLine>())
20127 .and_then(|lines| lines.last().map(|line| line.range.end));
20128
20129 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20130 let inline_values = editor
20131 .update(cx, |editor, cx| {
20132 let Some(current_execution_position) = current_execution_position else {
20133 return Some(Task::ready(Ok(Vec::new())));
20134 };
20135
20136 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20137 let snapshot = buffer.snapshot(cx);
20138
20139 let excerpt = snapshot.excerpt_containing(
20140 current_execution_position..current_execution_position,
20141 )?;
20142
20143 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20144 })?;
20145
20146 let range =
20147 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20148
20149 project.inline_values(buffer, range, cx)
20150 })
20151 .ok()
20152 .flatten()?
20153 .await
20154 .context("refreshing debugger inlays")
20155 .log_err()?;
20156
20157 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20158
20159 for (buffer_id, inline_value) in inline_values
20160 .into_iter()
20161 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20162 {
20163 buffer_inline_values
20164 .entry(buffer_id)
20165 .or_default()
20166 .push(inline_value);
20167 }
20168
20169 editor
20170 .update(cx, |editor, cx| {
20171 let snapshot = editor.buffer.read(cx).snapshot(cx);
20172 let mut new_inlays = Vec::default();
20173
20174 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20175 let buffer_id = buffer_snapshot.remote_id();
20176 buffer_inline_values
20177 .get(&buffer_id)
20178 .into_iter()
20179 .flatten()
20180 .for_each(|hint| {
20181 let inlay = Inlay::debugger(
20182 post_inc(&mut editor.next_inlay_id),
20183 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20184 hint.text(),
20185 );
20186 if !inlay.text.chars().contains(&'\n') {
20187 new_inlays.push(inlay);
20188 }
20189 });
20190 }
20191
20192 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20193 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20194
20195 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20196 })
20197 .ok()?;
20198 Some(())
20199 });
20200 }
20201
20202 fn on_buffer_event(
20203 &mut self,
20204 multibuffer: &Entity<MultiBuffer>,
20205 event: &multi_buffer::Event,
20206 window: &mut Window,
20207 cx: &mut Context<Self>,
20208 ) {
20209 match event {
20210 multi_buffer::Event::Edited {
20211 singleton_buffer_edited,
20212 edited_buffer,
20213 } => {
20214 self.scrollbar_marker_state.dirty = true;
20215 self.active_indent_guides_state.dirty = true;
20216 self.refresh_active_diagnostics(cx);
20217 self.refresh_code_actions(window, cx);
20218 self.refresh_selected_text_highlights(true, window, cx);
20219 self.refresh_single_line_folds(window, cx);
20220 refresh_matching_bracket_highlights(self, window, cx);
20221 if self.has_active_edit_prediction() {
20222 self.update_visible_edit_prediction(window, cx);
20223 }
20224 if let Some(project) = self.project.as_ref()
20225 && let Some(edited_buffer) = edited_buffer
20226 {
20227 project.update(cx, |project, cx| {
20228 self.registered_buffers
20229 .entry(edited_buffer.read(cx).remote_id())
20230 .or_insert_with(|| {
20231 project.register_buffer_with_language_servers(edited_buffer, cx)
20232 });
20233 });
20234 }
20235 cx.emit(EditorEvent::BufferEdited);
20236 cx.emit(SearchEvent::MatchesInvalidated);
20237
20238 if let Some(buffer) = edited_buffer {
20239 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20240 }
20241
20242 if *singleton_buffer_edited {
20243 if let Some(buffer) = edited_buffer
20244 && buffer.read(cx).file().is_none()
20245 {
20246 cx.emit(EditorEvent::TitleChanged);
20247 }
20248 if let Some(project) = &self.project {
20249 #[allow(clippy::mutable_key_type)]
20250 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20251 multibuffer
20252 .all_buffers()
20253 .into_iter()
20254 .filter_map(|buffer| {
20255 buffer.update(cx, |buffer, cx| {
20256 let language = buffer.language()?;
20257 let should_discard = project.update(cx, |project, cx| {
20258 project.is_local()
20259 && !project.has_language_servers_for(buffer, cx)
20260 });
20261 should_discard.not().then_some(language.clone())
20262 })
20263 })
20264 .collect::<HashSet<_>>()
20265 });
20266 if !languages_affected.is_empty() {
20267 self.refresh_inlay_hints(
20268 InlayHintRefreshReason::BufferEdited(languages_affected),
20269 cx,
20270 );
20271 }
20272 }
20273 }
20274
20275 let Some(project) = &self.project else { return };
20276 let (telemetry, is_via_ssh) = {
20277 let project = project.read(cx);
20278 let telemetry = project.client().telemetry().clone();
20279 let is_via_ssh = project.is_via_remote_server();
20280 (telemetry, is_via_ssh)
20281 };
20282 refresh_linked_ranges(self, window, cx);
20283 telemetry.log_edit_event("editor", is_via_ssh);
20284 }
20285 multi_buffer::Event::ExcerptsAdded {
20286 buffer,
20287 predecessor,
20288 excerpts,
20289 } => {
20290 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20291 let buffer_id = buffer.read(cx).remote_id();
20292 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20293 && let Some(project) = &self.project
20294 {
20295 update_uncommitted_diff_for_buffer(
20296 cx.entity(),
20297 project,
20298 [buffer.clone()],
20299 self.buffer.clone(),
20300 cx,
20301 )
20302 .detach();
20303 }
20304 self.update_lsp_data(false, Some(buffer_id), window, cx);
20305 cx.emit(EditorEvent::ExcerptsAdded {
20306 buffer: buffer.clone(),
20307 predecessor: *predecessor,
20308 excerpts: excerpts.clone(),
20309 });
20310 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20311 }
20312 multi_buffer::Event::ExcerptsRemoved {
20313 ids,
20314 removed_buffer_ids,
20315 } => {
20316 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20317 let buffer = self.buffer.read(cx);
20318 self.registered_buffers
20319 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20320 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20321 cx.emit(EditorEvent::ExcerptsRemoved {
20322 ids: ids.clone(),
20323 removed_buffer_ids: removed_buffer_ids.clone(),
20324 });
20325 }
20326 multi_buffer::Event::ExcerptsEdited {
20327 excerpt_ids,
20328 buffer_ids,
20329 } => {
20330 self.display_map.update(cx, |map, cx| {
20331 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20332 });
20333 cx.emit(EditorEvent::ExcerptsEdited {
20334 ids: excerpt_ids.clone(),
20335 });
20336 }
20337 multi_buffer::Event::ExcerptsExpanded { ids } => {
20338 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20339 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20340 }
20341 multi_buffer::Event::Reparsed(buffer_id) => {
20342 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20343 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20344
20345 cx.emit(EditorEvent::Reparsed(*buffer_id));
20346 }
20347 multi_buffer::Event::DiffHunksToggled => {
20348 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20349 }
20350 multi_buffer::Event::LanguageChanged(buffer_id) => {
20351 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20352 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20353 cx.emit(EditorEvent::Reparsed(*buffer_id));
20354 cx.notify();
20355 }
20356 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20357 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20358 multi_buffer::Event::FileHandleChanged
20359 | multi_buffer::Event::Reloaded
20360 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20361 multi_buffer::Event::DiagnosticsUpdated => {
20362 self.update_diagnostics_state(window, cx);
20363 }
20364 _ => {}
20365 };
20366 }
20367
20368 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20369 if !self.diagnostics_enabled() {
20370 return;
20371 }
20372 self.refresh_active_diagnostics(cx);
20373 self.refresh_inline_diagnostics(true, window, cx);
20374 self.scrollbar_marker_state.dirty = true;
20375 cx.notify();
20376 }
20377
20378 pub fn start_temporary_diff_override(&mut self) {
20379 self.load_diff_task.take();
20380 self.temporary_diff_override = true;
20381 }
20382
20383 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20384 self.temporary_diff_override = false;
20385 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20386 self.buffer.update(cx, |buffer, cx| {
20387 buffer.set_all_diff_hunks_collapsed(cx);
20388 });
20389
20390 if let Some(project) = self.project.clone() {
20391 self.load_diff_task = Some(
20392 update_uncommitted_diff_for_buffer(
20393 cx.entity(),
20394 &project,
20395 self.buffer.read(cx).all_buffers(),
20396 self.buffer.clone(),
20397 cx,
20398 )
20399 .shared(),
20400 );
20401 }
20402 }
20403
20404 fn on_display_map_changed(
20405 &mut self,
20406 _: Entity<DisplayMap>,
20407 _: &mut Window,
20408 cx: &mut Context<Self>,
20409 ) {
20410 cx.notify();
20411 }
20412
20413 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20414 if self.diagnostics_enabled() {
20415 let new_severity = EditorSettings::get_global(cx)
20416 .diagnostics_max_severity
20417 .unwrap_or(DiagnosticSeverity::Hint);
20418 self.set_max_diagnostics_severity(new_severity, cx);
20419 }
20420 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20421 self.update_edit_prediction_settings(cx);
20422 self.refresh_edit_prediction(true, false, window, cx);
20423 self.refresh_inline_values(cx);
20424 self.refresh_inlay_hints(
20425 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20426 self.selections.newest_anchor().head(),
20427 &self.buffer.read(cx).snapshot(cx),
20428 cx,
20429 )),
20430 cx,
20431 );
20432
20433 let old_cursor_shape = self.cursor_shape;
20434 let old_show_breadcrumbs = self.show_breadcrumbs;
20435
20436 {
20437 let editor_settings = EditorSettings::get_global(cx);
20438 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20439 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20440 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20441 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20442 }
20443
20444 if old_cursor_shape != self.cursor_shape {
20445 cx.emit(EditorEvent::CursorShapeChanged);
20446 }
20447
20448 if old_show_breadcrumbs != self.show_breadcrumbs {
20449 cx.emit(EditorEvent::BreadcrumbsChanged);
20450 }
20451
20452 let project_settings = ProjectSettings::get_global(cx);
20453 self.serialize_dirty_buffers =
20454 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20455
20456 if self.mode.is_full() {
20457 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20458 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20459 if self.show_inline_diagnostics != show_inline_diagnostics {
20460 self.show_inline_diagnostics = show_inline_diagnostics;
20461 self.refresh_inline_diagnostics(false, window, cx);
20462 }
20463
20464 if self.git_blame_inline_enabled != inline_blame_enabled {
20465 self.toggle_git_blame_inline_internal(false, window, cx);
20466 }
20467
20468 let minimap_settings = EditorSettings::get_global(cx).minimap;
20469 if self.minimap_visibility != MinimapVisibility::Disabled {
20470 if self.minimap_visibility.settings_visibility()
20471 != minimap_settings.minimap_enabled()
20472 {
20473 self.set_minimap_visibility(
20474 MinimapVisibility::for_mode(self.mode(), cx),
20475 window,
20476 cx,
20477 );
20478 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20479 minimap_entity.update(cx, |minimap_editor, cx| {
20480 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20481 })
20482 }
20483 }
20484 }
20485
20486 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20487 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20488 }) {
20489 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20490 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20491 }
20492 self.refresh_colors(false, None, window, cx);
20493 }
20494
20495 cx.notify();
20496 }
20497
20498 pub fn set_searchable(&mut self, searchable: bool) {
20499 self.searchable = searchable;
20500 }
20501
20502 pub fn searchable(&self) -> bool {
20503 self.searchable
20504 }
20505
20506 fn open_proposed_changes_editor(
20507 &mut self,
20508 _: &OpenProposedChangesEditor,
20509 window: &mut Window,
20510 cx: &mut Context<Self>,
20511 ) {
20512 let Some(workspace) = self.workspace() else {
20513 cx.propagate();
20514 return;
20515 };
20516
20517 let selections = self.selections.all::<usize>(cx);
20518 let multi_buffer = self.buffer.read(cx);
20519 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20520 let mut new_selections_by_buffer = HashMap::default();
20521 for selection in selections {
20522 for (buffer, range, _) in
20523 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20524 {
20525 let mut range = range.to_point(buffer);
20526 range.start.column = 0;
20527 range.end.column = buffer.line_len(range.end.row);
20528 new_selections_by_buffer
20529 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20530 .or_insert(Vec::new())
20531 .push(range)
20532 }
20533 }
20534
20535 let proposed_changes_buffers = new_selections_by_buffer
20536 .into_iter()
20537 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20538 .collect::<Vec<_>>();
20539 let proposed_changes_editor = cx.new(|cx| {
20540 ProposedChangesEditor::new(
20541 "Proposed changes",
20542 proposed_changes_buffers,
20543 self.project.clone(),
20544 window,
20545 cx,
20546 )
20547 });
20548
20549 window.defer(cx, move |window, cx| {
20550 workspace.update(cx, |workspace, cx| {
20551 workspace.active_pane().update(cx, |pane, cx| {
20552 pane.add_item(
20553 Box::new(proposed_changes_editor),
20554 true,
20555 true,
20556 None,
20557 window,
20558 cx,
20559 );
20560 });
20561 });
20562 });
20563 }
20564
20565 pub fn open_excerpts_in_split(
20566 &mut self,
20567 _: &OpenExcerptsSplit,
20568 window: &mut Window,
20569 cx: &mut Context<Self>,
20570 ) {
20571 self.open_excerpts_common(None, true, window, cx)
20572 }
20573
20574 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20575 self.open_excerpts_common(None, false, window, cx)
20576 }
20577
20578 fn open_excerpts_common(
20579 &mut self,
20580 jump_data: Option<JumpData>,
20581 split: bool,
20582 window: &mut Window,
20583 cx: &mut Context<Self>,
20584 ) {
20585 let Some(workspace) = self.workspace() else {
20586 cx.propagate();
20587 return;
20588 };
20589
20590 if self.buffer.read(cx).is_singleton() {
20591 cx.propagate();
20592 return;
20593 }
20594
20595 let mut new_selections_by_buffer = HashMap::default();
20596 match &jump_data {
20597 Some(JumpData::MultiBufferPoint {
20598 excerpt_id,
20599 position,
20600 anchor,
20601 line_offset_from_top,
20602 }) => {
20603 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20604 if let Some(buffer) = multi_buffer_snapshot
20605 .buffer_id_for_excerpt(*excerpt_id)
20606 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20607 {
20608 let buffer_snapshot = buffer.read(cx).snapshot();
20609 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20610 language::ToPoint::to_point(anchor, &buffer_snapshot)
20611 } else {
20612 buffer_snapshot.clip_point(*position, Bias::Left)
20613 };
20614 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20615 new_selections_by_buffer.insert(
20616 buffer,
20617 (
20618 vec![jump_to_offset..jump_to_offset],
20619 Some(*line_offset_from_top),
20620 ),
20621 );
20622 }
20623 }
20624 Some(JumpData::MultiBufferRow {
20625 row,
20626 line_offset_from_top,
20627 }) => {
20628 let point = MultiBufferPoint::new(row.0, 0);
20629 if let Some((buffer, buffer_point, _)) =
20630 self.buffer.read(cx).point_to_buffer_point(point, cx)
20631 {
20632 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20633 new_selections_by_buffer
20634 .entry(buffer)
20635 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20636 .0
20637 .push(buffer_offset..buffer_offset)
20638 }
20639 }
20640 None => {
20641 let selections = self.selections.all::<usize>(cx);
20642 let multi_buffer = self.buffer.read(cx);
20643 for selection in selections {
20644 for (snapshot, range, _, anchor) in multi_buffer
20645 .snapshot(cx)
20646 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20647 {
20648 if let Some(anchor) = anchor {
20649 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20650 else {
20651 continue;
20652 };
20653 let offset = text::ToOffset::to_offset(
20654 &anchor.text_anchor,
20655 &buffer_handle.read(cx).snapshot(),
20656 );
20657 let range = offset..offset;
20658 new_selections_by_buffer
20659 .entry(buffer_handle)
20660 .or_insert((Vec::new(), None))
20661 .0
20662 .push(range)
20663 } else {
20664 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20665 else {
20666 continue;
20667 };
20668 new_selections_by_buffer
20669 .entry(buffer_handle)
20670 .or_insert((Vec::new(), None))
20671 .0
20672 .push(range)
20673 }
20674 }
20675 }
20676 }
20677 }
20678
20679 new_selections_by_buffer
20680 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20681
20682 if new_selections_by_buffer.is_empty() {
20683 return;
20684 }
20685
20686 // We defer the pane interaction because we ourselves are a workspace item
20687 // and activating a new item causes the pane to call a method on us reentrantly,
20688 // which panics if we're on the stack.
20689 window.defer(cx, move |window, cx| {
20690 workspace.update(cx, |workspace, cx| {
20691 let pane = if split {
20692 workspace.adjacent_pane(window, cx)
20693 } else {
20694 workspace.active_pane().clone()
20695 };
20696
20697 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20698 let editor = buffer
20699 .read(cx)
20700 .file()
20701 .is_none()
20702 .then(|| {
20703 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20704 // so `workspace.open_project_item` will never find them, always opening a new editor.
20705 // Instead, we try to activate the existing editor in the pane first.
20706 let (editor, pane_item_index) =
20707 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20708 let editor = item.downcast::<Editor>()?;
20709 let singleton_buffer =
20710 editor.read(cx).buffer().read(cx).as_singleton()?;
20711 if singleton_buffer == buffer {
20712 Some((editor, i))
20713 } else {
20714 None
20715 }
20716 })?;
20717 pane.update(cx, |pane, cx| {
20718 pane.activate_item(pane_item_index, true, true, window, cx)
20719 });
20720 Some(editor)
20721 })
20722 .flatten()
20723 .unwrap_or_else(|| {
20724 workspace.open_project_item::<Self>(
20725 pane.clone(),
20726 buffer,
20727 true,
20728 true,
20729 window,
20730 cx,
20731 )
20732 });
20733
20734 editor.update(cx, |editor, cx| {
20735 let autoscroll = match scroll_offset {
20736 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20737 None => Autoscroll::newest(),
20738 };
20739 let nav_history = editor.nav_history.take();
20740 editor.change_selections(
20741 SelectionEffects::scroll(autoscroll),
20742 window,
20743 cx,
20744 |s| {
20745 s.select_ranges(ranges);
20746 },
20747 );
20748 editor.nav_history = nav_history;
20749 });
20750 }
20751 })
20752 });
20753 }
20754
20755 // For now, don't allow opening excerpts in buffers that aren't backed by
20756 // regular project files.
20757 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20758 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20759 }
20760
20761 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20762 let snapshot = self.buffer.read(cx).read(cx);
20763 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20764 Some(
20765 ranges
20766 .iter()
20767 .map(move |range| {
20768 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20769 })
20770 .collect(),
20771 )
20772 }
20773
20774 fn selection_replacement_ranges(
20775 &self,
20776 range: Range<OffsetUtf16>,
20777 cx: &mut App,
20778 ) -> Vec<Range<OffsetUtf16>> {
20779 let selections = self.selections.all::<OffsetUtf16>(cx);
20780 let newest_selection = selections
20781 .iter()
20782 .max_by_key(|selection| selection.id)
20783 .unwrap();
20784 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20785 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20786 let snapshot = self.buffer.read(cx).read(cx);
20787 selections
20788 .into_iter()
20789 .map(|mut selection| {
20790 selection.start.0 =
20791 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20792 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20793 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20794 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20795 })
20796 .collect()
20797 }
20798
20799 fn report_editor_event(
20800 &self,
20801 reported_event: ReportEditorEvent,
20802 file_extension: Option<String>,
20803 cx: &App,
20804 ) {
20805 if cfg!(any(test, feature = "test-support")) {
20806 return;
20807 }
20808
20809 let Some(project) = &self.project else { return };
20810
20811 // If None, we are in a file without an extension
20812 let file = self
20813 .buffer
20814 .read(cx)
20815 .as_singleton()
20816 .and_then(|b| b.read(cx).file());
20817 let file_extension = file_extension.or(file
20818 .as_ref()
20819 .and_then(|file| Path::new(file.file_name(cx)).extension())
20820 .and_then(|e| e.to_str())
20821 .map(|a| a.to_string()));
20822
20823 let vim_mode = vim_enabled(cx);
20824
20825 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20826 let copilot_enabled = edit_predictions_provider
20827 == language::language_settings::EditPredictionProvider::Copilot;
20828 let copilot_enabled_for_language = self
20829 .buffer
20830 .read(cx)
20831 .language_settings(cx)
20832 .show_edit_predictions;
20833
20834 let project = project.read(cx);
20835 let event_type = reported_event.event_type();
20836
20837 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20838 telemetry::event!(
20839 event_type,
20840 type = if auto_saved {"autosave"} else {"manual"},
20841 file_extension,
20842 vim_mode,
20843 copilot_enabled,
20844 copilot_enabled_for_language,
20845 edit_predictions_provider,
20846 is_via_ssh = project.is_via_remote_server(),
20847 );
20848 } else {
20849 telemetry::event!(
20850 event_type,
20851 file_extension,
20852 vim_mode,
20853 copilot_enabled,
20854 copilot_enabled_for_language,
20855 edit_predictions_provider,
20856 is_via_ssh = project.is_via_remote_server(),
20857 );
20858 };
20859 }
20860
20861 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20862 /// with each line being an array of {text, highlight} objects.
20863 fn copy_highlight_json(
20864 &mut self,
20865 _: &CopyHighlightJson,
20866 window: &mut Window,
20867 cx: &mut Context<Self>,
20868 ) {
20869 #[derive(Serialize)]
20870 struct Chunk<'a> {
20871 text: String,
20872 highlight: Option<&'a str>,
20873 }
20874
20875 let snapshot = self.buffer.read(cx).snapshot(cx);
20876 let range = self
20877 .selected_text_range(false, window, cx)
20878 .and_then(|selection| {
20879 if selection.range.is_empty() {
20880 None
20881 } else {
20882 Some(selection.range)
20883 }
20884 })
20885 .unwrap_or_else(|| 0..snapshot.len());
20886
20887 let chunks = snapshot.chunks(range, true);
20888 let mut lines = Vec::new();
20889 let mut line: VecDeque<Chunk> = VecDeque::new();
20890
20891 let Some(style) = self.style.as_ref() else {
20892 return;
20893 };
20894
20895 for chunk in chunks {
20896 let highlight = chunk
20897 .syntax_highlight_id
20898 .and_then(|id| id.name(&style.syntax));
20899 let mut chunk_lines = chunk.text.split('\n').peekable();
20900 while let Some(text) = chunk_lines.next() {
20901 let mut merged_with_last_token = false;
20902 if let Some(last_token) = line.back_mut()
20903 && last_token.highlight == highlight
20904 {
20905 last_token.text.push_str(text);
20906 merged_with_last_token = true;
20907 }
20908
20909 if !merged_with_last_token {
20910 line.push_back(Chunk {
20911 text: text.into(),
20912 highlight,
20913 });
20914 }
20915
20916 if chunk_lines.peek().is_some() {
20917 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20918 line.pop_front();
20919 }
20920 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20921 line.pop_back();
20922 }
20923
20924 lines.push(mem::take(&mut line));
20925 }
20926 }
20927 }
20928
20929 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20930 return;
20931 };
20932 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20933 }
20934
20935 pub fn open_context_menu(
20936 &mut self,
20937 _: &OpenContextMenu,
20938 window: &mut Window,
20939 cx: &mut Context<Self>,
20940 ) {
20941 self.request_autoscroll(Autoscroll::newest(), cx);
20942 let position = self.selections.newest_display(cx).start;
20943 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20944 }
20945
20946 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20947 &self.inlay_hint_cache
20948 }
20949
20950 pub fn replay_insert_event(
20951 &mut self,
20952 text: &str,
20953 relative_utf16_range: Option<Range<isize>>,
20954 window: &mut Window,
20955 cx: &mut Context<Self>,
20956 ) {
20957 if !self.input_enabled {
20958 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20959 return;
20960 }
20961 if let Some(relative_utf16_range) = relative_utf16_range {
20962 let selections = self.selections.all::<OffsetUtf16>(cx);
20963 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20964 let new_ranges = selections.into_iter().map(|range| {
20965 let start = OffsetUtf16(
20966 range
20967 .head()
20968 .0
20969 .saturating_add_signed(relative_utf16_range.start),
20970 );
20971 let end = OffsetUtf16(
20972 range
20973 .head()
20974 .0
20975 .saturating_add_signed(relative_utf16_range.end),
20976 );
20977 start..end
20978 });
20979 s.select_ranges(new_ranges);
20980 });
20981 }
20982
20983 self.handle_input(text, window, cx);
20984 }
20985
20986 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20987 let Some(provider) = self.semantics_provider.as_ref() else {
20988 return false;
20989 };
20990
20991 let mut supports = false;
20992 self.buffer().update(cx, |this, cx| {
20993 this.for_each_buffer(|buffer| {
20994 supports |= provider.supports_inlay_hints(buffer, cx);
20995 });
20996 });
20997
20998 supports
20999 }
21000
21001 pub fn is_focused(&self, window: &Window) -> bool {
21002 self.focus_handle.is_focused(window)
21003 }
21004
21005 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21006 cx.emit(EditorEvent::Focused);
21007
21008 if let Some(descendant) = self
21009 .last_focused_descendant
21010 .take()
21011 .and_then(|descendant| descendant.upgrade())
21012 {
21013 window.focus(&descendant);
21014 } else {
21015 if let Some(blame) = self.blame.as_ref() {
21016 blame.update(cx, GitBlame::focus)
21017 }
21018
21019 self.blink_manager.update(cx, BlinkManager::enable);
21020 self.show_cursor_names(window, cx);
21021 self.buffer.update(cx, |buffer, cx| {
21022 buffer.finalize_last_transaction(cx);
21023 if self.leader_id.is_none() {
21024 buffer.set_active_selections(
21025 &self.selections.disjoint_anchors(),
21026 self.selections.line_mode,
21027 self.cursor_shape,
21028 cx,
21029 );
21030 }
21031 });
21032 }
21033 }
21034
21035 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21036 cx.emit(EditorEvent::FocusedIn)
21037 }
21038
21039 fn handle_focus_out(
21040 &mut self,
21041 event: FocusOutEvent,
21042 _window: &mut Window,
21043 cx: &mut Context<Self>,
21044 ) {
21045 if event.blurred != self.focus_handle {
21046 self.last_focused_descendant = Some(event.blurred);
21047 }
21048 self.selection_drag_state = SelectionDragState::None;
21049 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21050 }
21051
21052 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21053 self.blink_manager.update(cx, BlinkManager::disable);
21054 self.buffer
21055 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21056
21057 if let Some(blame) = self.blame.as_ref() {
21058 blame.update(cx, GitBlame::blur)
21059 }
21060 if !self.hover_state.focused(window, cx) {
21061 hide_hover(self, cx);
21062 }
21063 if !self
21064 .context_menu
21065 .borrow()
21066 .as_ref()
21067 .is_some_and(|context_menu| context_menu.focused(window, cx))
21068 {
21069 self.hide_context_menu(window, cx);
21070 }
21071 self.discard_edit_prediction(false, cx);
21072 cx.emit(EditorEvent::Blurred);
21073 cx.notify();
21074 }
21075
21076 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21077 let mut pending: String = window
21078 .pending_input_keystrokes()
21079 .into_iter()
21080 .flatten()
21081 .filter_map(|keystroke| {
21082 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21083 keystroke.key_char.clone()
21084 } else {
21085 None
21086 }
21087 })
21088 .collect();
21089
21090 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21091 pending = "".to_string();
21092 }
21093
21094 let existing_pending = self
21095 .text_highlights::<PendingInput>(cx)
21096 .map(|(_, ranges)| ranges.to_vec());
21097 if existing_pending.is_none() && pending.is_empty() {
21098 return;
21099 }
21100 let transaction =
21101 self.transact(window, cx, |this, window, cx| {
21102 let selections = this.selections.all::<usize>(cx);
21103 let edits = selections
21104 .iter()
21105 .map(|selection| (selection.end..selection.end, pending.clone()));
21106 this.edit(edits, cx);
21107 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21108 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21109 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21110 }));
21111 });
21112 if let Some(existing_ranges) = existing_pending {
21113 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21114 this.edit(edits, cx);
21115 }
21116 });
21117
21118 let snapshot = self.snapshot(window, cx);
21119 let ranges = self
21120 .selections
21121 .all::<usize>(cx)
21122 .into_iter()
21123 .map(|selection| {
21124 snapshot.buffer_snapshot.anchor_after(selection.end)
21125 ..snapshot
21126 .buffer_snapshot
21127 .anchor_before(selection.end + pending.len())
21128 })
21129 .collect();
21130
21131 if pending.is_empty() {
21132 self.clear_highlights::<PendingInput>(cx);
21133 } else {
21134 self.highlight_text::<PendingInput>(
21135 ranges,
21136 HighlightStyle {
21137 underline: Some(UnderlineStyle {
21138 thickness: px(1.),
21139 color: None,
21140 wavy: false,
21141 }),
21142 ..Default::default()
21143 },
21144 cx,
21145 );
21146 }
21147
21148 self.ime_transaction = self.ime_transaction.or(transaction);
21149 if let Some(transaction) = self.ime_transaction {
21150 self.buffer.update(cx, |buffer, cx| {
21151 buffer.group_until_transaction(transaction, cx);
21152 });
21153 }
21154
21155 if self.text_highlights::<PendingInput>(cx).is_none() {
21156 self.ime_transaction.take();
21157 }
21158 }
21159
21160 pub fn register_action_renderer(
21161 &mut self,
21162 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21163 ) -> Subscription {
21164 let id = self.next_editor_action_id.post_inc();
21165 self.editor_actions
21166 .borrow_mut()
21167 .insert(id, Box::new(listener));
21168
21169 let editor_actions = self.editor_actions.clone();
21170 Subscription::new(move || {
21171 editor_actions.borrow_mut().remove(&id);
21172 })
21173 }
21174
21175 pub fn register_action<A: Action>(
21176 &mut self,
21177 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21178 ) -> Subscription {
21179 let id = self.next_editor_action_id.post_inc();
21180 let listener = Arc::new(listener);
21181 self.editor_actions.borrow_mut().insert(
21182 id,
21183 Box::new(move |_, window, _| {
21184 let listener = listener.clone();
21185 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21186 let action = action.downcast_ref().unwrap();
21187 if phase == DispatchPhase::Bubble {
21188 listener(action, window, cx)
21189 }
21190 })
21191 }),
21192 );
21193
21194 let editor_actions = self.editor_actions.clone();
21195 Subscription::new(move || {
21196 editor_actions.borrow_mut().remove(&id);
21197 })
21198 }
21199
21200 pub fn file_header_size(&self) -> u32 {
21201 FILE_HEADER_HEIGHT
21202 }
21203
21204 pub fn restore(
21205 &mut self,
21206 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21207 window: &mut Window,
21208 cx: &mut Context<Self>,
21209 ) {
21210 let workspace = self.workspace();
21211 let project = self.project();
21212 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21213 let mut tasks = Vec::new();
21214 for (buffer_id, changes) in revert_changes {
21215 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21216 buffer.update(cx, |buffer, cx| {
21217 buffer.edit(
21218 changes
21219 .into_iter()
21220 .map(|(range, text)| (range, text.to_string())),
21221 None,
21222 cx,
21223 );
21224 });
21225
21226 if let Some(project) =
21227 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21228 {
21229 project.update(cx, |project, cx| {
21230 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21231 })
21232 }
21233 }
21234 }
21235 tasks
21236 });
21237 cx.spawn_in(window, async move |_, cx| {
21238 for (buffer, task) in save_tasks {
21239 let result = task.await;
21240 if result.is_err() {
21241 let Some(path) = buffer
21242 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21243 .ok()
21244 else {
21245 continue;
21246 };
21247 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21248 let Some(task) = cx
21249 .update_window_entity(workspace, |workspace, window, cx| {
21250 workspace
21251 .open_path_preview(path, None, false, false, false, window, cx)
21252 })
21253 .ok()
21254 else {
21255 continue;
21256 };
21257 task.await.log_err();
21258 }
21259 }
21260 }
21261 })
21262 .detach();
21263 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21264 selections.refresh()
21265 });
21266 }
21267
21268 pub fn to_pixel_point(
21269 &self,
21270 source: multi_buffer::Anchor,
21271 editor_snapshot: &EditorSnapshot,
21272 window: &mut Window,
21273 ) -> Option<gpui::Point<Pixels>> {
21274 let source_point = source.to_display_point(editor_snapshot);
21275 self.display_to_pixel_point(source_point, editor_snapshot, window)
21276 }
21277
21278 pub fn display_to_pixel_point(
21279 &self,
21280 source: DisplayPoint,
21281 editor_snapshot: &EditorSnapshot,
21282 window: &mut Window,
21283 ) -> Option<gpui::Point<Pixels>> {
21284 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21285 let text_layout_details = self.text_layout_details(window);
21286 let scroll_top = text_layout_details
21287 .scroll_anchor
21288 .scroll_position(editor_snapshot)
21289 .y;
21290
21291 if source.row().as_f32() < scroll_top.floor() {
21292 return None;
21293 }
21294 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21295 let source_y = line_height * (source.row().as_f32() - scroll_top);
21296 Some(gpui::Point::new(source_x, source_y))
21297 }
21298
21299 pub fn has_visible_completions_menu(&self) -> bool {
21300 !self.edit_prediction_preview_is_active()
21301 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21302 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21303 })
21304 }
21305
21306 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21307 if self.mode.is_minimap() {
21308 return;
21309 }
21310 self.addons
21311 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21312 }
21313
21314 pub fn unregister_addon<T: Addon>(&mut self) {
21315 self.addons.remove(&std::any::TypeId::of::<T>());
21316 }
21317
21318 pub fn addon<T: Addon>(&self) -> Option<&T> {
21319 let type_id = std::any::TypeId::of::<T>();
21320 self.addons
21321 .get(&type_id)
21322 .and_then(|item| item.to_any().downcast_ref::<T>())
21323 }
21324
21325 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21326 let type_id = std::any::TypeId::of::<T>();
21327 self.addons
21328 .get_mut(&type_id)
21329 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21330 }
21331
21332 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21333 let text_layout_details = self.text_layout_details(window);
21334 let style = &text_layout_details.editor_style;
21335 let font_id = window.text_system().resolve_font(&style.text.font());
21336 let font_size = style.text.font_size.to_pixels(window.rem_size());
21337 let line_height = style.text.line_height_in_pixels(window.rem_size());
21338 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21339 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21340
21341 CharacterDimensions {
21342 em_width,
21343 em_advance,
21344 line_height,
21345 }
21346 }
21347
21348 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21349 self.load_diff_task.clone()
21350 }
21351
21352 fn read_metadata_from_db(
21353 &mut self,
21354 item_id: u64,
21355 workspace_id: WorkspaceId,
21356 window: &mut Window,
21357 cx: &mut Context<Editor>,
21358 ) {
21359 if self.is_singleton(cx)
21360 && !self.mode.is_minimap()
21361 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21362 {
21363 let buffer_snapshot = OnceCell::new();
21364
21365 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21366 && !folds.is_empty()
21367 {
21368 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21369 self.fold_ranges(
21370 folds
21371 .into_iter()
21372 .map(|(start, end)| {
21373 snapshot.clip_offset(start, Bias::Left)
21374 ..snapshot.clip_offset(end, Bias::Right)
21375 })
21376 .collect(),
21377 false,
21378 window,
21379 cx,
21380 );
21381 }
21382
21383 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21384 && !selections.is_empty()
21385 {
21386 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21387 // skip adding the initial selection to selection history
21388 self.selection_history.mode = SelectionHistoryMode::Skipping;
21389 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21390 s.select_ranges(selections.into_iter().map(|(start, end)| {
21391 snapshot.clip_offset(start, Bias::Left)
21392 ..snapshot.clip_offset(end, Bias::Right)
21393 }));
21394 });
21395 self.selection_history.mode = SelectionHistoryMode::Normal;
21396 };
21397 }
21398
21399 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21400 }
21401
21402 fn update_lsp_data(
21403 &mut self,
21404 ignore_cache: bool,
21405 for_buffer: Option<BufferId>,
21406 window: &mut Window,
21407 cx: &mut Context<'_, Self>,
21408 ) {
21409 self.pull_diagnostics(for_buffer, window, cx);
21410 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21411 }
21412}
21413
21414fn vim_enabled(cx: &App) -> bool {
21415 cx.global::<SettingsStore>()
21416 .raw_user_settings()
21417 .get("vim_mode")
21418 == Some(&serde_json::Value::Bool(true))
21419}
21420
21421fn process_completion_for_edit(
21422 completion: &Completion,
21423 intent: CompletionIntent,
21424 buffer: &Entity<Buffer>,
21425 cursor_position: &text::Anchor,
21426 cx: &mut Context<Editor>,
21427) -> CompletionEdit {
21428 let buffer = buffer.read(cx);
21429 let buffer_snapshot = buffer.snapshot();
21430 let (snippet, new_text) = if completion.is_snippet() {
21431 // Workaround for typescript language server issues so that methods don't expand within
21432 // strings and functions with type expressions. The previous point is used because the query
21433 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21434 let mut snippet_source = completion.new_text.clone();
21435 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21436 previous_point.column = previous_point.column.saturating_sub(1);
21437 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21438 && scope.prefers_label_for_snippet_in_completion()
21439 && let Some(label) = completion.label()
21440 && matches!(
21441 completion.kind(),
21442 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21443 )
21444 {
21445 snippet_source = label;
21446 }
21447 match Snippet::parse(&snippet_source).log_err() {
21448 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21449 None => (None, completion.new_text.clone()),
21450 }
21451 } else {
21452 (None, completion.new_text.clone())
21453 };
21454
21455 let mut range_to_replace = {
21456 let replace_range = &completion.replace_range;
21457 if let CompletionSource::Lsp {
21458 insert_range: Some(insert_range),
21459 ..
21460 } = &completion.source
21461 {
21462 debug_assert_eq!(
21463 insert_range.start, replace_range.start,
21464 "insert_range and replace_range should start at the same position"
21465 );
21466 debug_assert!(
21467 insert_range
21468 .start
21469 .cmp(cursor_position, &buffer_snapshot)
21470 .is_le(),
21471 "insert_range should start before or at cursor position"
21472 );
21473 debug_assert!(
21474 replace_range
21475 .start
21476 .cmp(cursor_position, &buffer_snapshot)
21477 .is_le(),
21478 "replace_range should start before or at cursor position"
21479 );
21480
21481 let should_replace = match intent {
21482 CompletionIntent::CompleteWithInsert => false,
21483 CompletionIntent::CompleteWithReplace => true,
21484 CompletionIntent::Complete | CompletionIntent::Compose => {
21485 let insert_mode =
21486 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21487 .completions
21488 .lsp_insert_mode;
21489 match insert_mode {
21490 LspInsertMode::Insert => false,
21491 LspInsertMode::Replace => true,
21492 LspInsertMode::ReplaceSubsequence => {
21493 let mut text_to_replace = buffer.chars_for_range(
21494 buffer.anchor_before(replace_range.start)
21495 ..buffer.anchor_after(replace_range.end),
21496 );
21497 let mut current_needle = text_to_replace.next();
21498 for haystack_ch in completion.label.text.chars() {
21499 if let Some(needle_ch) = current_needle
21500 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21501 {
21502 current_needle = text_to_replace.next();
21503 }
21504 }
21505 current_needle.is_none()
21506 }
21507 LspInsertMode::ReplaceSuffix => {
21508 if replace_range
21509 .end
21510 .cmp(cursor_position, &buffer_snapshot)
21511 .is_gt()
21512 {
21513 let range_after_cursor = *cursor_position..replace_range.end;
21514 let text_after_cursor = buffer
21515 .text_for_range(
21516 buffer.anchor_before(range_after_cursor.start)
21517 ..buffer.anchor_after(range_after_cursor.end),
21518 )
21519 .collect::<String>()
21520 .to_ascii_lowercase();
21521 completion
21522 .label
21523 .text
21524 .to_ascii_lowercase()
21525 .ends_with(&text_after_cursor)
21526 } else {
21527 true
21528 }
21529 }
21530 }
21531 }
21532 };
21533
21534 if should_replace {
21535 replace_range.clone()
21536 } else {
21537 insert_range.clone()
21538 }
21539 } else {
21540 replace_range.clone()
21541 }
21542 };
21543
21544 if range_to_replace
21545 .end
21546 .cmp(cursor_position, &buffer_snapshot)
21547 .is_lt()
21548 {
21549 range_to_replace.end = *cursor_position;
21550 }
21551
21552 CompletionEdit {
21553 new_text,
21554 replace_range: range_to_replace.to_offset(buffer),
21555 snippet,
21556 }
21557}
21558
21559struct CompletionEdit {
21560 new_text: String,
21561 replace_range: Range<usize>,
21562 snippet: Option<Snippet>,
21563}
21564
21565fn insert_extra_newline_brackets(
21566 buffer: &MultiBufferSnapshot,
21567 range: Range<usize>,
21568 language: &language::LanguageScope,
21569) -> bool {
21570 let leading_whitespace_len = buffer
21571 .reversed_chars_at(range.start)
21572 .take_while(|c| c.is_whitespace() && *c != '\n')
21573 .map(|c| c.len_utf8())
21574 .sum::<usize>();
21575 let trailing_whitespace_len = buffer
21576 .chars_at(range.end)
21577 .take_while(|c| c.is_whitespace() && *c != '\n')
21578 .map(|c| c.len_utf8())
21579 .sum::<usize>();
21580 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21581
21582 language.brackets().any(|(pair, enabled)| {
21583 let pair_start = pair.start.trim_end();
21584 let pair_end = pair.end.trim_start();
21585
21586 enabled
21587 && pair.newline
21588 && buffer.contains_str_at(range.end, pair_end)
21589 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21590 })
21591}
21592
21593fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21594 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21595 [(buffer, range, _)] => (*buffer, range.clone()),
21596 _ => return false,
21597 };
21598 let pair = {
21599 let mut result: Option<BracketMatch> = None;
21600
21601 for pair in buffer
21602 .all_bracket_ranges(range.clone())
21603 .filter(move |pair| {
21604 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21605 })
21606 {
21607 let len = pair.close_range.end - pair.open_range.start;
21608
21609 if let Some(existing) = &result {
21610 let existing_len = existing.close_range.end - existing.open_range.start;
21611 if len > existing_len {
21612 continue;
21613 }
21614 }
21615
21616 result = Some(pair);
21617 }
21618
21619 result
21620 };
21621 let Some(pair) = pair else {
21622 return false;
21623 };
21624 pair.newline_only
21625 && buffer
21626 .chars_for_range(pair.open_range.end..range.start)
21627 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21628 .all(|c| c.is_whitespace() && c != '\n')
21629}
21630
21631fn update_uncommitted_diff_for_buffer(
21632 editor: Entity<Editor>,
21633 project: &Entity<Project>,
21634 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21635 buffer: Entity<MultiBuffer>,
21636 cx: &mut App,
21637) -> Task<()> {
21638 let mut tasks = Vec::new();
21639 project.update(cx, |project, cx| {
21640 for buffer in buffers {
21641 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21642 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21643 }
21644 }
21645 });
21646 cx.spawn(async move |cx| {
21647 let diffs = future::join_all(tasks).await;
21648 if editor
21649 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21650 .unwrap_or(false)
21651 {
21652 return;
21653 }
21654
21655 buffer
21656 .update(cx, |buffer, cx| {
21657 for diff in diffs.into_iter().flatten() {
21658 buffer.add_diff(diff, cx);
21659 }
21660 })
21661 .ok();
21662 })
21663}
21664
21665fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21666 let tab_size = tab_size.get() as usize;
21667 let mut width = offset;
21668
21669 for ch in text.chars() {
21670 width += if ch == '\t' {
21671 tab_size - (width % tab_size)
21672 } else {
21673 1
21674 };
21675 }
21676
21677 width - offset
21678}
21679
21680#[cfg(test)]
21681mod tests {
21682 use super::*;
21683
21684 #[test]
21685 fn test_string_size_with_expanded_tabs() {
21686 let nz = |val| NonZeroU32::new(val).unwrap();
21687 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21688 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21689 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21690 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21691 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21692 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21693 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21694 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21695 }
21696}
21697
21698/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21699struct WordBreakingTokenizer<'a> {
21700 input: &'a str,
21701}
21702
21703impl<'a> WordBreakingTokenizer<'a> {
21704 fn new(input: &'a str) -> Self {
21705 Self { input }
21706 }
21707}
21708
21709fn is_char_ideographic(ch: char) -> bool {
21710 use unicode_script::Script::*;
21711 use unicode_script::UnicodeScript;
21712 matches!(ch.script(), Han | Tangut | Yi)
21713}
21714
21715fn is_grapheme_ideographic(text: &str) -> bool {
21716 text.chars().any(is_char_ideographic)
21717}
21718
21719fn is_grapheme_whitespace(text: &str) -> bool {
21720 text.chars().any(|x| x.is_whitespace())
21721}
21722
21723fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21724 text.chars()
21725 .next()
21726 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21727}
21728
21729#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21730enum WordBreakToken<'a> {
21731 Word { token: &'a str, grapheme_len: usize },
21732 InlineWhitespace { token: &'a str, grapheme_len: usize },
21733 Newline,
21734}
21735
21736impl<'a> Iterator for WordBreakingTokenizer<'a> {
21737 /// Yields a span, the count of graphemes in the token, and whether it was
21738 /// whitespace. Note that it also breaks at word boundaries.
21739 type Item = WordBreakToken<'a>;
21740
21741 fn next(&mut self) -> Option<Self::Item> {
21742 use unicode_segmentation::UnicodeSegmentation;
21743 if self.input.is_empty() {
21744 return None;
21745 }
21746
21747 let mut iter = self.input.graphemes(true).peekable();
21748 let mut offset = 0;
21749 let mut grapheme_len = 0;
21750 if let Some(first_grapheme) = iter.next() {
21751 let is_newline = first_grapheme == "\n";
21752 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21753 offset += first_grapheme.len();
21754 grapheme_len += 1;
21755 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21756 if let Some(grapheme) = iter.peek().copied()
21757 && should_stay_with_preceding_ideograph(grapheme)
21758 {
21759 offset += grapheme.len();
21760 grapheme_len += 1;
21761 }
21762 } else {
21763 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21764 let mut next_word_bound = words.peek().copied();
21765 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21766 next_word_bound = words.next();
21767 }
21768 while let Some(grapheme) = iter.peek().copied() {
21769 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21770 break;
21771 };
21772 if is_grapheme_whitespace(grapheme) != is_whitespace
21773 || (grapheme == "\n") != is_newline
21774 {
21775 break;
21776 };
21777 offset += grapheme.len();
21778 grapheme_len += 1;
21779 iter.next();
21780 }
21781 }
21782 let token = &self.input[..offset];
21783 self.input = &self.input[offset..];
21784 if token == "\n" {
21785 Some(WordBreakToken::Newline)
21786 } else if is_whitespace {
21787 Some(WordBreakToken::InlineWhitespace {
21788 token,
21789 grapheme_len,
21790 })
21791 } else {
21792 Some(WordBreakToken::Word {
21793 token,
21794 grapheme_len,
21795 })
21796 }
21797 } else {
21798 None
21799 }
21800 }
21801}
21802
21803#[test]
21804fn test_word_breaking_tokenizer() {
21805 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21806 ("", &[]),
21807 (" ", &[whitespace(" ", 2)]),
21808 ("Ʒ", &[word("Ʒ", 1)]),
21809 ("Ǽ", &[word("Ǽ", 1)]),
21810 ("⋑", &[word("⋑", 1)]),
21811 ("⋑⋑", &[word("⋑⋑", 2)]),
21812 (
21813 "原理,进而",
21814 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21815 ),
21816 (
21817 "hello world",
21818 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21819 ),
21820 (
21821 "hello, world",
21822 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21823 ),
21824 (
21825 " hello world",
21826 &[
21827 whitespace(" ", 2),
21828 word("hello", 5),
21829 whitespace(" ", 1),
21830 word("world", 5),
21831 ],
21832 ),
21833 (
21834 "这是什么 \n 钢笔",
21835 &[
21836 word("这", 1),
21837 word("是", 1),
21838 word("什", 1),
21839 word("么", 1),
21840 whitespace(" ", 1),
21841 newline(),
21842 whitespace(" ", 1),
21843 word("钢", 1),
21844 word("笔", 1),
21845 ],
21846 ),
21847 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21848 ];
21849
21850 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21851 WordBreakToken::Word {
21852 token,
21853 grapheme_len,
21854 }
21855 }
21856
21857 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21858 WordBreakToken::InlineWhitespace {
21859 token,
21860 grapheme_len,
21861 }
21862 }
21863
21864 fn newline() -> WordBreakToken<'static> {
21865 WordBreakToken::Newline
21866 }
21867
21868 for (input, result) in tests {
21869 assert_eq!(
21870 WordBreakingTokenizer::new(input)
21871 .collect::<Vec<_>>()
21872 .as_slice(),
21873 *result,
21874 );
21875 }
21876}
21877
21878fn wrap_with_prefix(
21879 first_line_prefix: String,
21880 subsequent_lines_prefix: String,
21881 unwrapped_text: String,
21882 wrap_column: usize,
21883 tab_size: NonZeroU32,
21884 preserve_existing_whitespace: bool,
21885) -> String {
21886 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21887 let subsequent_lines_prefix_len =
21888 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21889 let mut wrapped_text = String::new();
21890 let mut current_line = first_line_prefix;
21891 let mut is_first_line = true;
21892
21893 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21894 let mut current_line_len = first_line_prefix_len;
21895 let mut in_whitespace = false;
21896 for token in tokenizer {
21897 let have_preceding_whitespace = in_whitespace;
21898 match token {
21899 WordBreakToken::Word {
21900 token,
21901 grapheme_len,
21902 } => {
21903 in_whitespace = false;
21904 let current_prefix_len = if is_first_line {
21905 first_line_prefix_len
21906 } else {
21907 subsequent_lines_prefix_len
21908 };
21909 if current_line_len + grapheme_len > wrap_column
21910 && current_line_len != current_prefix_len
21911 {
21912 wrapped_text.push_str(current_line.trim_end());
21913 wrapped_text.push('\n');
21914 is_first_line = false;
21915 current_line = subsequent_lines_prefix.clone();
21916 current_line_len = subsequent_lines_prefix_len;
21917 }
21918 current_line.push_str(token);
21919 current_line_len += grapheme_len;
21920 }
21921 WordBreakToken::InlineWhitespace {
21922 mut token,
21923 mut grapheme_len,
21924 } => {
21925 in_whitespace = true;
21926 if have_preceding_whitespace && !preserve_existing_whitespace {
21927 continue;
21928 }
21929 if !preserve_existing_whitespace {
21930 token = " ";
21931 grapheme_len = 1;
21932 }
21933 let current_prefix_len = if is_first_line {
21934 first_line_prefix_len
21935 } else {
21936 subsequent_lines_prefix_len
21937 };
21938 if current_line_len + grapheme_len > wrap_column {
21939 wrapped_text.push_str(current_line.trim_end());
21940 wrapped_text.push('\n');
21941 is_first_line = false;
21942 current_line = subsequent_lines_prefix.clone();
21943 current_line_len = subsequent_lines_prefix_len;
21944 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
21945 current_line.push_str(token);
21946 current_line_len += grapheme_len;
21947 }
21948 }
21949 WordBreakToken::Newline => {
21950 in_whitespace = true;
21951 let current_prefix_len = if is_first_line {
21952 first_line_prefix_len
21953 } else {
21954 subsequent_lines_prefix_len
21955 };
21956 if preserve_existing_whitespace {
21957 wrapped_text.push_str(current_line.trim_end());
21958 wrapped_text.push('\n');
21959 is_first_line = false;
21960 current_line = subsequent_lines_prefix.clone();
21961 current_line_len = subsequent_lines_prefix_len;
21962 } else if have_preceding_whitespace {
21963 continue;
21964 } else if current_line_len + 1 > wrap_column
21965 && current_line_len != current_prefix_len
21966 {
21967 wrapped_text.push_str(current_line.trim_end());
21968 wrapped_text.push('\n');
21969 is_first_line = false;
21970 current_line = subsequent_lines_prefix.clone();
21971 current_line_len = subsequent_lines_prefix_len;
21972 } else if current_line_len != current_prefix_len {
21973 current_line.push(' ');
21974 current_line_len += 1;
21975 }
21976 }
21977 }
21978 }
21979
21980 if !current_line.is_empty() {
21981 wrapped_text.push_str(¤t_line);
21982 }
21983 wrapped_text
21984}
21985
21986#[test]
21987fn test_wrap_with_prefix() {
21988 assert_eq!(
21989 wrap_with_prefix(
21990 "# ".to_string(),
21991 "# ".to_string(),
21992 "abcdefg".to_string(),
21993 4,
21994 NonZeroU32::new(4).unwrap(),
21995 false,
21996 ),
21997 "# abcdefg"
21998 );
21999 assert_eq!(
22000 wrap_with_prefix(
22001 "".to_string(),
22002 "".to_string(),
22003 "\thello world".to_string(),
22004 8,
22005 NonZeroU32::new(4).unwrap(),
22006 false,
22007 ),
22008 "hello\nworld"
22009 );
22010 assert_eq!(
22011 wrap_with_prefix(
22012 "// ".to_string(),
22013 "// ".to_string(),
22014 "xx \nyy zz aa bb cc".to_string(),
22015 12,
22016 NonZeroU32::new(4).unwrap(),
22017 false,
22018 ),
22019 "// xx yy zz\n// aa bb cc"
22020 );
22021 assert_eq!(
22022 wrap_with_prefix(
22023 String::new(),
22024 String::new(),
22025 "这是什么 \n 钢笔".to_string(),
22026 3,
22027 NonZeroU32::new(4).unwrap(),
22028 false,
22029 ),
22030 "这是什\n么 钢\n笔"
22031 );
22032}
22033
22034pub trait CollaborationHub {
22035 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22036 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22037 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22038}
22039
22040impl CollaborationHub for Entity<Project> {
22041 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22042 self.read(cx).collaborators()
22043 }
22044
22045 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22046 self.read(cx).user_store().read(cx).participant_indices()
22047 }
22048
22049 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22050 let this = self.read(cx);
22051 let user_ids = this.collaborators().values().map(|c| c.user_id);
22052 this.user_store().read(cx).participant_names(user_ids, cx)
22053 }
22054}
22055
22056pub trait SemanticsProvider {
22057 fn hover(
22058 &self,
22059 buffer: &Entity<Buffer>,
22060 position: text::Anchor,
22061 cx: &mut App,
22062 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22063
22064 fn inline_values(
22065 &self,
22066 buffer_handle: Entity<Buffer>,
22067 range: Range<text::Anchor>,
22068 cx: &mut App,
22069 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22070
22071 fn inlay_hints(
22072 &self,
22073 buffer_handle: Entity<Buffer>,
22074 range: Range<text::Anchor>,
22075 cx: &mut App,
22076 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22077
22078 fn resolve_inlay_hint(
22079 &self,
22080 hint: InlayHint,
22081 buffer_handle: Entity<Buffer>,
22082 server_id: LanguageServerId,
22083 cx: &mut App,
22084 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22085
22086 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22087
22088 fn document_highlights(
22089 &self,
22090 buffer: &Entity<Buffer>,
22091 position: text::Anchor,
22092 cx: &mut App,
22093 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22094
22095 fn definitions(
22096 &self,
22097 buffer: &Entity<Buffer>,
22098 position: text::Anchor,
22099 kind: GotoDefinitionKind,
22100 cx: &mut App,
22101 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22102
22103 fn range_for_rename(
22104 &self,
22105 buffer: &Entity<Buffer>,
22106 position: text::Anchor,
22107 cx: &mut App,
22108 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22109
22110 fn perform_rename(
22111 &self,
22112 buffer: &Entity<Buffer>,
22113 position: text::Anchor,
22114 new_name: String,
22115 cx: &mut App,
22116 ) -> Option<Task<Result<ProjectTransaction>>>;
22117}
22118
22119pub trait CompletionProvider {
22120 fn completions(
22121 &self,
22122 excerpt_id: ExcerptId,
22123 buffer: &Entity<Buffer>,
22124 buffer_position: text::Anchor,
22125 trigger: CompletionContext,
22126 window: &mut Window,
22127 cx: &mut Context<Editor>,
22128 ) -> Task<Result<Vec<CompletionResponse>>>;
22129
22130 fn resolve_completions(
22131 &self,
22132 _buffer: Entity<Buffer>,
22133 _completion_indices: Vec<usize>,
22134 _completions: Rc<RefCell<Box<[Completion]>>>,
22135 _cx: &mut Context<Editor>,
22136 ) -> Task<Result<bool>> {
22137 Task::ready(Ok(false))
22138 }
22139
22140 fn apply_additional_edits_for_completion(
22141 &self,
22142 _buffer: Entity<Buffer>,
22143 _completions: Rc<RefCell<Box<[Completion]>>>,
22144 _completion_index: usize,
22145 _push_to_history: bool,
22146 _cx: &mut Context<Editor>,
22147 ) -> Task<Result<Option<language::Transaction>>> {
22148 Task::ready(Ok(None))
22149 }
22150
22151 fn is_completion_trigger(
22152 &self,
22153 buffer: &Entity<Buffer>,
22154 position: language::Anchor,
22155 text: &str,
22156 trigger_in_words: bool,
22157 menu_is_open: bool,
22158 cx: &mut Context<Editor>,
22159 ) -> bool;
22160
22161 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22162
22163 fn sort_completions(&self) -> bool {
22164 true
22165 }
22166
22167 fn filter_completions(&self) -> bool {
22168 true
22169 }
22170}
22171
22172pub trait CodeActionProvider {
22173 fn id(&self) -> Arc<str>;
22174
22175 fn code_actions(
22176 &self,
22177 buffer: &Entity<Buffer>,
22178 range: Range<text::Anchor>,
22179 window: &mut Window,
22180 cx: &mut App,
22181 ) -> Task<Result<Vec<CodeAction>>>;
22182
22183 fn apply_code_action(
22184 &self,
22185 buffer_handle: Entity<Buffer>,
22186 action: CodeAction,
22187 excerpt_id: ExcerptId,
22188 push_to_history: bool,
22189 window: &mut Window,
22190 cx: &mut App,
22191 ) -> Task<Result<ProjectTransaction>>;
22192}
22193
22194impl CodeActionProvider for Entity<Project> {
22195 fn id(&self) -> Arc<str> {
22196 "project".into()
22197 }
22198
22199 fn code_actions(
22200 &self,
22201 buffer: &Entity<Buffer>,
22202 range: Range<text::Anchor>,
22203 _window: &mut Window,
22204 cx: &mut App,
22205 ) -> Task<Result<Vec<CodeAction>>> {
22206 self.update(cx, |project, cx| {
22207 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22208 let code_actions = project.code_actions(buffer, range, None, cx);
22209 cx.background_spawn(async move {
22210 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22211 Ok(code_lens_actions
22212 .context("code lens fetch")?
22213 .into_iter()
22214 .flatten()
22215 .chain(
22216 code_actions
22217 .context("code action fetch")?
22218 .into_iter()
22219 .flatten(),
22220 )
22221 .collect())
22222 })
22223 })
22224 }
22225
22226 fn apply_code_action(
22227 &self,
22228 buffer_handle: Entity<Buffer>,
22229 action: CodeAction,
22230 _excerpt_id: ExcerptId,
22231 push_to_history: bool,
22232 _window: &mut Window,
22233 cx: &mut App,
22234 ) -> Task<Result<ProjectTransaction>> {
22235 self.update(cx, |project, cx| {
22236 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22237 })
22238 }
22239}
22240
22241fn snippet_completions(
22242 project: &Project,
22243 buffer: &Entity<Buffer>,
22244 buffer_position: text::Anchor,
22245 cx: &mut App,
22246) -> Task<Result<CompletionResponse>> {
22247 let languages = buffer.read(cx).languages_at(buffer_position);
22248 let snippet_store = project.snippets().read(cx);
22249
22250 let scopes: Vec<_> = languages
22251 .iter()
22252 .filter_map(|language| {
22253 let language_name = language.lsp_id();
22254 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22255
22256 if snippets.is_empty() {
22257 None
22258 } else {
22259 Some((language.default_scope(), snippets))
22260 }
22261 })
22262 .collect();
22263
22264 if scopes.is_empty() {
22265 return Task::ready(Ok(CompletionResponse {
22266 completions: vec![],
22267 display_options: CompletionDisplayOptions::default(),
22268 is_incomplete: false,
22269 }));
22270 }
22271
22272 let snapshot = buffer.read(cx).text_snapshot();
22273 let chars: String = snapshot
22274 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22275 .collect();
22276 let executor = cx.background_executor().clone();
22277
22278 cx.background_spawn(async move {
22279 let mut is_incomplete = false;
22280 let mut completions: Vec<Completion> = Vec::new();
22281 for (scope, snippets) in scopes.into_iter() {
22282 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22283 let mut last_word = chars
22284 .chars()
22285 .take_while(|c| classifier.is_word(*c))
22286 .collect::<String>();
22287 last_word = last_word.chars().rev().collect();
22288
22289 if last_word.is_empty() {
22290 return Ok(CompletionResponse {
22291 completions: vec![],
22292 display_options: CompletionDisplayOptions::default(),
22293 is_incomplete: true,
22294 });
22295 }
22296
22297 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22298 let to_lsp = |point: &text::Anchor| {
22299 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22300 point_to_lsp(end)
22301 };
22302 let lsp_end = to_lsp(&buffer_position);
22303
22304 let candidates = snippets
22305 .iter()
22306 .enumerate()
22307 .flat_map(|(ix, snippet)| {
22308 snippet
22309 .prefix
22310 .iter()
22311 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22312 })
22313 .collect::<Vec<StringMatchCandidate>>();
22314
22315 const MAX_RESULTS: usize = 100;
22316 let mut matches = fuzzy::match_strings(
22317 &candidates,
22318 &last_word,
22319 last_word.chars().any(|c| c.is_uppercase()),
22320 true,
22321 MAX_RESULTS,
22322 &Default::default(),
22323 executor.clone(),
22324 )
22325 .await;
22326
22327 if matches.len() >= MAX_RESULTS {
22328 is_incomplete = true;
22329 }
22330
22331 // Remove all candidates where the query's start does not match the start of any word in the candidate
22332 if let Some(query_start) = last_word.chars().next() {
22333 matches.retain(|string_match| {
22334 split_words(&string_match.string).any(|word| {
22335 // Check that the first codepoint of the word as lowercase matches the first
22336 // codepoint of the query as lowercase
22337 word.chars()
22338 .flat_map(|codepoint| codepoint.to_lowercase())
22339 .zip(query_start.to_lowercase())
22340 .all(|(word_cp, query_cp)| word_cp == query_cp)
22341 })
22342 });
22343 }
22344
22345 let matched_strings = matches
22346 .into_iter()
22347 .map(|m| m.string)
22348 .collect::<HashSet<_>>();
22349
22350 completions.extend(snippets.iter().filter_map(|snippet| {
22351 let matching_prefix = snippet
22352 .prefix
22353 .iter()
22354 .find(|prefix| matched_strings.contains(*prefix))?;
22355 let start = as_offset - last_word.len();
22356 let start = snapshot.anchor_before(start);
22357 let range = start..buffer_position;
22358 let lsp_start = to_lsp(&start);
22359 let lsp_range = lsp::Range {
22360 start: lsp_start,
22361 end: lsp_end,
22362 };
22363 Some(Completion {
22364 replace_range: range,
22365 new_text: snippet.body.clone(),
22366 source: CompletionSource::Lsp {
22367 insert_range: None,
22368 server_id: LanguageServerId(usize::MAX),
22369 resolved: true,
22370 lsp_completion: Box::new(lsp::CompletionItem {
22371 label: snippet.prefix.first().unwrap().clone(),
22372 kind: Some(CompletionItemKind::SNIPPET),
22373 label_details: snippet.description.as_ref().map(|description| {
22374 lsp::CompletionItemLabelDetails {
22375 detail: Some(description.clone()),
22376 description: None,
22377 }
22378 }),
22379 insert_text_format: Some(InsertTextFormat::SNIPPET),
22380 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22381 lsp::InsertReplaceEdit {
22382 new_text: snippet.body.clone(),
22383 insert: lsp_range,
22384 replace: lsp_range,
22385 },
22386 )),
22387 filter_text: Some(snippet.body.clone()),
22388 sort_text: Some(char::MAX.to_string()),
22389 ..lsp::CompletionItem::default()
22390 }),
22391 lsp_defaults: None,
22392 },
22393 label: CodeLabel {
22394 text: matching_prefix.clone(),
22395 runs: Vec::new(),
22396 filter_range: 0..matching_prefix.len(),
22397 },
22398 icon_path: None,
22399 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22400 single_line: snippet.name.clone().into(),
22401 plain_text: snippet
22402 .description
22403 .clone()
22404 .map(|description| description.into()),
22405 }),
22406 insert_text_mode: None,
22407 confirm: None,
22408 })
22409 }))
22410 }
22411
22412 Ok(CompletionResponse {
22413 completions,
22414 display_options: CompletionDisplayOptions::default(),
22415 is_incomplete,
22416 })
22417 })
22418}
22419
22420impl CompletionProvider for Entity<Project> {
22421 fn completions(
22422 &self,
22423 _excerpt_id: ExcerptId,
22424 buffer: &Entity<Buffer>,
22425 buffer_position: text::Anchor,
22426 options: CompletionContext,
22427 _window: &mut Window,
22428 cx: &mut Context<Editor>,
22429 ) -> Task<Result<Vec<CompletionResponse>>> {
22430 self.update(cx, |project, cx| {
22431 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22432 let project_completions = project.completions(buffer, buffer_position, options, cx);
22433 cx.background_spawn(async move {
22434 let mut responses = project_completions.await?;
22435 let snippets = snippets.await?;
22436 if !snippets.completions.is_empty() {
22437 responses.push(snippets);
22438 }
22439 Ok(responses)
22440 })
22441 })
22442 }
22443
22444 fn resolve_completions(
22445 &self,
22446 buffer: Entity<Buffer>,
22447 completion_indices: Vec<usize>,
22448 completions: Rc<RefCell<Box<[Completion]>>>,
22449 cx: &mut Context<Editor>,
22450 ) -> Task<Result<bool>> {
22451 self.update(cx, |project, cx| {
22452 project.lsp_store().update(cx, |lsp_store, cx| {
22453 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22454 })
22455 })
22456 }
22457
22458 fn apply_additional_edits_for_completion(
22459 &self,
22460 buffer: Entity<Buffer>,
22461 completions: Rc<RefCell<Box<[Completion]>>>,
22462 completion_index: usize,
22463 push_to_history: bool,
22464 cx: &mut Context<Editor>,
22465 ) -> Task<Result<Option<language::Transaction>>> {
22466 self.update(cx, |project, cx| {
22467 project.lsp_store().update(cx, |lsp_store, cx| {
22468 lsp_store.apply_additional_edits_for_completion(
22469 buffer,
22470 completions,
22471 completion_index,
22472 push_to_history,
22473 cx,
22474 )
22475 })
22476 })
22477 }
22478
22479 fn is_completion_trigger(
22480 &self,
22481 buffer: &Entity<Buffer>,
22482 position: language::Anchor,
22483 text: &str,
22484 trigger_in_words: bool,
22485 menu_is_open: bool,
22486 cx: &mut Context<Editor>,
22487 ) -> bool {
22488 let mut chars = text.chars();
22489 let char = if let Some(char) = chars.next() {
22490 char
22491 } else {
22492 return false;
22493 };
22494 if chars.next().is_some() {
22495 return false;
22496 }
22497
22498 let buffer = buffer.read(cx);
22499 let snapshot = buffer.snapshot();
22500 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22501 return false;
22502 }
22503 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22504 if trigger_in_words && classifier.is_word(char) {
22505 return true;
22506 }
22507
22508 buffer.completion_triggers().contains(text)
22509 }
22510}
22511
22512impl SemanticsProvider for Entity<Project> {
22513 fn hover(
22514 &self,
22515 buffer: &Entity<Buffer>,
22516 position: text::Anchor,
22517 cx: &mut App,
22518 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22519 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22520 }
22521
22522 fn document_highlights(
22523 &self,
22524 buffer: &Entity<Buffer>,
22525 position: text::Anchor,
22526 cx: &mut App,
22527 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22528 Some(self.update(cx, |project, cx| {
22529 project.document_highlights(buffer, position, cx)
22530 }))
22531 }
22532
22533 fn definitions(
22534 &self,
22535 buffer: &Entity<Buffer>,
22536 position: text::Anchor,
22537 kind: GotoDefinitionKind,
22538 cx: &mut App,
22539 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22540 Some(self.update(cx, |project, cx| match kind {
22541 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22542 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22543 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22544 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22545 }))
22546 }
22547
22548 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22549 self.update(cx, |project, cx| {
22550 if project
22551 .active_debug_session(cx)
22552 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22553 {
22554 return true;
22555 }
22556
22557 buffer.update(cx, |buffer, cx| {
22558 project.any_language_server_supports_inlay_hints(buffer, cx)
22559 })
22560 })
22561 }
22562
22563 fn inline_values(
22564 &self,
22565 buffer_handle: Entity<Buffer>,
22566 range: Range<text::Anchor>,
22567 cx: &mut App,
22568 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22569 self.update(cx, |project, cx| {
22570 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22571
22572 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22573 })
22574 }
22575
22576 fn inlay_hints(
22577 &self,
22578 buffer_handle: Entity<Buffer>,
22579 range: Range<text::Anchor>,
22580 cx: &mut App,
22581 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22582 Some(self.update(cx, |project, cx| {
22583 project.inlay_hints(buffer_handle, range, cx)
22584 }))
22585 }
22586
22587 fn resolve_inlay_hint(
22588 &self,
22589 hint: InlayHint,
22590 buffer_handle: Entity<Buffer>,
22591 server_id: LanguageServerId,
22592 cx: &mut App,
22593 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22594 Some(self.update(cx, |project, cx| {
22595 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22596 }))
22597 }
22598
22599 fn range_for_rename(
22600 &self,
22601 buffer: &Entity<Buffer>,
22602 position: text::Anchor,
22603 cx: &mut App,
22604 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22605 Some(self.update(cx, |project, cx| {
22606 let buffer = buffer.clone();
22607 let task = project.prepare_rename(buffer.clone(), position, cx);
22608 cx.spawn(async move |_, cx| {
22609 Ok(match task.await? {
22610 PrepareRenameResponse::Success(range) => Some(range),
22611 PrepareRenameResponse::InvalidPosition => None,
22612 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22613 // Fallback on using TreeSitter info to determine identifier range
22614 buffer.read_with(cx, |buffer, _| {
22615 let snapshot = buffer.snapshot();
22616 let (range, kind) = snapshot.surrounding_word(position, false);
22617 if kind != Some(CharKind::Word) {
22618 return None;
22619 }
22620 Some(
22621 snapshot.anchor_before(range.start)
22622 ..snapshot.anchor_after(range.end),
22623 )
22624 })?
22625 }
22626 })
22627 })
22628 }))
22629 }
22630
22631 fn perform_rename(
22632 &self,
22633 buffer: &Entity<Buffer>,
22634 position: text::Anchor,
22635 new_name: String,
22636 cx: &mut App,
22637 ) -> Option<Task<Result<ProjectTransaction>>> {
22638 Some(self.update(cx, |project, cx| {
22639 project.perform_rename(buffer.clone(), position, new_name, cx)
22640 }))
22641 }
22642}
22643
22644fn inlay_hint_settings(
22645 location: Anchor,
22646 snapshot: &MultiBufferSnapshot,
22647 cx: &mut Context<Editor>,
22648) -> InlayHintSettings {
22649 let file = snapshot.file_at(location);
22650 let language = snapshot.language_at(location).map(|l| l.name());
22651 language_settings(language, file, cx).inlay_hints
22652}
22653
22654fn consume_contiguous_rows(
22655 contiguous_row_selections: &mut Vec<Selection<Point>>,
22656 selection: &Selection<Point>,
22657 display_map: &DisplaySnapshot,
22658 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22659) -> (MultiBufferRow, MultiBufferRow) {
22660 contiguous_row_selections.push(selection.clone());
22661 let start_row = starting_row(selection, display_map);
22662 let mut end_row = ending_row(selection, display_map);
22663
22664 while let Some(next_selection) = selections.peek() {
22665 if next_selection.start.row <= end_row.0 {
22666 end_row = ending_row(next_selection, display_map);
22667 contiguous_row_selections.push(selections.next().unwrap().clone());
22668 } else {
22669 break;
22670 }
22671 }
22672 (start_row, end_row)
22673}
22674
22675fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22676 if selection.start.column > 0 {
22677 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22678 } else {
22679 MultiBufferRow(selection.start.row)
22680 }
22681}
22682
22683fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22684 if next_selection.end.column > 0 || next_selection.is_empty() {
22685 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22686 } else {
22687 MultiBufferRow(next_selection.end.row)
22688 }
22689}
22690
22691impl EditorSnapshot {
22692 pub fn remote_selections_in_range<'a>(
22693 &'a self,
22694 range: &'a Range<Anchor>,
22695 collaboration_hub: &dyn CollaborationHub,
22696 cx: &'a App,
22697 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22698 let participant_names = collaboration_hub.user_names(cx);
22699 let participant_indices = collaboration_hub.user_participant_indices(cx);
22700 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22701 let collaborators_by_replica_id = collaborators_by_peer_id
22702 .values()
22703 .map(|collaborator| (collaborator.replica_id, collaborator))
22704 .collect::<HashMap<_, _>>();
22705 self.buffer_snapshot
22706 .selections_in_range(range, false)
22707 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22708 if replica_id == AGENT_REPLICA_ID {
22709 Some(RemoteSelection {
22710 replica_id,
22711 selection,
22712 cursor_shape,
22713 line_mode,
22714 collaborator_id: CollaboratorId::Agent,
22715 user_name: Some("Agent".into()),
22716 color: cx.theme().players().agent(),
22717 })
22718 } else {
22719 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22720 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22721 let user_name = participant_names.get(&collaborator.user_id).cloned();
22722 Some(RemoteSelection {
22723 replica_id,
22724 selection,
22725 cursor_shape,
22726 line_mode,
22727 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22728 user_name,
22729 color: if let Some(index) = participant_index {
22730 cx.theme().players().color_for_participant(index.0)
22731 } else {
22732 cx.theme().players().absent()
22733 },
22734 })
22735 }
22736 })
22737 }
22738
22739 pub fn hunks_for_ranges(
22740 &self,
22741 ranges: impl IntoIterator<Item = Range<Point>>,
22742 ) -> Vec<MultiBufferDiffHunk> {
22743 let mut hunks = Vec::new();
22744 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22745 HashMap::default();
22746 for query_range in ranges {
22747 let query_rows =
22748 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22749 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22750 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22751 ) {
22752 // Include deleted hunks that are adjacent to the query range, because
22753 // otherwise they would be missed.
22754 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22755 if hunk.status().is_deleted() {
22756 intersects_range |= hunk.row_range.start == query_rows.end;
22757 intersects_range |= hunk.row_range.end == query_rows.start;
22758 }
22759 if intersects_range {
22760 if !processed_buffer_rows
22761 .entry(hunk.buffer_id)
22762 .or_default()
22763 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22764 {
22765 continue;
22766 }
22767 hunks.push(hunk);
22768 }
22769 }
22770 }
22771
22772 hunks
22773 }
22774
22775 fn display_diff_hunks_for_rows<'a>(
22776 &'a self,
22777 display_rows: Range<DisplayRow>,
22778 folded_buffers: &'a HashSet<BufferId>,
22779 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22780 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22781 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22782
22783 self.buffer_snapshot
22784 .diff_hunks_in_range(buffer_start..buffer_end)
22785 .filter_map(|hunk| {
22786 if folded_buffers.contains(&hunk.buffer_id) {
22787 return None;
22788 }
22789
22790 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22791 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22792
22793 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22794 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22795
22796 let display_hunk = if hunk_display_start.column() != 0 {
22797 DisplayDiffHunk::Folded {
22798 display_row: hunk_display_start.row(),
22799 }
22800 } else {
22801 let mut end_row = hunk_display_end.row();
22802 if hunk_display_end.column() > 0 {
22803 end_row.0 += 1;
22804 }
22805 let is_created_file = hunk.is_created_file();
22806 DisplayDiffHunk::Unfolded {
22807 status: hunk.status(),
22808 diff_base_byte_range: hunk.diff_base_byte_range,
22809 display_row_range: hunk_display_start.row()..end_row,
22810 multi_buffer_range: Anchor::range_in_buffer(
22811 hunk.excerpt_id,
22812 hunk.buffer_id,
22813 hunk.buffer_range,
22814 ),
22815 is_created_file,
22816 }
22817 };
22818
22819 Some(display_hunk)
22820 })
22821 }
22822
22823 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22824 self.display_snapshot.buffer_snapshot.language_at(position)
22825 }
22826
22827 pub fn is_focused(&self) -> bool {
22828 self.is_focused
22829 }
22830
22831 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22832 self.placeholder_text.as_ref()
22833 }
22834
22835 pub fn scroll_position(&self) -> gpui::Point<f32> {
22836 self.scroll_anchor.scroll_position(&self.display_snapshot)
22837 }
22838
22839 fn gutter_dimensions(
22840 &self,
22841 font_id: FontId,
22842 font_size: Pixels,
22843 max_line_number_width: Pixels,
22844 cx: &App,
22845 ) -> Option<GutterDimensions> {
22846 if !self.show_gutter {
22847 return None;
22848 }
22849
22850 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22851 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22852
22853 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22854 matches!(
22855 ProjectSettings::get_global(cx).git.git_gutter,
22856 Some(GitGutterSetting::TrackedFiles)
22857 )
22858 });
22859 let gutter_settings = EditorSettings::get_global(cx).gutter;
22860 let show_line_numbers = self
22861 .show_line_numbers
22862 .unwrap_or(gutter_settings.line_numbers);
22863 let line_gutter_width = if show_line_numbers {
22864 // Avoid flicker-like gutter resizes when the line number gains another digit by
22865 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22866 let min_width_for_number_on_gutter =
22867 ch_advance * gutter_settings.min_line_number_digits as f32;
22868 max_line_number_width.max(min_width_for_number_on_gutter)
22869 } else {
22870 0.0.into()
22871 };
22872
22873 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22874 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22875
22876 let git_blame_entries_width =
22877 self.git_blame_gutter_max_author_length
22878 .map(|max_author_length| {
22879 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22880 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22881
22882 /// The number of characters to dedicate to gaps and margins.
22883 const SPACING_WIDTH: usize = 4;
22884
22885 let max_char_count = max_author_length.min(renderer.max_author_length())
22886 + ::git::SHORT_SHA_LENGTH
22887 + MAX_RELATIVE_TIMESTAMP.len()
22888 + SPACING_WIDTH;
22889
22890 ch_advance * max_char_count
22891 });
22892
22893 let is_singleton = self.buffer_snapshot.is_singleton();
22894
22895 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22896 left_padding += if !is_singleton {
22897 ch_width * 4.0
22898 } else if show_runnables || show_breakpoints {
22899 ch_width * 3.0
22900 } else if show_git_gutter && show_line_numbers {
22901 ch_width * 2.0
22902 } else if show_git_gutter || show_line_numbers {
22903 ch_width
22904 } else {
22905 px(0.)
22906 };
22907
22908 let shows_folds = is_singleton && gutter_settings.folds;
22909
22910 let right_padding = if shows_folds && show_line_numbers {
22911 ch_width * 4.0
22912 } else if shows_folds || (!is_singleton && show_line_numbers) {
22913 ch_width * 3.0
22914 } else if show_line_numbers {
22915 ch_width
22916 } else {
22917 px(0.)
22918 };
22919
22920 Some(GutterDimensions {
22921 left_padding,
22922 right_padding,
22923 width: line_gutter_width + left_padding + right_padding,
22924 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22925 git_blame_entries_width,
22926 })
22927 }
22928
22929 pub fn render_crease_toggle(
22930 &self,
22931 buffer_row: MultiBufferRow,
22932 row_contains_cursor: bool,
22933 editor: Entity<Editor>,
22934 window: &mut Window,
22935 cx: &mut App,
22936 ) -> Option<AnyElement> {
22937 let folded = self.is_line_folded(buffer_row);
22938 let mut is_foldable = false;
22939
22940 if let Some(crease) = self
22941 .crease_snapshot
22942 .query_row(buffer_row, &self.buffer_snapshot)
22943 {
22944 is_foldable = true;
22945 match crease {
22946 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22947 if let Some(render_toggle) = render_toggle {
22948 let toggle_callback =
22949 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22950 if folded {
22951 editor.update(cx, |editor, cx| {
22952 editor.fold_at(buffer_row, window, cx)
22953 });
22954 } else {
22955 editor.update(cx, |editor, cx| {
22956 editor.unfold_at(buffer_row, window, cx)
22957 });
22958 }
22959 });
22960 return Some((render_toggle)(
22961 buffer_row,
22962 folded,
22963 toggle_callback,
22964 window,
22965 cx,
22966 ));
22967 }
22968 }
22969 }
22970 }
22971
22972 is_foldable |= self.starts_indent(buffer_row);
22973
22974 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22975 Some(
22976 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22977 .toggle_state(folded)
22978 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22979 if folded {
22980 this.unfold_at(buffer_row, window, cx);
22981 } else {
22982 this.fold_at(buffer_row, window, cx);
22983 }
22984 }))
22985 .into_any_element(),
22986 )
22987 } else {
22988 None
22989 }
22990 }
22991
22992 pub fn render_crease_trailer(
22993 &self,
22994 buffer_row: MultiBufferRow,
22995 window: &mut Window,
22996 cx: &mut App,
22997 ) -> Option<AnyElement> {
22998 let folded = self.is_line_folded(buffer_row);
22999 if let Crease::Inline { render_trailer, .. } = self
23000 .crease_snapshot
23001 .query_row(buffer_row, &self.buffer_snapshot)?
23002 {
23003 let render_trailer = render_trailer.as_ref()?;
23004 Some(render_trailer(buffer_row, folded, window, cx))
23005 } else {
23006 None
23007 }
23008 }
23009}
23010
23011impl Deref for EditorSnapshot {
23012 type Target = DisplaySnapshot;
23013
23014 fn deref(&self) -> &Self::Target {
23015 &self.display_snapshot
23016 }
23017}
23018
23019#[derive(Clone, Debug, PartialEq, Eq)]
23020pub enum EditorEvent {
23021 InputIgnored {
23022 text: Arc<str>,
23023 },
23024 InputHandled {
23025 utf16_range_to_replace: Option<Range<isize>>,
23026 text: Arc<str>,
23027 },
23028 ExcerptsAdded {
23029 buffer: Entity<Buffer>,
23030 predecessor: ExcerptId,
23031 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23032 },
23033 ExcerptsRemoved {
23034 ids: Vec<ExcerptId>,
23035 removed_buffer_ids: Vec<BufferId>,
23036 },
23037 BufferFoldToggled {
23038 ids: Vec<ExcerptId>,
23039 folded: bool,
23040 },
23041 ExcerptsEdited {
23042 ids: Vec<ExcerptId>,
23043 },
23044 ExcerptsExpanded {
23045 ids: Vec<ExcerptId>,
23046 },
23047 BufferEdited,
23048 Edited {
23049 transaction_id: clock::Lamport,
23050 },
23051 Reparsed(BufferId),
23052 Focused,
23053 FocusedIn,
23054 Blurred,
23055 DirtyChanged,
23056 Saved,
23057 TitleChanged,
23058 SelectionsChanged {
23059 local: bool,
23060 },
23061 ScrollPositionChanged {
23062 local: bool,
23063 autoscroll: bool,
23064 },
23065 TransactionUndone {
23066 transaction_id: clock::Lamport,
23067 },
23068 TransactionBegun {
23069 transaction_id: clock::Lamport,
23070 },
23071 CursorShapeChanged,
23072 BreadcrumbsChanged,
23073 PushedToNavHistory {
23074 anchor: Anchor,
23075 is_deactivate: bool,
23076 },
23077}
23078
23079impl EventEmitter<EditorEvent> for Editor {}
23080
23081impl Focusable for Editor {
23082 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23083 self.focus_handle.clone()
23084 }
23085}
23086
23087impl Render for Editor {
23088 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23089 let settings = ThemeSettings::get_global(cx);
23090
23091 let mut text_style = match self.mode {
23092 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23093 color: cx.theme().colors().editor_foreground,
23094 font_family: settings.ui_font.family.clone(),
23095 font_features: settings.ui_font.features.clone(),
23096 font_fallbacks: settings.ui_font.fallbacks.clone(),
23097 font_size: rems(0.875).into(),
23098 font_weight: settings.ui_font.weight,
23099 line_height: relative(settings.buffer_line_height.value()),
23100 ..Default::default()
23101 },
23102 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23103 color: cx.theme().colors().editor_foreground,
23104 font_family: settings.buffer_font.family.clone(),
23105 font_features: settings.buffer_font.features.clone(),
23106 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23107 font_size: settings.buffer_font_size(cx).into(),
23108 font_weight: settings.buffer_font.weight,
23109 line_height: relative(settings.buffer_line_height.value()),
23110 ..Default::default()
23111 },
23112 };
23113 if let Some(text_style_refinement) = &self.text_style_refinement {
23114 text_style.refine(text_style_refinement)
23115 }
23116
23117 let background = match self.mode {
23118 EditorMode::SingleLine => cx.theme().system().transparent,
23119 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23120 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23121 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23122 };
23123
23124 EditorElement::new(
23125 &cx.entity(),
23126 EditorStyle {
23127 background,
23128 border: cx.theme().colors().border,
23129 local_player: cx.theme().players().local(),
23130 text: text_style,
23131 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23132 syntax: cx.theme().syntax().clone(),
23133 status: cx.theme().status().clone(),
23134 inlay_hints_style: make_inlay_hints_style(cx),
23135 edit_prediction_styles: make_suggestion_styles(cx),
23136 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23137 show_underlines: self.diagnostics_enabled(),
23138 },
23139 )
23140 }
23141}
23142
23143impl EntityInputHandler for Editor {
23144 fn text_for_range(
23145 &mut self,
23146 range_utf16: Range<usize>,
23147 adjusted_range: &mut Option<Range<usize>>,
23148 _: &mut Window,
23149 cx: &mut Context<Self>,
23150 ) -> Option<String> {
23151 let snapshot = self.buffer.read(cx).read(cx);
23152 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23153 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23154 if (start.0..end.0) != range_utf16 {
23155 adjusted_range.replace(start.0..end.0);
23156 }
23157 Some(snapshot.text_for_range(start..end).collect())
23158 }
23159
23160 fn selected_text_range(
23161 &mut self,
23162 ignore_disabled_input: bool,
23163 _: &mut Window,
23164 cx: &mut Context<Self>,
23165 ) -> Option<UTF16Selection> {
23166 // Prevent the IME menu from appearing when holding down an alphabetic key
23167 // while input is disabled.
23168 if !ignore_disabled_input && !self.input_enabled {
23169 return None;
23170 }
23171
23172 let selection = self.selections.newest::<OffsetUtf16>(cx);
23173 let range = selection.range();
23174
23175 Some(UTF16Selection {
23176 range: range.start.0..range.end.0,
23177 reversed: selection.reversed,
23178 })
23179 }
23180
23181 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23182 let snapshot = self.buffer.read(cx).read(cx);
23183 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23184 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23185 }
23186
23187 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23188 self.clear_highlights::<InputComposition>(cx);
23189 self.ime_transaction.take();
23190 }
23191
23192 fn replace_text_in_range(
23193 &mut self,
23194 range_utf16: Option<Range<usize>>,
23195 text: &str,
23196 window: &mut Window,
23197 cx: &mut Context<Self>,
23198 ) {
23199 if !self.input_enabled {
23200 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23201 return;
23202 }
23203
23204 self.transact(window, cx, |this, window, cx| {
23205 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23206 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23207 Some(this.selection_replacement_ranges(range_utf16, cx))
23208 } else {
23209 this.marked_text_ranges(cx)
23210 };
23211
23212 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23213 let newest_selection_id = this.selections.newest_anchor().id;
23214 this.selections
23215 .all::<OffsetUtf16>(cx)
23216 .iter()
23217 .zip(ranges_to_replace.iter())
23218 .find_map(|(selection, range)| {
23219 if selection.id == newest_selection_id {
23220 Some(
23221 (range.start.0 as isize - selection.head().0 as isize)
23222 ..(range.end.0 as isize - selection.head().0 as isize),
23223 )
23224 } else {
23225 None
23226 }
23227 })
23228 });
23229
23230 cx.emit(EditorEvent::InputHandled {
23231 utf16_range_to_replace: range_to_replace,
23232 text: text.into(),
23233 });
23234
23235 if let Some(new_selected_ranges) = new_selected_ranges {
23236 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23237 selections.select_ranges(new_selected_ranges)
23238 });
23239 this.backspace(&Default::default(), window, cx);
23240 }
23241
23242 this.handle_input(text, window, cx);
23243 });
23244
23245 if let Some(transaction) = self.ime_transaction {
23246 self.buffer.update(cx, |buffer, cx| {
23247 buffer.group_until_transaction(transaction, cx);
23248 });
23249 }
23250
23251 self.unmark_text(window, cx);
23252 }
23253
23254 fn replace_and_mark_text_in_range(
23255 &mut self,
23256 range_utf16: Option<Range<usize>>,
23257 text: &str,
23258 new_selected_range_utf16: Option<Range<usize>>,
23259 window: &mut Window,
23260 cx: &mut Context<Self>,
23261 ) {
23262 if !self.input_enabled {
23263 return;
23264 }
23265
23266 let transaction = self.transact(window, cx, |this, window, cx| {
23267 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23268 let snapshot = this.buffer.read(cx).read(cx);
23269 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23270 for marked_range in &mut marked_ranges {
23271 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23272 marked_range.start.0 += relative_range_utf16.start;
23273 marked_range.start =
23274 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23275 marked_range.end =
23276 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23277 }
23278 }
23279 Some(marked_ranges)
23280 } else if let Some(range_utf16) = range_utf16 {
23281 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23282 Some(this.selection_replacement_ranges(range_utf16, cx))
23283 } else {
23284 None
23285 };
23286
23287 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23288 let newest_selection_id = this.selections.newest_anchor().id;
23289 this.selections
23290 .all::<OffsetUtf16>(cx)
23291 .iter()
23292 .zip(ranges_to_replace.iter())
23293 .find_map(|(selection, range)| {
23294 if selection.id == newest_selection_id {
23295 Some(
23296 (range.start.0 as isize - selection.head().0 as isize)
23297 ..(range.end.0 as isize - selection.head().0 as isize),
23298 )
23299 } else {
23300 None
23301 }
23302 })
23303 });
23304
23305 cx.emit(EditorEvent::InputHandled {
23306 utf16_range_to_replace: range_to_replace,
23307 text: text.into(),
23308 });
23309
23310 if let Some(ranges) = ranges_to_replace {
23311 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23312 s.select_ranges(ranges)
23313 });
23314 }
23315
23316 let marked_ranges = {
23317 let snapshot = this.buffer.read(cx).read(cx);
23318 this.selections
23319 .disjoint_anchors()
23320 .iter()
23321 .map(|selection| {
23322 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23323 })
23324 .collect::<Vec<_>>()
23325 };
23326
23327 if text.is_empty() {
23328 this.unmark_text(window, cx);
23329 } else {
23330 this.highlight_text::<InputComposition>(
23331 marked_ranges.clone(),
23332 HighlightStyle {
23333 underline: Some(UnderlineStyle {
23334 thickness: px(1.),
23335 color: None,
23336 wavy: false,
23337 }),
23338 ..Default::default()
23339 },
23340 cx,
23341 );
23342 }
23343
23344 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23345 let use_autoclose = this.use_autoclose;
23346 let use_auto_surround = this.use_auto_surround;
23347 this.set_use_autoclose(false);
23348 this.set_use_auto_surround(false);
23349 this.handle_input(text, window, cx);
23350 this.set_use_autoclose(use_autoclose);
23351 this.set_use_auto_surround(use_auto_surround);
23352
23353 if let Some(new_selected_range) = new_selected_range_utf16 {
23354 let snapshot = this.buffer.read(cx).read(cx);
23355 let new_selected_ranges = marked_ranges
23356 .into_iter()
23357 .map(|marked_range| {
23358 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23359 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23360 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23361 snapshot.clip_offset_utf16(new_start, Bias::Left)
23362 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23363 })
23364 .collect::<Vec<_>>();
23365
23366 drop(snapshot);
23367 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23368 selections.select_ranges(new_selected_ranges)
23369 });
23370 }
23371 });
23372
23373 self.ime_transaction = self.ime_transaction.or(transaction);
23374 if let Some(transaction) = self.ime_transaction {
23375 self.buffer.update(cx, |buffer, cx| {
23376 buffer.group_until_transaction(transaction, cx);
23377 });
23378 }
23379
23380 if self.text_highlights::<InputComposition>(cx).is_none() {
23381 self.ime_transaction.take();
23382 }
23383 }
23384
23385 fn bounds_for_range(
23386 &mut self,
23387 range_utf16: Range<usize>,
23388 element_bounds: gpui::Bounds<Pixels>,
23389 window: &mut Window,
23390 cx: &mut Context<Self>,
23391 ) -> Option<gpui::Bounds<Pixels>> {
23392 let text_layout_details = self.text_layout_details(window);
23393 let CharacterDimensions {
23394 em_width,
23395 em_advance,
23396 line_height,
23397 } = self.character_dimensions(window);
23398
23399 let snapshot = self.snapshot(window, cx);
23400 let scroll_position = snapshot.scroll_position();
23401 let scroll_left = scroll_position.x * em_advance;
23402
23403 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23404 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23405 + self.gutter_dimensions.full_width();
23406 let y = line_height * (start.row().as_f32() - scroll_position.y);
23407
23408 Some(Bounds {
23409 origin: element_bounds.origin + point(x, y),
23410 size: size(em_width, line_height),
23411 })
23412 }
23413
23414 fn character_index_for_point(
23415 &mut self,
23416 point: gpui::Point<Pixels>,
23417 _window: &mut Window,
23418 _cx: &mut Context<Self>,
23419 ) -> Option<usize> {
23420 let position_map = self.last_position_map.as_ref()?;
23421 if !position_map.text_hitbox.contains(&point) {
23422 return None;
23423 }
23424 let display_point = position_map.point_for_position(point).previous_valid;
23425 let anchor = position_map
23426 .snapshot
23427 .display_point_to_anchor(display_point, Bias::Left);
23428 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23429 Some(utf16_offset.0)
23430 }
23431}
23432
23433trait SelectionExt {
23434 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23435 fn spanned_rows(
23436 &self,
23437 include_end_if_at_line_start: bool,
23438 map: &DisplaySnapshot,
23439 ) -> Range<MultiBufferRow>;
23440}
23441
23442impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23443 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23444 let start = self
23445 .start
23446 .to_point(&map.buffer_snapshot)
23447 .to_display_point(map);
23448 let end = self
23449 .end
23450 .to_point(&map.buffer_snapshot)
23451 .to_display_point(map);
23452 if self.reversed {
23453 end..start
23454 } else {
23455 start..end
23456 }
23457 }
23458
23459 fn spanned_rows(
23460 &self,
23461 include_end_if_at_line_start: bool,
23462 map: &DisplaySnapshot,
23463 ) -> Range<MultiBufferRow> {
23464 let start = self.start.to_point(&map.buffer_snapshot);
23465 let mut end = self.end.to_point(&map.buffer_snapshot);
23466 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23467 end.row -= 1;
23468 }
23469
23470 let buffer_start = map.prev_line_boundary(start).0;
23471 let buffer_end = map.next_line_boundary(end).0;
23472 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23473 }
23474}
23475
23476impl<T: InvalidationRegion> InvalidationStack<T> {
23477 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23478 where
23479 S: Clone + ToOffset,
23480 {
23481 while let Some(region) = self.last() {
23482 let all_selections_inside_invalidation_ranges =
23483 if selections.len() == region.ranges().len() {
23484 selections
23485 .iter()
23486 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23487 .all(|(selection, invalidation_range)| {
23488 let head = selection.head().to_offset(buffer);
23489 invalidation_range.start <= head && invalidation_range.end >= head
23490 })
23491 } else {
23492 false
23493 };
23494
23495 if all_selections_inside_invalidation_ranges {
23496 break;
23497 } else {
23498 self.pop();
23499 }
23500 }
23501 }
23502}
23503
23504impl<T> Default for InvalidationStack<T> {
23505 fn default() -> Self {
23506 Self(Default::default())
23507 }
23508}
23509
23510impl<T> Deref for InvalidationStack<T> {
23511 type Target = Vec<T>;
23512
23513 fn deref(&self) -> &Self::Target {
23514 &self.0
23515 }
23516}
23517
23518impl<T> DerefMut for InvalidationStack<T> {
23519 fn deref_mut(&mut self) -> &mut Self::Target {
23520 &mut self.0
23521 }
23522}
23523
23524impl InvalidationRegion for SnippetState {
23525 fn ranges(&self) -> &[Range<Anchor>] {
23526 &self.ranges[self.active_index]
23527 }
23528}
23529
23530fn edit_prediction_edit_text(
23531 current_snapshot: &BufferSnapshot,
23532 edits: &[(Range<Anchor>, String)],
23533 edit_preview: &EditPreview,
23534 include_deletions: bool,
23535 cx: &App,
23536) -> HighlightedText {
23537 let edits = edits
23538 .iter()
23539 .map(|(anchor, text)| {
23540 (
23541 anchor.start.text_anchor..anchor.end.text_anchor,
23542 text.clone(),
23543 )
23544 })
23545 .collect::<Vec<_>>();
23546
23547 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23548}
23549
23550fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23551 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23552 // Just show the raw edit text with basic styling
23553 let mut text = String::new();
23554 let mut highlights = Vec::new();
23555
23556 let insertion_highlight_style = HighlightStyle {
23557 color: Some(cx.theme().colors().text),
23558 ..Default::default()
23559 };
23560
23561 for (_, edit_text) in edits {
23562 let start_offset = text.len();
23563 text.push_str(edit_text);
23564 let end_offset = text.len();
23565
23566 if start_offset < end_offset {
23567 highlights.push((start_offset..end_offset, insertion_highlight_style));
23568 }
23569 }
23570
23571 HighlightedText {
23572 text: text.into(),
23573 highlights,
23574 }
23575}
23576
23577pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23578 match severity {
23579 lsp::DiagnosticSeverity::ERROR => colors.error,
23580 lsp::DiagnosticSeverity::WARNING => colors.warning,
23581 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23582 lsp::DiagnosticSeverity::HINT => colors.info,
23583 _ => colors.ignored,
23584 }
23585}
23586
23587pub fn styled_runs_for_code_label<'a>(
23588 label: &'a CodeLabel,
23589 syntax_theme: &'a theme::SyntaxTheme,
23590) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23591 let fade_out = HighlightStyle {
23592 fade_out: Some(0.35),
23593 ..Default::default()
23594 };
23595
23596 let mut prev_end = label.filter_range.end;
23597 label
23598 .runs
23599 .iter()
23600 .enumerate()
23601 .flat_map(move |(ix, (range, highlight_id))| {
23602 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23603 style
23604 } else {
23605 return Default::default();
23606 };
23607 let mut muted_style = style;
23608 muted_style.highlight(fade_out);
23609
23610 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23611 if range.start >= label.filter_range.end {
23612 if range.start > prev_end {
23613 runs.push((prev_end..range.start, fade_out));
23614 }
23615 runs.push((range.clone(), muted_style));
23616 } else if range.end <= label.filter_range.end {
23617 runs.push((range.clone(), style));
23618 } else {
23619 runs.push((range.start..label.filter_range.end, style));
23620 runs.push((label.filter_range.end..range.end, muted_style));
23621 }
23622 prev_end = cmp::max(prev_end, range.end);
23623
23624 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23625 runs.push((prev_end..label.text.len(), fade_out));
23626 }
23627
23628 runs
23629 })
23630}
23631
23632pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23633 let mut prev_index = 0;
23634 let mut prev_codepoint: Option<char> = None;
23635 text.char_indices()
23636 .chain([(text.len(), '\0')])
23637 .filter_map(move |(index, codepoint)| {
23638 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23639 let is_boundary = index == text.len()
23640 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23641 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23642 if is_boundary {
23643 let chunk = &text[prev_index..index];
23644 prev_index = index;
23645 Some(chunk)
23646 } else {
23647 None
23648 }
23649 })
23650}
23651
23652pub trait RangeToAnchorExt: Sized {
23653 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23654
23655 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23656 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23657 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23658 }
23659}
23660
23661impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23662 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23663 let start_offset = self.start.to_offset(snapshot);
23664 let end_offset = self.end.to_offset(snapshot);
23665 if start_offset == end_offset {
23666 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23667 } else {
23668 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23669 }
23670 }
23671}
23672
23673pub trait RowExt {
23674 fn as_f32(&self) -> f32;
23675
23676 fn next_row(&self) -> Self;
23677
23678 fn previous_row(&self) -> Self;
23679
23680 fn minus(&self, other: Self) -> u32;
23681}
23682
23683impl RowExt for DisplayRow {
23684 fn as_f32(&self) -> f32 {
23685 self.0 as f32
23686 }
23687
23688 fn next_row(&self) -> Self {
23689 Self(self.0 + 1)
23690 }
23691
23692 fn previous_row(&self) -> Self {
23693 Self(self.0.saturating_sub(1))
23694 }
23695
23696 fn minus(&self, other: Self) -> u32 {
23697 self.0 - other.0
23698 }
23699}
23700
23701impl RowExt for MultiBufferRow {
23702 fn as_f32(&self) -> f32 {
23703 self.0 as f32
23704 }
23705
23706 fn next_row(&self) -> Self {
23707 Self(self.0 + 1)
23708 }
23709
23710 fn previous_row(&self) -> Self {
23711 Self(self.0.saturating_sub(1))
23712 }
23713
23714 fn minus(&self, other: Self) -> u32 {
23715 self.0 - other.0
23716 }
23717}
23718
23719trait RowRangeExt {
23720 type Row;
23721
23722 fn len(&self) -> usize;
23723
23724 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23725}
23726
23727impl RowRangeExt for Range<MultiBufferRow> {
23728 type Row = MultiBufferRow;
23729
23730 fn len(&self) -> usize {
23731 (self.end.0 - self.start.0) as usize
23732 }
23733
23734 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23735 (self.start.0..self.end.0).map(MultiBufferRow)
23736 }
23737}
23738
23739impl RowRangeExt for Range<DisplayRow> {
23740 type Row = DisplayRow;
23741
23742 fn len(&self) -> usize {
23743 (self.end.0 - self.start.0) as usize
23744 }
23745
23746 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23747 (self.start.0..self.end.0).map(DisplayRow)
23748 }
23749}
23750
23751/// If select range has more than one line, we
23752/// just point the cursor to range.start.
23753fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23754 if range.start.row == range.end.row {
23755 range
23756 } else {
23757 range.start..range.start
23758 }
23759}
23760pub struct KillRing(ClipboardItem);
23761impl Global for KillRing {}
23762
23763const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23764
23765enum BreakpointPromptEditAction {
23766 Log,
23767 Condition,
23768 HitCondition,
23769}
23770
23771struct BreakpointPromptEditor {
23772 pub(crate) prompt: Entity<Editor>,
23773 editor: WeakEntity<Editor>,
23774 breakpoint_anchor: Anchor,
23775 breakpoint: Breakpoint,
23776 edit_action: BreakpointPromptEditAction,
23777 block_ids: HashSet<CustomBlockId>,
23778 editor_margins: Arc<Mutex<EditorMargins>>,
23779 _subscriptions: Vec<Subscription>,
23780}
23781
23782impl BreakpointPromptEditor {
23783 const MAX_LINES: u8 = 4;
23784
23785 fn new(
23786 editor: WeakEntity<Editor>,
23787 breakpoint_anchor: Anchor,
23788 breakpoint: Breakpoint,
23789 edit_action: BreakpointPromptEditAction,
23790 window: &mut Window,
23791 cx: &mut Context<Self>,
23792 ) -> Self {
23793 let base_text = match edit_action {
23794 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23795 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23796 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23797 }
23798 .map(|msg| msg.to_string())
23799 .unwrap_or_default();
23800
23801 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23802 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23803
23804 let prompt = cx.new(|cx| {
23805 let mut prompt = Editor::new(
23806 EditorMode::AutoHeight {
23807 min_lines: 1,
23808 max_lines: Some(Self::MAX_LINES as usize),
23809 },
23810 buffer,
23811 None,
23812 window,
23813 cx,
23814 );
23815 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23816 prompt.set_show_cursor_when_unfocused(false, cx);
23817 prompt.set_placeholder_text(
23818 match edit_action {
23819 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23820 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23821 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23822 },
23823 cx,
23824 );
23825
23826 prompt
23827 });
23828
23829 Self {
23830 prompt,
23831 editor,
23832 breakpoint_anchor,
23833 breakpoint,
23834 edit_action,
23835 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23836 block_ids: Default::default(),
23837 _subscriptions: vec![],
23838 }
23839 }
23840
23841 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23842 self.block_ids.extend(block_ids)
23843 }
23844
23845 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23846 if let Some(editor) = self.editor.upgrade() {
23847 let message = self
23848 .prompt
23849 .read(cx)
23850 .buffer
23851 .read(cx)
23852 .as_singleton()
23853 .expect("A multi buffer in breakpoint prompt isn't possible")
23854 .read(cx)
23855 .as_rope()
23856 .to_string();
23857
23858 editor.update(cx, |editor, cx| {
23859 editor.edit_breakpoint_at_anchor(
23860 self.breakpoint_anchor,
23861 self.breakpoint.clone(),
23862 match self.edit_action {
23863 BreakpointPromptEditAction::Log => {
23864 BreakpointEditAction::EditLogMessage(message.into())
23865 }
23866 BreakpointPromptEditAction::Condition => {
23867 BreakpointEditAction::EditCondition(message.into())
23868 }
23869 BreakpointPromptEditAction::HitCondition => {
23870 BreakpointEditAction::EditHitCondition(message.into())
23871 }
23872 },
23873 cx,
23874 );
23875
23876 editor.remove_blocks(self.block_ids.clone(), None, cx);
23877 cx.focus_self(window);
23878 });
23879 }
23880 }
23881
23882 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23883 self.editor
23884 .update(cx, |editor, cx| {
23885 editor.remove_blocks(self.block_ids.clone(), None, cx);
23886 window.focus(&editor.focus_handle);
23887 })
23888 .log_err();
23889 }
23890
23891 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23892 let settings = ThemeSettings::get_global(cx);
23893 let text_style = TextStyle {
23894 color: if self.prompt.read(cx).read_only(cx) {
23895 cx.theme().colors().text_disabled
23896 } else {
23897 cx.theme().colors().text
23898 },
23899 font_family: settings.buffer_font.family.clone(),
23900 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23901 font_size: settings.buffer_font_size(cx).into(),
23902 font_weight: settings.buffer_font.weight,
23903 line_height: relative(settings.buffer_line_height.value()),
23904 ..Default::default()
23905 };
23906 EditorElement::new(
23907 &self.prompt,
23908 EditorStyle {
23909 background: cx.theme().colors().editor_background,
23910 local_player: cx.theme().players().local(),
23911 text: text_style,
23912 ..Default::default()
23913 },
23914 )
23915 }
23916}
23917
23918impl Render for BreakpointPromptEditor {
23919 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23920 let editor_margins = *self.editor_margins.lock();
23921 let gutter_dimensions = editor_margins.gutter;
23922 h_flex()
23923 .key_context("Editor")
23924 .bg(cx.theme().colors().editor_background)
23925 .border_y_1()
23926 .border_color(cx.theme().status().info_border)
23927 .size_full()
23928 .py(window.line_height() / 2.5)
23929 .on_action(cx.listener(Self::confirm))
23930 .on_action(cx.listener(Self::cancel))
23931 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23932 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23933 }
23934}
23935
23936impl Focusable for BreakpointPromptEditor {
23937 fn focus_handle(&self, cx: &App) -> FocusHandle {
23938 self.prompt.focus_handle(cx)
23939 }
23940}
23941
23942fn all_edits_insertions_or_deletions(
23943 edits: &Vec<(Range<Anchor>, String)>,
23944 snapshot: &MultiBufferSnapshot,
23945) -> bool {
23946 let mut all_insertions = true;
23947 let mut all_deletions = true;
23948
23949 for (range, new_text) in edits.iter() {
23950 let range_is_empty = range.to_offset(snapshot).is_empty();
23951 let text_is_empty = new_text.is_empty();
23952
23953 if range_is_empty != text_is_empty {
23954 if range_is_empty {
23955 all_deletions = false;
23956 } else {
23957 all_insertions = false;
23958 }
23959 } else {
23960 return false;
23961 }
23962
23963 if !all_insertions && !all_deletions {
23964 return false;
23965 }
23966 }
23967 all_insertions || all_deletions
23968}
23969
23970struct MissingEditPredictionKeybindingTooltip;
23971
23972impl Render for MissingEditPredictionKeybindingTooltip {
23973 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23974 ui::tooltip_container(window, cx, |container, _, cx| {
23975 container
23976 .flex_shrink_0()
23977 .max_w_80()
23978 .min_h(rems_from_px(124.))
23979 .justify_between()
23980 .child(
23981 v_flex()
23982 .flex_1()
23983 .text_ui_sm(cx)
23984 .child(Label::new("Conflict with Accept Keybinding"))
23985 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23986 )
23987 .child(
23988 h_flex()
23989 .pb_1()
23990 .gap_1()
23991 .items_end()
23992 .w_full()
23993 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23994 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23995 }))
23996 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23997 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23998 })),
23999 )
24000 })
24001 }
24002}
24003
24004#[derive(Debug, Clone, Copy, PartialEq)]
24005pub struct LineHighlight {
24006 pub background: Background,
24007 pub border: Option<gpui::Hsla>,
24008 pub include_gutter: bool,
24009 pub type_id: Option<TypeId>,
24010}
24011
24012struct LineManipulationResult {
24013 pub new_text: String,
24014 pub line_count_before: usize,
24015 pub line_count_after: usize,
24016}
24017
24018fn render_diff_hunk_controls(
24019 row: u32,
24020 status: &DiffHunkStatus,
24021 hunk_range: Range<Anchor>,
24022 is_created_file: bool,
24023 line_height: Pixels,
24024 editor: &Entity<Editor>,
24025 _window: &mut Window,
24026 cx: &mut App,
24027) -> AnyElement {
24028 h_flex()
24029 .h(line_height)
24030 .mr_1()
24031 .gap_1()
24032 .px_0p5()
24033 .pb_1()
24034 .border_x_1()
24035 .border_b_1()
24036 .border_color(cx.theme().colors().border_variant)
24037 .rounded_b_lg()
24038 .bg(cx.theme().colors().editor_background)
24039 .gap_1()
24040 .block_mouse_except_scroll()
24041 .shadow_md()
24042 .child(if status.has_secondary_hunk() {
24043 Button::new(("stage", row as u64), "Stage")
24044 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24045 .tooltip({
24046 let focus_handle = editor.focus_handle(cx);
24047 move |window, cx| {
24048 Tooltip::for_action_in(
24049 "Stage Hunk",
24050 &::git::ToggleStaged,
24051 &focus_handle,
24052 window,
24053 cx,
24054 )
24055 }
24056 })
24057 .on_click({
24058 let editor = editor.clone();
24059 move |_event, _window, cx| {
24060 editor.update(cx, |editor, cx| {
24061 editor.stage_or_unstage_diff_hunks(
24062 true,
24063 vec![hunk_range.start..hunk_range.start],
24064 cx,
24065 );
24066 });
24067 }
24068 })
24069 } else {
24070 Button::new(("unstage", row as u64), "Unstage")
24071 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24072 .tooltip({
24073 let focus_handle = editor.focus_handle(cx);
24074 move |window, cx| {
24075 Tooltip::for_action_in(
24076 "Unstage Hunk",
24077 &::git::ToggleStaged,
24078 &focus_handle,
24079 window,
24080 cx,
24081 )
24082 }
24083 })
24084 .on_click({
24085 let editor = editor.clone();
24086 move |_event, _window, cx| {
24087 editor.update(cx, |editor, cx| {
24088 editor.stage_or_unstage_diff_hunks(
24089 false,
24090 vec![hunk_range.start..hunk_range.start],
24091 cx,
24092 );
24093 });
24094 }
24095 })
24096 })
24097 .child(
24098 Button::new(("restore", row as u64), "Restore")
24099 .tooltip({
24100 let focus_handle = editor.focus_handle(cx);
24101 move |window, cx| {
24102 Tooltip::for_action_in(
24103 "Restore Hunk",
24104 &::git::Restore,
24105 &focus_handle,
24106 window,
24107 cx,
24108 )
24109 }
24110 })
24111 .on_click({
24112 let editor = editor.clone();
24113 move |_event, window, cx| {
24114 editor.update(cx, |editor, cx| {
24115 let snapshot = editor.snapshot(window, cx);
24116 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24117 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24118 });
24119 }
24120 })
24121 .disabled(is_created_file),
24122 )
24123 .when(
24124 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24125 |el| {
24126 el.child(
24127 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24128 .shape(IconButtonShape::Square)
24129 .icon_size(IconSize::Small)
24130 // .disabled(!has_multiple_hunks)
24131 .tooltip({
24132 let focus_handle = editor.focus_handle(cx);
24133 move |window, cx| {
24134 Tooltip::for_action_in(
24135 "Next Hunk",
24136 &GoToHunk,
24137 &focus_handle,
24138 window,
24139 cx,
24140 )
24141 }
24142 })
24143 .on_click({
24144 let editor = editor.clone();
24145 move |_event, window, cx| {
24146 editor.update(cx, |editor, cx| {
24147 let snapshot = editor.snapshot(window, cx);
24148 let position =
24149 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24150 editor.go_to_hunk_before_or_after_position(
24151 &snapshot,
24152 position,
24153 Direction::Next,
24154 window,
24155 cx,
24156 );
24157 editor.expand_selected_diff_hunks(cx);
24158 });
24159 }
24160 }),
24161 )
24162 .child(
24163 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24164 .shape(IconButtonShape::Square)
24165 .icon_size(IconSize::Small)
24166 // .disabled(!has_multiple_hunks)
24167 .tooltip({
24168 let focus_handle = editor.focus_handle(cx);
24169 move |window, cx| {
24170 Tooltip::for_action_in(
24171 "Previous Hunk",
24172 &GoToPreviousHunk,
24173 &focus_handle,
24174 window,
24175 cx,
24176 )
24177 }
24178 })
24179 .on_click({
24180 let editor = editor.clone();
24181 move |_event, window, cx| {
24182 editor.update(cx, |editor, cx| {
24183 let snapshot = editor.snapshot(window, cx);
24184 let point =
24185 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24186 editor.go_to_hunk_before_or_after_position(
24187 &snapshot,
24188 point,
24189 Direction::Prev,
24190 window,
24191 cx,
24192 );
24193 editor.expand_selected_diff_hunks(cx);
24194 });
24195 }
24196 }),
24197 )
24198 },
24199 )
24200 .into_any_element()
24201}
24202
24203pub fn multibuffer_context_lines(cx: &App) -> u32 {
24204 EditorSettings::try_get(cx)
24205 .map(|settings| settings.excerpt_context_lines)
24206 .unwrap_or(2)
24207 .clamp(1, 32)
24208}