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)) = buffer.syntax_ancestor(new_range.clone())
14950 {
14951 if !node.is_named() {
14952 new_range = node.start_byte()..node.end_byte();
14953 continue;
14954 }
14955
14956 new_range = match containing_range {
14957 MultiOrSingleBufferOffsetRange::Single(_) => break,
14958 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14959 };
14960 if !display_map.intersects_fold(new_range.start)
14961 && !display_map.intersects_fold(new_range.end)
14962 {
14963 break;
14964 }
14965 }
14966
14967 selected_larger_node |= new_range != old_range;
14968 Selection {
14969 id: selection.id,
14970 start: new_range.start,
14971 end: new_range.end,
14972 goal: SelectionGoal::None,
14973 reversed: selection.reversed,
14974 }
14975 })
14976 .collect::<Vec<_>>();
14977
14978 if !selected_larger_node {
14979 return; // don't put this call in the history
14980 }
14981
14982 // scroll based on transformation done to the last selection created by the user
14983 let (last_old, last_new) = old_selections
14984 .last()
14985 .zip(new_selections.last().cloned())
14986 .expect("old_selections isn't empty");
14987
14988 // revert selection
14989 let is_selection_reversed = {
14990 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14991 new_selections.last_mut().expect("checked above").reversed =
14992 should_newest_selection_be_reversed;
14993 should_newest_selection_be_reversed
14994 };
14995
14996 if selected_larger_node {
14997 self.select_syntax_node_history.disable_clearing = true;
14998 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14999 s.select(new_selections.clone());
15000 });
15001 self.select_syntax_node_history.disable_clearing = false;
15002 }
15003
15004 let start_row = last_new.start.to_display_point(&display_map).row().0;
15005 let end_row = last_new.end.to_display_point(&display_map).row().0;
15006 let selection_height = end_row - start_row + 1;
15007 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15008
15009 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15010 let scroll_behavior = if fits_on_the_screen {
15011 self.request_autoscroll(Autoscroll::fit(), cx);
15012 SelectSyntaxNodeScrollBehavior::FitSelection
15013 } else if is_selection_reversed {
15014 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15015 SelectSyntaxNodeScrollBehavior::CursorTop
15016 } else {
15017 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15018 SelectSyntaxNodeScrollBehavior::CursorBottom
15019 };
15020
15021 self.select_syntax_node_history.push((
15022 old_selections,
15023 scroll_behavior,
15024 is_selection_reversed,
15025 ));
15026 }
15027
15028 pub fn select_smaller_syntax_node(
15029 &mut self,
15030 _: &SelectSmallerSyntaxNode,
15031 window: &mut Window,
15032 cx: &mut Context<Self>,
15033 ) {
15034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15035
15036 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15037 self.select_syntax_node_history.pop()
15038 {
15039 if let Some(selection) = selections.last_mut() {
15040 selection.reversed = is_selection_reversed;
15041 }
15042
15043 self.select_syntax_node_history.disable_clearing = true;
15044 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15045 s.select(selections.to_vec());
15046 });
15047 self.select_syntax_node_history.disable_clearing = false;
15048
15049 match scroll_behavior {
15050 SelectSyntaxNodeScrollBehavior::CursorTop => {
15051 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15052 }
15053 SelectSyntaxNodeScrollBehavior::FitSelection => {
15054 self.request_autoscroll(Autoscroll::fit(), cx);
15055 }
15056 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15057 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15058 }
15059 }
15060 }
15061 }
15062
15063 pub fn unwrap_syntax_node(
15064 &mut self,
15065 _: &UnwrapSyntaxNode,
15066 window: &mut Window,
15067 cx: &mut Context<Self>,
15068 ) {
15069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15070
15071 let buffer = self.buffer.read(cx).snapshot(cx);
15072 let selections = self
15073 .selections
15074 .all::<usize>(cx)
15075 .into_iter()
15076 // subtracting the offset requires sorting
15077 .sorted_by_key(|i| i.start);
15078
15079 let full_edits = selections
15080 .into_iter()
15081 .filter_map(|selection| {
15082 // Only requires two branches once if-let-chains stabilize (#53667)
15083 let child = if !selection.is_empty() {
15084 selection.range()
15085 } else if let Some((_, ancestor_range)) =
15086 buffer.syntax_ancestor(selection.start..selection.end)
15087 {
15088 match ancestor_range {
15089 MultiOrSingleBufferOffsetRange::Single(range) => range,
15090 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15091 }
15092 } else {
15093 selection.range()
15094 };
15095
15096 let mut parent = child.clone();
15097 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15098 parent = match ancestor_range {
15099 MultiOrSingleBufferOffsetRange::Single(range) => range,
15100 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15101 };
15102 if parent.start < child.start || parent.end > child.end {
15103 break;
15104 }
15105 }
15106
15107 if parent == child {
15108 return None;
15109 }
15110 let text = buffer.text_for_range(child).collect::<String>();
15111 Some((selection.id, parent, text))
15112 })
15113 .collect::<Vec<_>>();
15114
15115 self.transact(window, cx, |this, window, cx| {
15116 this.buffer.update(cx, |buffer, cx| {
15117 buffer.edit(
15118 full_edits
15119 .iter()
15120 .map(|(_, p, t)| (p.clone(), t.clone()))
15121 .collect::<Vec<_>>(),
15122 None,
15123 cx,
15124 );
15125 });
15126 this.change_selections(Default::default(), window, cx, |s| {
15127 let mut offset = 0;
15128 let mut selections = vec![];
15129 for (id, parent, text) in full_edits {
15130 let start = parent.start - offset;
15131 offset += parent.len() - text.len();
15132 selections.push(Selection {
15133 id,
15134 start,
15135 end: start + text.len(),
15136 reversed: false,
15137 goal: Default::default(),
15138 });
15139 }
15140 s.select(selections);
15141 });
15142 });
15143 }
15144
15145 pub fn select_next_syntax_node(
15146 &mut self,
15147 _: &SelectNextSyntaxNode,
15148 window: &mut Window,
15149 cx: &mut Context<Self>,
15150 ) {
15151 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15152 if old_selections.is_empty() {
15153 return;
15154 }
15155
15156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15157
15158 let buffer = self.buffer.read(cx).snapshot(cx);
15159 let mut selected_sibling = false;
15160
15161 let new_selections = old_selections
15162 .iter()
15163 .map(|selection| {
15164 let old_range = selection.start..selection.end;
15165
15166 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15167 let new_range = node.byte_range();
15168 selected_sibling = true;
15169 Selection {
15170 id: selection.id,
15171 start: new_range.start,
15172 end: new_range.end,
15173 goal: SelectionGoal::None,
15174 reversed: selection.reversed,
15175 }
15176 } else {
15177 selection.clone()
15178 }
15179 })
15180 .collect::<Vec<_>>();
15181
15182 if selected_sibling {
15183 self.change_selections(
15184 SelectionEffects::scroll(Autoscroll::fit()),
15185 window,
15186 cx,
15187 |s| {
15188 s.select(new_selections);
15189 },
15190 );
15191 }
15192 }
15193
15194 pub fn select_prev_syntax_node(
15195 &mut self,
15196 _: &SelectPreviousSyntaxNode,
15197 window: &mut Window,
15198 cx: &mut Context<Self>,
15199 ) {
15200 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15201 if old_selections.is_empty() {
15202 return;
15203 }
15204
15205 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15206
15207 let buffer = self.buffer.read(cx).snapshot(cx);
15208 let mut selected_sibling = false;
15209
15210 let new_selections = old_selections
15211 .iter()
15212 .map(|selection| {
15213 let old_range = selection.start..selection.end;
15214
15215 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15216 let new_range = node.byte_range();
15217 selected_sibling = true;
15218 Selection {
15219 id: selection.id,
15220 start: new_range.start,
15221 end: new_range.end,
15222 goal: SelectionGoal::None,
15223 reversed: selection.reversed,
15224 }
15225 } else {
15226 selection.clone()
15227 }
15228 })
15229 .collect::<Vec<_>>();
15230
15231 if selected_sibling {
15232 self.change_selections(
15233 SelectionEffects::scroll(Autoscroll::fit()),
15234 window,
15235 cx,
15236 |s| {
15237 s.select(new_selections);
15238 },
15239 );
15240 }
15241 }
15242
15243 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15244 if !EditorSettings::get_global(cx).gutter.runnables {
15245 self.clear_tasks();
15246 return Task::ready(());
15247 }
15248 let project = self.project().map(Entity::downgrade);
15249 let task_sources = self.lsp_task_sources(cx);
15250 let multi_buffer = self.buffer.downgrade();
15251 cx.spawn_in(window, async move |editor, cx| {
15252 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15253 let Some(project) = project.and_then(|p| p.upgrade()) else {
15254 return;
15255 };
15256 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15257 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15258 }) else {
15259 return;
15260 };
15261
15262 let hide_runnables = project
15263 .update(cx, |project, _| project.is_via_collab())
15264 .unwrap_or(true);
15265 if hide_runnables {
15266 return;
15267 }
15268 let new_rows =
15269 cx.background_spawn({
15270 let snapshot = display_snapshot.clone();
15271 async move {
15272 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15273 }
15274 })
15275 .await;
15276 let Ok(lsp_tasks) =
15277 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15278 else {
15279 return;
15280 };
15281 let lsp_tasks = lsp_tasks.await;
15282
15283 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15284 lsp_tasks
15285 .into_iter()
15286 .flat_map(|(kind, tasks)| {
15287 tasks.into_iter().filter_map(move |(location, task)| {
15288 Some((kind.clone(), location?, task))
15289 })
15290 })
15291 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15292 let buffer = location.target.buffer;
15293 let buffer_snapshot = buffer.read(cx).snapshot();
15294 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15295 |(excerpt_id, snapshot, _)| {
15296 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15297 display_snapshot
15298 .buffer_snapshot
15299 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15300 } else {
15301 None
15302 }
15303 },
15304 );
15305 if let Some(offset) = offset {
15306 let task_buffer_range =
15307 location.target.range.to_point(&buffer_snapshot);
15308 let context_buffer_range =
15309 task_buffer_range.to_offset(&buffer_snapshot);
15310 let context_range = BufferOffset(context_buffer_range.start)
15311 ..BufferOffset(context_buffer_range.end);
15312
15313 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15314 .or_insert_with(|| RunnableTasks {
15315 templates: Vec::new(),
15316 offset,
15317 column: task_buffer_range.start.column,
15318 extra_variables: HashMap::default(),
15319 context_range,
15320 })
15321 .templates
15322 .push((kind, task.original_task().clone()));
15323 }
15324
15325 acc
15326 })
15327 }) else {
15328 return;
15329 };
15330
15331 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15332 buffer.language_settings(cx).tasks.prefer_lsp
15333 }) else {
15334 return;
15335 };
15336
15337 let rows = Self::runnable_rows(
15338 project,
15339 display_snapshot,
15340 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15341 new_rows,
15342 cx.clone(),
15343 )
15344 .await;
15345 editor
15346 .update(cx, |editor, _| {
15347 editor.clear_tasks();
15348 for (key, mut value) in rows {
15349 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15350 value.templates.extend(lsp_tasks.templates);
15351 }
15352
15353 editor.insert_tasks(key, value);
15354 }
15355 for (key, value) in lsp_tasks_by_rows {
15356 editor.insert_tasks(key, value);
15357 }
15358 })
15359 .ok();
15360 })
15361 }
15362 fn fetch_runnable_ranges(
15363 snapshot: &DisplaySnapshot,
15364 range: Range<Anchor>,
15365 ) -> Vec<language::RunnableRange> {
15366 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15367 }
15368
15369 fn runnable_rows(
15370 project: Entity<Project>,
15371 snapshot: DisplaySnapshot,
15372 prefer_lsp: bool,
15373 runnable_ranges: Vec<RunnableRange>,
15374 cx: AsyncWindowContext,
15375 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15376 cx.spawn(async move |cx| {
15377 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15378 for mut runnable in runnable_ranges {
15379 let Some(tasks) = cx
15380 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15381 .ok()
15382 else {
15383 continue;
15384 };
15385 let mut tasks = tasks.await;
15386
15387 if prefer_lsp {
15388 tasks.retain(|(task_kind, _)| {
15389 !matches!(task_kind, TaskSourceKind::Language { .. })
15390 });
15391 }
15392 if tasks.is_empty() {
15393 continue;
15394 }
15395
15396 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15397 let Some(row) = snapshot
15398 .buffer_snapshot
15399 .buffer_line_for_row(MultiBufferRow(point.row))
15400 .map(|(_, range)| range.start.row)
15401 else {
15402 continue;
15403 };
15404
15405 let context_range =
15406 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15407 runnable_rows.push((
15408 (runnable.buffer_id, row),
15409 RunnableTasks {
15410 templates: tasks,
15411 offset: snapshot
15412 .buffer_snapshot
15413 .anchor_before(runnable.run_range.start),
15414 context_range,
15415 column: point.column,
15416 extra_variables: runnable.extra_captures,
15417 },
15418 ));
15419 }
15420 runnable_rows
15421 })
15422 }
15423
15424 fn templates_with_tags(
15425 project: &Entity<Project>,
15426 runnable: &mut Runnable,
15427 cx: &mut App,
15428 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15429 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15430 let (worktree_id, file) = project
15431 .buffer_for_id(runnable.buffer, cx)
15432 .and_then(|buffer| buffer.read(cx).file())
15433 .map(|file| (file.worktree_id(cx), file.clone()))
15434 .unzip();
15435
15436 (
15437 project.task_store().read(cx).task_inventory().cloned(),
15438 worktree_id,
15439 file,
15440 )
15441 });
15442
15443 let tags = mem::take(&mut runnable.tags);
15444 let language = runnable.language.clone();
15445 cx.spawn(async move |cx| {
15446 let mut templates_with_tags = Vec::new();
15447 if let Some(inventory) = inventory {
15448 for RunnableTag(tag) in tags {
15449 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15450 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15451 }) else {
15452 return templates_with_tags;
15453 };
15454 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15455 move |(_, template)| {
15456 template.tags.iter().any(|source_tag| source_tag == &tag)
15457 },
15458 ));
15459 }
15460 }
15461 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15462
15463 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15464 // Strongest source wins; if we have worktree tag binding, prefer that to
15465 // global and language bindings;
15466 // if we have a global binding, prefer that to language binding.
15467 let first_mismatch = templates_with_tags
15468 .iter()
15469 .position(|(tag_source, _)| tag_source != leading_tag_source);
15470 if let Some(index) = first_mismatch {
15471 templates_with_tags.truncate(index);
15472 }
15473 }
15474
15475 templates_with_tags
15476 })
15477 }
15478
15479 pub fn move_to_enclosing_bracket(
15480 &mut self,
15481 _: &MoveToEnclosingBracket,
15482 window: &mut Window,
15483 cx: &mut Context<Self>,
15484 ) {
15485 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15486 self.change_selections(Default::default(), window, cx, |s| {
15487 s.move_offsets_with(|snapshot, selection| {
15488 let Some(enclosing_bracket_ranges) =
15489 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15490 else {
15491 return;
15492 };
15493
15494 let mut best_length = usize::MAX;
15495 let mut best_inside = false;
15496 let mut best_in_bracket_range = false;
15497 let mut best_destination = None;
15498 for (open, close) in enclosing_bracket_ranges {
15499 let close = close.to_inclusive();
15500 let length = close.end() - open.start;
15501 let inside = selection.start >= open.end && selection.end <= *close.start();
15502 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15503 || close.contains(&selection.head());
15504
15505 // If best is next to a bracket and current isn't, skip
15506 if !in_bracket_range && best_in_bracket_range {
15507 continue;
15508 }
15509
15510 // Prefer smaller lengths unless best is inside and current isn't
15511 if length > best_length && (best_inside || !inside) {
15512 continue;
15513 }
15514
15515 best_length = length;
15516 best_inside = inside;
15517 best_in_bracket_range = in_bracket_range;
15518 best_destination = Some(
15519 if close.contains(&selection.start) && close.contains(&selection.end) {
15520 if inside { open.end } else { open.start }
15521 } else if inside {
15522 *close.start()
15523 } else {
15524 *close.end()
15525 },
15526 );
15527 }
15528
15529 if let Some(destination) = best_destination {
15530 selection.collapse_to(destination, SelectionGoal::None);
15531 }
15532 })
15533 });
15534 }
15535
15536 pub fn undo_selection(
15537 &mut self,
15538 _: &UndoSelection,
15539 window: &mut Window,
15540 cx: &mut Context<Self>,
15541 ) {
15542 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15543 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15544 self.selection_history.mode = SelectionHistoryMode::Undoing;
15545 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15546 this.end_selection(window, cx);
15547 this.change_selections(
15548 SelectionEffects::scroll(Autoscroll::newest()),
15549 window,
15550 cx,
15551 |s| s.select_anchors(entry.selections.to_vec()),
15552 );
15553 });
15554 self.selection_history.mode = SelectionHistoryMode::Normal;
15555
15556 self.select_next_state = entry.select_next_state;
15557 self.select_prev_state = entry.select_prev_state;
15558 self.add_selections_state = entry.add_selections_state;
15559 }
15560 }
15561
15562 pub fn redo_selection(
15563 &mut self,
15564 _: &RedoSelection,
15565 window: &mut Window,
15566 cx: &mut Context<Self>,
15567 ) {
15568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15569 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15570 self.selection_history.mode = SelectionHistoryMode::Redoing;
15571 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15572 this.end_selection(window, cx);
15573 this.change_selections(
15574 SelectionEffects::scroll(Autoscroll::newest()),
15575 window,
15576 cx,
15577 |s| s.select_anchors(entry.selections.to_vec()),
15578 );
15579 });
15580 self.selection_history.mode = SelectionHistoryMode::Normal;
15581
15582 self.select_next_state = entry.select_next_state;
15583 self.select_prev_state = entry.select_prev_state;
15584 self.add_selections_state = entry.add_selections_state;
15585 }
15586 }
15587
15588 pub fn expand_excerpts(
15589 &mut self,
15590 action: &ExpandExcerpts,
15591 _: &mut Window,
15592 cx: &mut Context<Self>,
15593 ) {
15594 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15595 }
15596
15597 pub fn expand_excerpts_down(
15598 &mut self,
15599 action: &ExpandExcerptsDown,
15600 _: &mut Window,
15601 cx: &mut Context<Self>,
15602 ) {
15603 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15604 }
15605
15606 pub fn expand_excerpts_up(
15607 &mut self,
15608 action: &ExpandExcerptsUp,
15609 _: &mut Window,
15610 cx: &mut Context<Self>,
15611 ) {
15612 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15613 }
15614
15615 pub fn expand_excerpts_for_direction(
15616 &mut self,
15617 lines: u32,
15618 direction: ExpandExcerptDirection,
15619
15620 cx: &mut Context<Self>,
15621 ) {
15622 let selections = self.selections.disjoint_anchors();
15623
15624 let lines = if lines == 0 {
15625 EditorSettings::get_global(cx).expand_excerpt_lines
15626 } else {
15627 lines
15628 };
15629
15630 self.buffer.update(cx, |buffer, cx| {
15631 let snapshot = buffer.snapshot(cx);
15632 let mut excerpt_ids = selections
15633 .iter()
15634 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15635 .collect::<Vec<_>>();
15636 excerpt_ids.sort();
15637 excerpt_ids.dedup();
15638 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15639 })
15640 }
15641
15642 pub fn expand_excerpt(
15643 &mut self,
15644 excerpt: ExcerptId,
15645 direction: ExpandExcerptDirection,
15646 window: &mut Window,
15647 cx: &mut Context<Self>,
15648 ) {
15649 let current_scroll_position = self.scroll_position(cx);
15650 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15651 let mut should_scroll_up = false;
15652
15653 if direction == ExpandExcerptDirection::Down {
15654 let multi_buffer = self.buffer.read(cx);
15655 let snapshot = multi_buffer.snapshot(cx);
15656 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15657 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15658 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15659 {
15660 let buffer_snapshot = buffer.read(cx).snapshot();
15661 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15662 let last_row = buffer_snapshot.max_point().row;
15663 let lines_below = last_row.saturating_sub(excerpt_end_row);
15664 should_scroll_up = lines_below >= lines_to_expand;
15665 }
15666 }
15667
15668 self.buffer.update(cx, |buffer, cx| {
15669 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15670 });
15671
15672 if should_scroll_up {
15673 let new_scroll_position =
15674 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15675 self.set_scroll_position(new_scroll_position, window, cx);
15676 }
15677 }
15678
15679 pub fn go_to_singleton_buffer_point(
15680 &mut self,
15681 point: Point,
15682 window: &mut Window,
15683 cx: &mut Context<Self>,
15684 ) {
15685 self.go_to_singleton_buffer_range(point..point, window, cx);
15686 }
15687
15688 pub fn go_to_singleton_buffer_range(
15689 &mut self,
15690 range: Range<Point>,
15691 window: &mut Window,
15692 cx: &mut Context<Self>,
15693 ) {
15694 let multibuffer = self.buffer().read(cx);
15695 let Some(buffer) = multibuffer.as_singleton() else {
15696 return;
15697 };
15698 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15699 return;
15700 };
15701 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15702 return;
15703 };
15704 self.change_selections(
15705 SelectionEffects::default().nav_history(true),
15706 window,
15707 cx,
15708 |s| s.select_anchor_ranges([start..end]),
15709 );
15710 }
15711
15712 pub fn go_to_diagnostic(
15713 &mut self,
15714 action: &GoToDiagnostic,
15715 window: &mut Window,
15716 cx: &mut Context<Self>,
15717 ) {
15718 if !self.diagnostics_enabled() {
15719 return;
15720 }
15721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15722 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15723 }
15724
15725 pub fn go_to_prev_diagnostic(
15726 &mut self,
15727 action: &GoToPreviousDiagnostic,
15728 window: &mut Window,
15729 cx: &mut Context<Self>,
15730 ) {
15731 if !self.diagnostics_enabled() {
15732 return;
15733 }
15734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15735 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15736 }
15737
15738 pub fn go_to_diagnostic_impl(
15739 &mut self,
15740 direction: Direction,
15741 severity: GoToDiagnosticSeverityFilter,
15742 window: &mut Window,
15743 cx: &mut Context<Self>,
15744 ) {
15745 let buffer = self.buffer.read(cx).snapshot(cx);
15746 let selection = self.selections.newest::<usize>(cx);
15747
15748 let mut active_group_id = None;
15749 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15750 && active_group.active_range.start.to_offset(&buffer) == selection.start
15751 {
15752 active_group_id = Some(active_group.group_id);
15753 }
15754
15755 fn filtered(
15756 snapshot: EditorSnapshot,
15757 severity: GoToDiagnosticSeverityFilter,
15758 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15759 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15760 diagnostics
15761 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15762 .filter(|entry| entry.range.start != entry.range.end)
15763 .filter(|entry| !entry.diagnostic.is_unnecessary)
15764 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15765 }
15766
15767 let snapshot = self.snapshot(window, cx);
15768 let before = filtered(
15769 snapshot.clone(),
15770 severity,
15771 buffer
15772 .diagnostics_in_range(0..selection.start)
15773 .filter(|entry| entry.range.start <= selection.start),
15774 );
15775 let after = filtered(
15776 snapshot,
15777 severity,
15778 buffer
15779 .diagnostics_in_range(selection.start..buffer.len())
15780 .filter(|entry| entry.range.start >= selection.start),
15781 );
15782
15783 let mut found: Option<DiagnosticEntry<usize>> = None;
15784 if direction == Direction::Prev {
15785 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15786 {
15787 for diagnostic in prev_diagnostics.into_iter().rev() {
15788 if diagnostic.range.start != selection.start
15789 || active_group_id
15790 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15791 {
15792 found = Some(diagnostic);
15793 break 'outer;
15794 }
15795 }
15796 }
15797 } else {
15798 for diagnostic in after.chain(before) {
15799 if diagnostic.range.start != selection.start
15800 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15801 {
15802 found = Some(diagnostic);
15803 break;
15804 }
15805 }
15806 }
15807 let Some(next_diagnostic) = found else {
15808 return;
15809 };
15810
15811 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15812 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15813 return;
15814 };
15815 self.change_selections(Default::default(), window, cx, |s| {
15816 s.select_ranges(vec![
15817 next_diagnostic.range.start..next_diagnostic.range.start,
15818 ])
15819 });
15820 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15821 self.refresh_edit_prediction(false, true, window, cx);
15822 }
15823
15824 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15826 let snapshot = self.snapshot(window, cx);
15827 let selection = self.selections.newest::<Point>(cx);
15828 self.go_to_hunk_before_or_after_position(
15829 &snapshot,
15830 selection.head(),
15831 Direction::Next,
15832 window,
15833 cx,
15834 );
15835 }
15836
15837 pub fn go_to_hunk_before_or_after_position(
15838 &mut self,
15839 snapshot: &EditorSnapshot,
15840 position: Point,
15841 direction: Direction,
15842 window: &mut Window,
15843 cx: &mut Context<Editor>,
15844 ) {
15845 let row = if direction == Direction::Next {
15846 self.hunk_after_position(snapshot, position)
15847 .map(|hunk| hunk.row_range.start)
15848 } else {
15849 self.hunk_before_position(snapshot, position)
15850 };
15851
15852 if let Some(row) = row {
15853 let destination = Point::new(row.0, 0);
15854 let autoscroll = Autoscroll::center();
15855
15856 self.unfold_ranges(&[destination..destination], false, false, cx);
15857 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15858 s.select_ranges([destination..destination]);
15859 });
15860 }
15861 }
15862
15863 fn hunk_after_position(
15864 &mut self,
15865 snapshot: &EditorSnapshot,
15866 position: Point,
15867 ) -> Option<MultiBufferDiffHunk> {
15868 snapshot
15869 .buffer_snapshot
15870 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15871 .find(|hunk| hunk.row_range.start.0 > position.row)
15872 .or_else(|| {
15873 snapshot
15874 .buffer_snapshot
15875 .diff_hunks_in_range(Point::zero()..position)
15876 .find(|hunk| hunk.row_range.end.0 < position.row)
15877 })
15878 }
15879
15880 fn go_to_prev_hunk(
15881 &mut self,
15882 _: &GoToPreviousHunk,
15883 window: &mut Window,
15884 cx: &mut Context<Self>,
15885 ) {
15886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15887 let snapshot = self.snapshot(window, cx);
15888 let selection = self.selections.newest::<Point>(cx);
15889 self.go_to_hunk_before_or_after_position(
15890 &snapshot,
15891 selection.head(),
15892 Direction::Prev,
15893 window,
15894 cx,
15895 );
15896 }
15897
15898 fn hunk_before_position(
15899 &mut self,
15900 snapshot: &EditorSnapshot,
15901 position: Point,
15902 ) -> Option<MultiBufferRow> {
15903 snapshot
15904 .buffer_snapshot
15905 .diff_hunk_before(position)
15906 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15907 }
15908
15909 fn go_to_next_change(
15910 &mut self,
15911 _: &GoToNextChange,
15912 window: &mut Window,
15913 cx: &mut Context<Self>,
15914 ) {
15915 if let Some(selections) = self
15916 .change_list
15917 .next_change(1, Direction::Next)
15918 .map(|s| s.to_vec())
15919 {
15920 self.change_selections(Default::default(), window, cx, |s| {
15921 let map = s.display_map();
15922 s.select_display_ranges(selections.iter().map(|a| {
15923 let point = a.to_display_point(&map);
15924 point..point
15925 }))
15926 })
15927 }
15928 }
15929
15930 fn go_to_previous_change(
15931 &mut self,
15932 _: &GoToPreviousChange,
15933 window: &mut Window,
15934 cx: &mut Context<Self>,
15935 ) {
15936 if let Some(selections) = self
15937 .change_list
15938 .next_change(1, Direction::Prev)
15939 .map(|s| s.to_vec())
15940 {
15941 self.change_selections(Default::default(), window, cx, |s| {
15942 let map = s.display_map();
15943 s.select_display_ranges(selections.iter().map(|a| {
15944 let point = a.to_display_point(&map);
15945 point..point
15946 }))
15947 })
15948 }
15949 }
15950
15951 pub fn go_to_next_document_highlight(
15952 &mut self,
15953 _: &GoToNextDocumentHighlight,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
15958 }
15959
15960 pub fn go_to_prev_document_highlight(
15961 &mut self,
15962 _: &GoToPreviousDocumentHighlight,
15963 window: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) {
15966 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
15967 }
15968
15969 pub fn go_to_document_highlight_before_or_after_position(
15970 &mut self,
15971 direction: Direction,
15972 window: &mut Window,
15973 cx: &mut Context<Editor>,
15974 ) {
15975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15976 let snapshot = self.snapshot(window, cx);
15977 let buffer = &snapshot.buffer_snapshot;
15978 let position = self.selections.newest::<Point>(cx).head();
15979 let anchor_position = buffer.anchor_after(position);
15980
15981 // Get all document highlights (both read and write)
15982 let mut all_highlights = Vec::new();
15983
15984 if let Some((_, read_highlights)) = self
15985 .background_highlights
15986 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
15987 {
15988 all_highlights.extend(read_highlights.iter());
15989 }
15990
15991 if let Some((_, write_highlights)) = self
15992 .background_highlights
15993 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
15994 {
15995 all_highlights.extend(write_highlights.iter());
15996 }
15997
15998 if all_highlights.is_empty() {
15999 return;
16000 }
16001
16002 // Sort highlights by position
16003 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16004
16005 let target_highlight = match direction {
16006 Direction::Next => {
16007 // Find the first highlight after the current position
16008 all_highlights
16009 .iter()
16010 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16011 }
16012 Direction::Prev => {
16013 // Find the last highlight before the current position
16014 all_highlights
16015 .iter()
16016 .rev()
16017 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16018 }
16019 };
16020
16021 if let Some(highlight) = target_highlight {
16022 let destination = highlight.start.to_point(buffer);
16023 let autoscroll = Autoscroll::center();
16024
16025 self.unfold_ranges(&[destination..destination], false, false, cx);
16026 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16027 s.select_ranges([destination..destination]);
16028 });
16029 }
16030 }
16031
16032 fn go_to_line<T: 'static>(
16033 &mut self,
16034 position: Anchor,
16035 highlight_color: Option<Hsla>,
16036 window: &mut Window,
16037 cx: &mut Context<Self>,
16038 ) {
16039 let snapshot = self.snapshot(window, cx).display_snapshot;
16040 let position = position.to_point(&snapshot.buffer_snapshot);
16041 let start = snapshot
16042 .buffer_snapshot
16043 .clip_point(Point::new(position.row, 0), Bias::Left);
16044 let end = start + Point::new(1, 0);
16045 let start = snapshot.buffer_snapshot.anchor_before(start);
16046 let end = snapshot.buffer_snapshot.anchor_before(end);
16047
16048 self.highlight_rows::<T>(
16049 start..end,
16050 highlight_color
16051 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16052 Default::default(),
16053 cx,
16054 );
16055
16056 if self.buffer.read(cx).is_singleton() {
16057 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16058 }
16059 }
16060
16061 pub fn go_to_definition(
16062 &mut self,
16063 _: &GoToDefinition,
16064 window: &mut Window,
16065 cx: &mut Context<Self>,
16066 ) -> Task<Result<Navigated>> {
16067 let definition =
16068 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16069 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16070 cx.spawn_in(window, async move |editor, cx| {
16071 if definition.await? == Navigated::Yes {
16072 return Ok(Navigated::Yes);
16073 }
16074 match fallback_strategy {
16075 GoToDefinitionFallback::None => Ok(Navigated::No),
16076 GoToDefinitionFallback::FindAllReferences => {
16077 match editor.update_in(cx, |editor, window, cx| {
16078 editor.find_all_references(&FindAllReferences, window, cx)
16079 })? {
16080 Some(references) => references.await,
16081 None => Ok(Navigated::No),
16082 }
16083 }
16084 }
16085 })
16086 }
16087
16088 pub fn go_to_declaration(
16089 &mut self,
16090 _: &GoToDeclaration,
16091 window: &mut Window,
16092 cx: &mut Context<Self>,
16093 ) -> Task<Result<Navigated>> {
16094 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16095 }
16096
16097 pub fn go_to_declaration_split(
16098 &mut self,
16099 _: &GoToDeclaration,
16100 window: &mut Window,
16101 cx: &mut Context<Self>,
16102 ) -> Task<Result<Navigated>> {
16103 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16104 }
16105
16106 pub fn go_to_implementation(
16107 &mut self,
16108 _: &GoToImplementation,
16109 window: &mut Window,
16110 cx: &mut Context<Self>,
16111 ) -> Task<Result<Navigated>> {
16112 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16113 }
16114
16115 pub fn go_to_implementation_split(
16116 &mut self,
16117 _: &GoToImplementationSplit,
16118 window: &mut Window,
16119 cx: &mut Context<Self>,
16120 ) -> Task<Result<Navigated>> {
16121 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16122 }
16123
16124 pub fn go_to_type_definition(
16125 &mut self,
16126 _: &GoToTypeDefinition,
16127 window: &mut Window,
16128 cx: &mut Context<Self>,
16129 ) -> Task<Result<Navigated>> {
16130 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16131 }
16132
16133 pub fn go_to_definition_split(
16134 &mut self,
16135 _: &GoToDefinitionSplit,
16136 window: &mut Window,
16137 cx: &mut Context<Self>,
16138 ) -> Task<Result<Navigated>> {
16139 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16140 }
16141
16142 pub fn go_to_type_definition_split(
16143 &mut self,
16144 _: &GoToTypeDefinitionSplit,
16145 window: &mut Window,
16146 cx: &mut Context<Self>,
16147 ) -> Task<Result<Navigated>> {
16148 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16149 }
16150
16151 fn go_to_definition_of_kind(
16152 &mut self,
16153 kind: GotoDefinitionKind,
16154 split: bool,
16155 window: &mut Window,
16156 cx: &mut Context<Self>,
16157 ) -> Task<Result<Navigated>> {
16158 let Some(provider) = self.semantics_provider.clone() else {
16159 return Task::ready(Ok(Navigated::No));
16160 };
16161 let head = self.selections.newest::<usize>(cx).head();
16162 let buffer = self.buffer.read(cx);
16163 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16164 return Task::ready(Ok(Navigated::No));
16165 };
16166 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16167 return Task::ready(Ok(Navigated::No));
16168 };
16169
16170 cx.spawn_in(window, async move |editor, cx| {
16171 let Some(definitions) = definitions.await? else {
16172 return Ok(Navigated::No);
16173 };
16174 let navigated = editor
16175 .update_in(cx, |editor, window, cx| {
16176 editor.navigate_to_hover_links(
16177 Some(kind),
16178 definitions
16179 .into_iter()
16180 .filter(|location| {
16181 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16182 })
16183 .map(HoverLink::Text)
16184 .collect::<Vec<_>>(),
16185 split,
16186 window,
16187 cx,
16188 )
16189 })?
16190 .await?;
16191 anyhow::Ok(navigated)
16192 })
16193 }
16194
16195 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16196 let selection = self.selections.newest_anchor();
16197 let head = selection.head();
16198 let tail = selection.tail();
16199
16200 let Some((buffer, start_position)) =
16201 self.buffer.read(cx).text_anchor_for_position(head, cx)
16202 else {
16203 return;
16204 };
16205
16206 let end_position = if head != tail {
16207 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16208 return;
16209 };
16210 Some(pos)
16211 } else {
16212 None
16213 };
16214
16215 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16216 let url = if let Some(end_pos) = end_position {
16217 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16218 } else {
16219 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16220 };
16221
16222 if let Some(url) = url {
16223 editor.update(cx, |_, cx| {
16224 cx.open_url(&url);
16225 })
16226 } else {
16227 Ok(())
16228 }
16229 });
16230
16231 url_finder.detach();
16232 }
16233
16234 pub fn open_selected_filename(
16235 &mut self,
16236 _: &OpenSelectedFilename,
16237 window: &mut Window,
16238 cx: &mut Context<Self>,
16239 ) {
16240 let Some(workspace) = self.workspace() else {
16241 return;
16242 };
16243
16244 let position = self.selections.newest_anchor().head();
16245
16246 let Some((buffer, buffer_position)) =
16247 self.buffer.read(cx).text_anchor_for_position(position, cx)
16248 else {
16249 return;
16250 };
16251
16252 let project = self.project.clone();
16253
16254 cx.spawn_in(window, async move |_, cx| {
16255 let result = find_file(&buffer, project, buffer_position, cx).await;
16256
16257 if let Some((_, path)) = result {
16258 workspace
16259 .update_in(cx, |workspace, window, cx| {
16260 workspace.open_resolved_path(path, window, cx)
16261 })?
16262 .await?;
16263 }
16264 anyhow::Ok(())
16265 })
16266 .detach();
16267 }
16268
16269 pub(crate) fn navigate_to_hover_links(
16270 &mut self,
16271 kind: Option<GotoDefinitionKind>,
16272 definitions: Vec<HoverLink>,
16273 split: bool,
16274 window: &mut Window,
16275 cx: &mut Context<Editor>,
16276 ) -> Task<Result<Navigated>> {
16277 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16278 let mut first_url_or_file = None;
16279 let definitions: Vec<_> = definitions
16280 .into_iter()
16281 .filter_map(|def| match def {
16282 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16283 HoverLink::InlayHint(lsp_location, server_id) => {
16284 let computation =
16285 self.compute_target_location(lsp_location, server_id, window, cx);
16286 Some(cx.background_spawn(computation))
16287 }
16288 HoverLink::Url(url) => {
16289 first_url_or_file = Some(Either::Left(url));
16290 None
16291 }
16292 HoverLink::File(path) => {
16293 first_url_or_file = Some(Either::Right(path));
16294 None
16295 }
16296 })
16297 .collect();
16298
16299 let workspace = self.workspace();
16300
16301 cx.spawn_in(window, async move |editor, acx| {
16302 let mut locations: Vec<Location> = future::join_all(definitions)
16303 .await
16304 .into_iter()
16305 .filter_map(|location| location.transpose())
16306 .collect::<Result<_>>()
16307 .context("location tasks")?;
16308
16309 if locations.len() > 1 {
16310 let Some(workspace) = workspace else {
16311 return Ok(Navigated::No);
16312 };
16313
16314 let tab_kind = match kind {
16315 Some(GotoDefinitionKind::Implementation) => "Implementations",
16316 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16317 Some(GotoDefinitionKind::Declaration) => "Declarations",
16318 Some(GotoDefinitionKind::Type) => "Types",
16319 };
16320 let title = editor
16321 .update_in(acx, |_, _, cx| {
16322 let target = locations
16323 .iter()
16324 .map(|location| {
16325 location
16326 .buffer
16327 .read(cx)
16328 .text_for_range(location.range.clone())
16329 .collect::<String>()
16330 })
16331 .filter(|text| !text.contains('\n'))
16332 .unique()
16333 .take(3)
16334 .join(", ");
16335 if target.is_empty() {
16336 tab_kind.to_owned()
16337 } else {
16338 format!("{tab_kind} for {target}")
16339 }
16340 })
16341 .context("buffer title")?;
16342
16343 let opened = workspace
16344 .update_in(acx, |workspace, window, cx| {
16345 Self::open_locations_in_multibuffer(
16346 workspace,
16347 locations,
16348 title,
16349 split,
16350 MultibufferSelectionMode::First,
16351 window,
16352 cx,
16353 )
16354 })
16355 .is_ok();
16356
16357 anyhow::Ok(Navigated::from_bool(opened))
16358 } else if locations.is_empty() {
16359 // If there is one definition, just open it directly
16360 match first_url_or_file {
16361 Some(Either::Left(url)) => {
16362 acx.update(|_, cx| cx.open_url(&url))?;
16363 Ok(Navigated::Yes)
16364 }
16365 Some(Either::Right(path)) => {
16366 let Some(workspace) = workspace else {
16367 return Ok(Navigated::No);
16368 };
16369
16370 workspace
16371 .update_in(acx, |workspace, window, cx| {
16372 workspace.open_resolved_path(path, window, cx)
16373 })?
16374 .await?;
16375 Ok(Navigated::Yes)
16376 }
16377 None => Ok(Navigated::No),
16378 }
16379 } else {
16380 let Some(workspace) = workspace else {
16381 return Ok(Navigated::No);
16382 };
16383
16384 let target = locations.pop().unwrap();
16385 editor.update_in(acx, |editor, window, cx| {
16386 let pane = workspace.read(cx).active_pane().clone();
16387
16388 let range = target.range.to_point(target.buffer.read(cx));
16389 let range = editor.range_for_match(&range);
16390 let range = collapse_multiline_range(range);
16391
16392 if !split
16393 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16394 {
16395 editor.go_to_singleton_buffer_range(range, window, cx);
16396 } else {
16397 window.defer(cx, move |window, cx| {
16398 let target_editor: Entity<Self> =
16399 workspace.update(cx, |workspace, cx| {
16400 let pane = if split {
16401 workspace.adjacent_pane(window, cx)
16402 } else {
16403 workspace.active_pane().clone()
16404 };
16405
16406 workspace.open_project_item(
16407 pane,
16408 target.buffer.clone(),
16409 true,
16410 true,
16411 window,
16412 cx,
16413 )
16414 });
16415 target_editor.update(cx, |target_editor, cx| {
16416 // When selecting a definition in a different buffer, disable the nav history
16417 // to avoid creating a history entry at the previous cursor location.
16418 pane.update(cx, |pane, _| pane.disable_history());
16419 target_editor.go_to_singleton_buffer_range(range, window, cx);
16420 pane.update(cx, |pane, _| pane.enable_history());
16421 });
16422 });
16423 }
16424 Navigated::Yes
16425 })
16426 }
16427 })
16428 }
16429
16430 fn compute_target_location(
16431 &self,
16432 lsp_location: lsp::Location,
16433 server_id: LanguageServerId,
16434 window: &mut Window,
16435 cx: &mut Context<Self>,
16436 ) -> Task<anyhow::Result<Option<Location>>> {
16437 let Some(project) = self.project.clone() else {
16438 return Task::ready(Ok(None));
16439 };
16440
16441 cx.spawn_in(window, async move |editor, cx| {
16442 let location_task = editor.update(cx, |_, cx| {
16443 project.update(cx, |project, cx| {
16444 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16445 })
16446 })?;
16447 let location = Some({
16448 let target_buffer_handle = location_task.await.context("open local buffer")?;
16449 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16450 let target_start = target_buffer
16451 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16452 let target_end = target_buffer
16453 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16454 target_buffer.anchor_after(target_start)
16455 ..target_buffer.anchor_before(target_end)
16456 })?;
16457 Location {
16458 buffer: target_buffer_handle,
16459 range,
16460 }
16461 });
16462 Ok(location)
16463 })
16464 }
16465
16466 pub fn find_all_references(
16467 &mut self,
16468 _: &FindAllReferences,
16469 window: &mut Window,
16470 cx: &mut Context<Self>,
16471 ) -> Option<Task<Result<Navigated>>> {
16472 let selection = self.selections.newest::<usize>(cx);
16473 let multi_buffer = self.buffer.read(cx);
16474 let head = selection.head();
16475
16476 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16477 let head_anchor = multi_buffer_snapshot.anchor_at(
16478 head,
16479 if head < selection.tail() {
16480 Bias::Right
16481 } else {
16482 Bias::Left
16483 },
16484 );
16485
16486 match self
16487 .find_all_references_task_sources
16488 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16489 {
16490 Ok(_) => {
16491 log::info!(
16492 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16493 );
16494 return None;
16495 }
16496 Err(i) => {
16497 self.find_all_references_task_sources.insert(i, head_anchor);
16498 }
16499 }
16500
16501 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16502 let workspace = self.workspace()?;
16503 let project = workspace.read(cx).project().clone();
16504 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16505 Some(cx.spawn_in(window, async move |editor, cx| {
16506 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16507 if let Ok(i) = editor
16508 .find_all_references_task_sources
16509 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16510 {
16511 editor.find_all_references_task_sources.remove(i);
16512 }
16513 });
16514
16515 let Some(locations) = references.await? else {
16516 return anyhow::Ok(Navigated::No);
16517 };
16518 if locations.is_empty() {
16519 return anyhow::Ok(Navigated::No);
16520 }
16521
16522 workspace.update_in(cx, |workspace, window, cx| {
16523 let target = locations
16524 .iter()
16525 .map(|location| {
16526 location
16527 .buffer
16528 .read(cx)
16529 .text_for_range(location.range.clone())
16530 .collect::<String>()
16531 })
16532 .filter(|text| !text.contains('\n'))
16533 .unique()
16534 .take(3)
16535 .join(", ");
16536 let title = if target.is_empty() {
16537 "References".to_owned()
16538 } else {
16539 format!("References to {target}")
16540 };
16541 Self::open_locations_in_multibuffer(
16542 workspace,
16543 locations,
16544 title,
16545 false,
16546 MultibufferSelectionMode::First,
16547 window,
16548 cx,
16549 );
16550 Navigated::Yes
16551 })
16552 }))
16553 }
16554
16555 /// Opens a multibuffer with the given project locations in it
16556 pub fn open_locations_in_multibuffer(
16557 workspace: &mut Workspace,
16558 mut locations: Vec<Location>,
16559 title: String,
16560 split: bool,
16561 multibuffer_selection_mode: MultibufferSelectionMode,
16562 window: &mut Window,
16563 cx: &mut Context<Workspace>,
16564 ) {
16565 if locations.is_empty() {
16566 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16567 return;
16568 }
16569
16570 // If there are multiple definitions, open them in a multibuffer
16571 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16572 let mut locations = locations.into_iter().peekable();
16573 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16574 let capability = workspace.project().read(cx).capability();
16575
16576 let excerpt_buffer = cx.new(|cx| {
16577 let mut multibuffer = MultiBuffer::new(capability);
16578 while let Some(location) = locations.next() {
16579 let buffer = location.buffer.read(cx);
16580 let mut ranges_for_buffer = Vec::new();
16581 let range = location.range.to_point(buffer);
16582 ranges_for_buffer.push(range.clone());
16583
16584 while let Some(next_location) = locations.peek() {
16585 if next_location.buffer == location.buffer {
16586 ranges_for_buffer.push(next_location.range.to_point(buffer));
16587 locations.next();
16588 } else {
16589 break;
16590 }
16591 }
16592
16593 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16594 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16595 PathKey::for_buffer(&location.buffer, cx),
16596 location.buffer.clone(),
16597 ranges_for_buffer,
16598 multibuffer_context_lines(cx),
16599 cx,
16600 );
16601 ranges.extend(new_ranges)
16602 }
16603
16604 multibuffer.with_title(title)
16605 });
16606
16607 let editor = cx.new(|cx| {
16608 Editor::for_multibuffer(
16609 excerpt_buffer,
16610 Some(workspace.project().clone()),
16611 window,
16612 cx,
16613 )
16614 });
16615 editor.update(cx, |editor, cx| {
16616 match multibuffer_selection_mode {
16617 MultibufferSelectionMode::First => {
16618 if let Some(first_range) = ranges.first() {
16619 editor.change_selections(
16620 SelectionEffects::no_scroll(),
16621 window,
16622 cx,
16623 |selections| {
16624 selections.clear_disjoint();
16625 selections
16626 .select_anchor_ranges(std::iter::once(first_range.clone()));
16627 },
16628 );
16629 }
16630 editor.highlight_background::<Self>(
16631 &ranges,
16632 |theme| theme.colors().editor_highlighted_line_background,
16633 cx,
16634 );
16635 }
16636 MultibufferSelectionMode::All => {
16637 editor.change_selections(
16638 SelectionEffects::no_scroll(),
16639 window,
16640 cx,
16641 |selections| {
16642 selections.clear_disjoint();
16643 selections.select_anchor_ranges(ranges);
16644 },
16645 );
16646 }
16647 }
16648 editor.register_buffers_with_language_servers(cx);
16649 });
16650
16651 let item = Box::new(editor);
16652 let item_id = item.item_id();
16653
16654 if split {
16655 workspace.split_item(SplitDirection::Right, item, window, cx);
16656 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16657 let (preview_item_id, preview_item_idx) =
16658 workspace.active_pane().read_with(cx, |pane, _| {
16659 (pane.preview_item_id(), pane.preview_item_idx())
16660 });
16661
16662 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16663
16664 if let Some(preview_item_id) = preview_item_id {
16665 workspace.active_pane().update(cx, |pane, cx| {
16666 pane.remove_item(preview_item_id, false, false, window, cx);
16667 });
16668 }
16669 } else {
16670 workspace.add_item_to_active_pane(item, None, true, window, cx);
16671 }
16672 workspace.active_pane().update(cx, |pane, cx| {
16673 pane.set_preview_item_id(Some(item_id), cx);
16674 });
16675 }
16676
16677 pub fn rename(
16678 &mut self,
16679 _: &Rename,
16680 window: &mut Window,
16681 cx: &mut Context<Self>,
16682 ) -> Option<Task<Result<()>>> {
16683 use language::ToOffset as _;
16684
16685 let provider = self.semantics_provider.clone()?;
16686 let selection = self.selections.newest_anchor().clone();
16687 let (cursor_buffer, cursor_buffer_position) = self
16688 .buffer
16689 .read(cx)
16690 .text_anchor_for_position(selection.head(), cx)?;
16691 let (tail_buffer, cursor_buffer_position_end) = self
16692 .buffer
16693 .read(cx)
16694 .text_anchor_for_position(selection.tail(), cx)?;
16695 if tail_buffer != cursor_buffer {
16696 return None;
16697 }
16698
16699 let snapshot = cursor_buffer.read(cx).snapshot();
16700 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16701 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16702 let prepare_rename = provider
16703 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16704 .unwrap_or_else(|| Task::ready(Ok(None)));
16705 drop(snapshot);
16706
16707 Some(cx.spawn_in(window, async move |this, cx| {
16708 let rename_range = if let Some(range) = prepare_rename.await? {
16709 Some(range)
16710 } else {
16711 this.update(cx, |this, cx| {
16712 let buffer = this.buffer.read(cx).snapshot(cx);
16713 let mut buffer_highlights = this
16714 .document_highlights_for_position(selection.head(), &buffer)
16715 .filter(|highlight| {
16716 highlight.start.excerpt_id == selection.head().excerpt_id
16717 && highlight.end.excerpt_id == selection.head().excerpt_id
16718 });
16719 buffer_highlights
16720 .next()
16721 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16722 })?
16723 };
16724 if let Some(rename_range) = rename_range {
16725 this.update_in(cx, |this, window, cx| {
16726 let snapshot = cursor_buffer.read(cx).snapshot();
16727 let rename_buffer_range = rename_range.to_offset(&snapshot);
16728 let cursor_offset_in_rename_range =
16729 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16730 let cursor_offset_in_rename_range_end =
16731 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16732
16733 this.take_rename(false, window, cx);
16734 let buffer = this.buffer.read(cx).read(cx);
16735 let cursor_offset = selection.head().to_offset(&buffer);
16736 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16737 let rename_end = rename_start + rename_buffer_range.len();
16738 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16739 let mut old_highlight_id = None;
16740 let old_name: Arc<str> = buffer
16741 .chunks(rename_start..rename_end, true)
16742 .map(|chunk| {
16743 if old_highlight_id.is_none() {
16744 old_highlight_id = chunk.syntax_highlight_id;
16745 }
16746 chunk.text
16747 })
16748 .collect::<String>()
16749 .into();
16750
16751 drop(buffer);
16752
16753 // Position the selection in the rename editor so that it matches the current selection.
16754 this.show_local_selections = false;
16755 let rename_editor = cx.new(|cx| {
16756 let mut editor = Editor::single_line(window, cx);
16757 editor.buffer.update(cx, |buffer, cx| {
16758 buffer.edit([(0..0, old_name.clone())], None, cx)
16759 });
16760 let rename_selection_range = match cursor_offset_in_rename_range
16761 .cmp(&cursor_offset_in_rename_range_end)
16762 {
16763 Ordering::Equal => {
16764 editor.select_all(&SelectAll, window, cx);
16765 return editor;
16766 }
16767 Ordering::Less => {
16768 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16769 }
16770 Ordering::Greater => {
16771 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16772 }
16773 };
16774 if rename_selection_range.end > old_name.len() {
16775 editor.select_all(&SelectAll, window, cx);
16776 } else {
16777 editor.change_selections(Default::default(), window, cx, |s| {
16778 s.select_ranges([rename_selection_range]);
16779 });
16780 }
16781 editor
16782 });
16783 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16784 if e == &EditorEvent::Focused {
16785 cx.emit(EditorEvent::FocusedIn)
16786 }
16787 })
16788 .detach();
16789
16790 let write_highlights =
16791 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16792 let read_highlights =
16793 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16794 let ranges = write_highlights
16795 .iter()
16796 .flat_map(|(_, ranges)| ranges.iter())
16797 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16798 .cloned()
16799 .collect();
16800
16801 this.highlight_text::<Rename>(
16802 ranges,
16803 HighlightStyle {
16804 fade_out: Some(0.6),
16805 ..Default::default()
16806 },
16807 cx,
16808 );
16809 let rename_focus_handle = rename_editor.focus_handle(cx);
16810 window.focus(&rename_focus_handle);
16811 let block_id = this.insert_blocks(
16812 [BlockProperties {
16813 style: BlockStyle::Flex,
16814 placement: BlockPlacement::Below(range.start),
16815 height: Some(1),
16816 render: Arc::new({
16817 let rename_editor = rename_editor.clone();
16818 move |cx: &mut BlockContext| {
16819 let mut text_style = cx.editor_style.text.clone();
16820 if let Some(highlight_style) = old_highlight_id
16821 .and_then(|h| h.style(&cx.editor_style.syntax))
16822 {
16823 text_style = text_style.highlight(highlight_style);
16824 }
16825 div()
16826 .block_mouse_except_scroll()
16827 .pl(cx.anchor_x)
16828 .child(EditorElement::new(
16829 &rename_editor,
16830 EditorStyle {
16831 background: cx.theme().system().transparent,
16832 local_player: cx.editor_style.local_player,
16833 text: text_style,
16834 scrollbar_width: cx.editor_style.scrollbar_width,
16835 syntax: cx.editor_style.syntax.clone(),
16836 status: cx.editor_style.status.clone(),
16837 inlay_hints_style: HighlightStyle {
16838 font_weight: Some(FontWeight::BOLD),
16839 ..make_inlay_hints_style(cx.app)
16840 },
16841 edit_prediction_styles: make_suggestion_styles(
16842 cx.app,
16843 ),
16844 ..EditorStyle::default()
16845 },
16846 ))
16847 .into_any_element()
16848 }
16849 }),
16850 priority: 0,
16851 }],
16852 Some(Autoscroll::fit()),
16853 cx,
16854 )[0];
16855 this.pending_rename = Some(RenameState {
16856 range,
16857 old_name,
16858 editor: rename_editor,
16859 block_id,
16860 });
16861 })?;
16862 }
16863
16864 Ok(())
16865 }))
16866 }
16867
16868 pub fn confirm_rename(
16869 &mut self,
16870 _: &ConfirmRename,
16871 window: &mut Window,
16872 cx: &mut Context<Self>,
16873 ) -> Option<Task<Result<()>>> {
16874 let rename = self.take_rename(false, window, cx)?;
16875 let workspace = self.workspace()?.downgrade();
16876 let (buffer, start) = self
16877 .buffer
16878 .read(cx)
16879 .text_anchor_for_position(rename.range.start, cx)?;
16880 let (end_buffer, _) = self
16881 .buffer
16882 .read(cx)
16883 .text_anchor_for_position(rename.range.end, cx)?;
16884 if buffer != end_buffer {
16885 return None;
16886 }
16887
16888 let old_name = rename.old_name;
16889 let new_name = rename.editor.read(cx).text(cx);
16890
16891 let rename = self.semantics_provider.as_ref()?.perform_rename(
16892 &buffer,
16893 start,
16894 new_name.clone(),
16895 cx,
16896 )?;
16897
16898 Some(cx.spawn_in(window, async move |editor, cx| {
16899 let project_transaction = rename.await?;
16900 Self::open_project_transaction(
16901 &editor,
16902 workspace,
16903 project_transaction,
16904 format!("Rename: {} → {}", old_name, new_name),
16905 cx,
16906 )
16907 .await?;
16908
16909 editor.update(cx, |editor, cx| {
16910 editor.refresh_document_highlights(cx);
16911 })?;
16912 Ok(())
16913 }))
16914 }
16915
16916 fn take_rename(
16917 &mut self,
16918 moving_cursor: bool,
16919 window: &mut Window,
16920 cx: &mut Context<Self>,
16921 ) -> Option<RenameState> {
16922 let rename = self.pending_rename.take()?;
16923 if rename.editor.focus_handle(cx).is_focused(window) {
16924 window.focus(&self.focus_handle);
16925 }
16926
16927 self.remove_blocks(
16928 [rename.block_id].into_iter().collect(),
16929 Some(Autoscroll::fit()),
16930 cx,
16931 );
16932 self.clear_highlights::<Rename>(cx);
16933 self.show_local_selections = true;
16934
16935 if moving_cursor {
16936 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16937 editor.selections.newest::<usize>(cx).head()
16938 });
16939
16940 // Update the selection to match the position of the selection inside
16941 // the rename editor.
16942 let snapshot = self.buffer.read(cx).read(cx);
16943 let rename_range = rename.range.to_offset(&snapshot);
16944 let cursor_in_editor = snapshot
16945 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16946 .min(rename_range.end);
16947 drop(snapshot);
16948
16949 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16950 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16951 });
16952 } else {
16953 self.refresh_document_highlights(cx);
16954 }
16955
16956 Some(rename)
16957 }
16958
16959 pub fn pending_rename(&self) -> Option<&RenameState> {
16960 self.pending_rename.as_ref()
16961 }
16962
16963 fn format(
16964 &mut self,
16965 _: &Format,
16966 window: &mut Window,
16967 cx: &mut Context<Self>,
16968 ) -> Option<Task<Result<()>>> {
16969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16970
16971 let project = match &self.project {
16972 Some(project) => project.clone(),
16973 None => return None,
16974 };
16975
16976 Some(self.perform_format(
16977 project,
16978 FormatTrigger::Manual,
16979 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16980 window,
16981 cx,
16982 ))
16983 }
16984
16985 fn format_selections(
16986 &mut self,
16987 _: &FormatSelections,
16988 window: &mut Window,
16989 cx: &mut Context<Self>,
16990 ) -> Option<Task<Result<()>>> {
16991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16992
16993 let project = match &self.project {
16994 Some(project) => project.clone(),
16995 None => return None,
16996 };
16997
16998 let ranges = self
16999 .selections
17000 .all_adjusted(cx)
17001 .into_iter()
17002 .map(|selection| selection.range())
17003 .collect_vec();
17004
17005 Some(self.perform_format(
17006 project,
17007 FormatTrigger::Manual,
17008 FormatTarget::Ranges(ranges),
17009 window,
17010 cx,
17011 ))
17012 }
17013
17014 fn perform_format(
17015 &mut self,
17016 project: Entity<Project>,
17017 trigger: FormatTrigger,
17018 target: FormatTarget,
17019 window: &mut Window,
17020 cx: &mut Context<Self>,
17021 ) -> Task<Result<()>> {
17022 let buffer = self.buffer.clone();
17023 let (buffers, target) = match target {
17024 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17025 FormatTarget::Ranges(selection_ranges) => {
17026 let multi_buffer = buffer.read(cx);
17027 let snapshot = multi_buffer.read(cx);
17028 let mut buffers = HashSet::default();
17029 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17030 BTreeMap::new();
17031 for selection_range in selection_ranges {
17032 for (buffer, buffer_range, _) in
17033 snapshot.range_to_buffer_ranges(selection_range)
17034 {
17035 let buffer_id = buffer.remote_id();
17036 let start = buffer.anchor_before(buffer_range.start);
17037 let end = buffer.anchor_after(buffer_range.end);
17038 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17039 buffer_id_to_ranges
17040 .entry(buffer_id)
17041 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17042 .or_insert_with(|| vec![start..end]);
17043 }
17044 }
17045 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17046 }
17047 };
17048
17049 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17050 let selections_prev = transaction_id_prev
17051 .and_then(|transaction_id_prev| {
17052 // default to selections as they were after the last edit, if we have them,
17053 // instead of how they are now.
17054 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17055 // will take you back to where you made the last edit, instead of staying where you scrolled
17056 self.selection_history
17057 .transaction(transaction_id_prev)
17058 .map(|t| t.0.clone())
17059 })
17060 .unwrap_or_else(|| self.selections.disjoint_anchors());
17061
17062 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17063 let format = project.update(cx, |project, cx| {
17064 project.format(buffers, target, true, trigger, cx)
17065 });
17066
17067 cx.spawn_in(window, async move |editor, cx| {
17068 let transaction = futures::select_biased! {
17069 transaction = format.log_err().fuse() => transaction,
17070 () = timeout => {
17071 log::warn!("timed out waiting for formatting");
17072 None
17073 }
17074 };
17075
17076 buffer
17077 .update(cx, |buffer, cx| {
17078 if let Some(transaction) = transaction
17079 && !buffer.is_singleton()
17080 {
17081 buffer.push_transaction(&transaction.0, cx);
17082 }
17083 cx.notify();
17084 })
17085 .ok();
17086
17087 if let Some(transaction_id_now) =
17088 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17089 {
17090 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17091 if has_new_transaction {
17092 _ = editor.update(cx, |editor, _| {
17093 editor
17094 .selection_history
17095 .insert_transaction(transaction_id_now, selections_prev);
17096 });
17097 }
17098 }
17099
17100 Ok(())
17101 })
17102 }
17103
17104 fn organize_imports(
17105 &mut self,
17106 _: &OrganizeImports,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) -> Option<Task<Result<()>>> {
17110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17111 let project = match &self.project {
17112 Some(project) => project.clone(),
17113 None => return None,
17114 };
17115 Some(self.perform_code_action_kind(
17116 project,
17117 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17118 window,
17119 cx,
17120 ))
17121 }
17122
17123 fn perform_code_action_kind(
17124 &mut self,
17125 project: Entity<Project>,
17126 kind: CodeActionKind,
17127 window: &mut Window,
17128 cx: &mut Context<Self>,
17129 ) -> Task<Result<()>> {
17130 let buffer = self.buffer.clone();
17131 let buffers = buffer.read(cx).all_buffers();
17132 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17133 let apply_action = project.update(cx, |project, cx| {
17134 project.apply_code_action_kind(buffers, kind, true, cx)
17135 });
17136 cx.spawn_in(window, async move |_, cx| {
17137 let transaction = futures::select_biased! {
17138 () = timeout => {
17139 log::warn!("timed out waiting for executing code action");
17140 None
17141 }
17142 transaction = apply_action.log_err().fuse() => transaction,
17143 };
17144 buffer
17145 .update(cx, |buffer, cx| {
17146 // check if we need this
17147 if let Some(transaction) = transaction
17148 && !buffer.is_singleton()
17149 {
17150 buffer.push_transaction(&transaction.0, cx);
17151 }
17152 cx.notify();
17153 })
17154 .ok();
17155 Ok(())
17156 })
17157 }
17158
17159 pub fn restart_language_server(
17160 &mut self,
17161 _: &RestartLanguageServer,
17162 _: &mut Window,
17163 cx: &mut Context<Self>,
17164 ) {
17165 if let Some(project) = self.project.clone() {
17166 self.buffer.update(cx, |multi_buffer, cx| {
17167 project.update(cx, |project, cx| {
17168 project.restart_language_servers_for_buffers(
17169 multi_buffer.all_buffers().into_iter().collect(),
17170 HashSet::default(),
17171 cx,
17172 );
17173 });
17174 })
17175 }
17176 }
17177
17178 pub fn stop_language_server(
17179 &mut self,
17180 _: &StopLanguageServer,
17181 _: &mut Window,
17182 cx: &mut Context<Self>,
17183 ) {
17184 if let Some(project) = self.project.clone() {
17185 self.buffer.update(cx, |multi_buffer, cx| {
17186 project.update(cx, |project, cx| {
17187 project.stop_language_servers_for_buffers(
17188 multi_buffer.all_buffers().into_iter().collect(),
17189 HashSet::default(),
17190 cx,
17191 );
17192 cx.emit(project::Event::RefreshInlayHints);
17193 });
17194 });
17195 }
17196 }
17197
17198 fn cancel_language_server_work(
17199 workspace: &mut Workspace,
17200 _: &actions::CancelLanguageServerWork,
17201 _: &mut Window,
17202 cx: &mut Context<Workspace>,
17203 ) {
17204 let project = workspace.project();
17205 let buffers = workspace
17206 .active_item(cx)
17207 .and_then(|item| item.act_as::<Editor>(cx))
17208 .map_or(HashSet::default(), |editor| {
17209 editor.read(cx).buffer.read(cx).all_buffers()
17210 });
17211 project.update(cx, |project, cx| {
17212 project.cancel_language_server_work_for_buffers(buffers, cx);
17213 });
17214 }
17215
17216 fn show_character_palette(
17217 &mut self,
17218 _: &ShowCharacterPalette,
17219 window: &mut Window,
17220 _: &mut Context<Self>,
17221 ) {
17222 window.show_character_palette();
17223 }
17224
17225 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17226 if !self.diagnostics_enabled() {
17227 return;
17228 }
17229
17230 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17231 let buffer = self.buffer.read(cx).snapshot(cx);
17232 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17233 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17234 let is_valid = buffer
17235 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17236 .any(|entry| {
17237 entry.diagnostic.is_primary
17238 && !entry.range.is_empty()
17239 && entry.range.start == primary_range_start
17240 && entry.diagnostic.message == active_diagnostics.active_message
17241 });
17242
17243 if !is_valid {
17244 self.dismiss_diagnostics(cx);
17245 }
17246 }
17247 }
17248
17249 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17250 match &self.active_diagnostics {
17251 ActiveDiagnostic::Group(group) => Some(group),
17252 _ => None,
17253 }
17254 }
17255
17256 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17257 if !self.diagnostics_enabled() {
17258 return;
17259 }
17260 self.dismiss_diagnostics(cx);
17261 self.active_diagnostics = ActiveDiagnostic::All;
17262 }
17263
17264 fn activate_diagnostics(
17265 &mut self,
17266 buffer_id: BufferId,
17267 diagnostic: DiagnosticEntry<usize>,
17268 window: &mut Window,
17269 cx: &mut Context<Self>,
17270 ) {
17271 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17272 return;
17273 }
17274 self.dismiss_diagnostics(cx);
17275 let snapshot = self.snapshot(window, cx);
17276 let buffer = self.buffer.read(cx).snapshot(cx);
17277 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17278 return;
17279 };
17280
17281 let diagnostic_group = buffer
17282 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17283 .collect::<Vec<_>>();
17284
17285 let blocks =
17286 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17287
17288 let blocks = self.display_map.update(cx, |display_map, cx| {
17289 display_map.insert_blocks(blocks, cx).into_iter().collect()
17290 });
17291 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17292 active_range: buffer.anchor_before(diagnostic.range.start)
17293 ..buffer.anchor_after(diagnostic.range.end),
17294 active_message: diagnostic.diagnostic.message.clone(),
17295 group_id: diagnostic.diagnostic.group_id,
17296 blocks,
17297 });
17298 cx.notify();
17299 }
17300
17301 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17302 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17303 return;
17304 };
17305
17306 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17307 if let ActiveDiagnostic::Group(group) = prev {
17308 self.display_map.update(cx, |display_map, cx| {
17309 display_map.remove_blocks(group.blocks, cx);
17310 });
17311 cx.notify();
17312 }
17313 }
17314
17315 /// Disable inline diagnostics rendering for this editor.
17316 pub fn disable_inline_diagnostics(&mut self) {
17317 self.inline_diagnostics_enabled = false;
17318 self.inline_diagnostics_update = Task::ready(());
17319 self.inline_diagnostics.clear();
17320 }
17321
17322 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17323 self.diagnostics_enabled = false;
17324 self.dismiss_diagnostics(cx);
17325 self.inline_diagnostics_update = Task::ready(());
17326 self.inline_diagnostics.clear();
17327 }
17328
17329 pub fn disable_word_completions(&mut self) {
17330 self.word_completions_enabled = false;
17331 }
17332
17333 pub fn diagnostics_enabled(&self) -> bool {
17334 self.diagnostics_enabled && self.mode.is_full()
17335 }
17336
17337 pub fn inline_diagnostics_enabled(&self) -> bool {
17338 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17339 }
17340
17341 pub fn show_inline_diagnostics(&self) -> bool {
17342 self.show_inline_diagnostics
17343 }
17344
17345 pub fn toggle_inline_diagnostics(
17346 &mut self,
17347 _: &ToggleInlineDiagnostics,
17348 window: &mut Window,
17349 cx: &mut Context<Editor>,
17350 ) {
17351 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17352 self.refresh_inline_diagnostics(false, window, cx);
17353 }
17354
17355 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17356 self.diagnostics_max_severity = severity;
17357 self.display_map.update(cx, |display_map, _| {
17358 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17359 });
17360 }
17361
17362 pub fn toggle_diagnostics(
17363 &mut self,
17364 _: &ToggleDiagnostics,
17365 window: &mut Window,
17366 cx: &mut Context<Editor>,
17367 ) {
17368 if !self.diagnostics_enabled() {
17369 return;
17370 }
17371
17372 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17373 EditorSettings::get_global(cx)
17374 .diagnostics_max_severity
17375 .filter(|severity| severity != &DiagnosticSeverity::Off)
17376 .unwrap_or(DiagnosticSeverity::Hint)
17377 } else {
17378 DiagnosticSeverity::Off
17379 };
17380 self.set_max_diagnostics_severity(new_severity, cx);
17381 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17382 self.active_diagnostics = ActiveDiagnostic::None;
17383 self.inline_diagnostics_update = Task::ready(());
17384 self.inline_diagnostics.clear();
17385 } else {
17386 self.refresh_inline_diagnostics(false, window, cx);
17387 }
17388
17389 cx.notify();
17390 }
17391
17392 pub fn toggle_minimap(
17393 &mut self,
17394 _: &ToggleMinimap,
17395 window: &mut Window,
17396 cx: &mut Context<Editor>,
17397 ) {
17398 if self.supports_minimap(cx) {
17399 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17400 }
17401 }
17402
17403 fn refresh_inline_diagnostics(
17404 &mut self,
17405 debounce: bool,
17406 window: &mut Window,
17407 cx: &mut Context<Self>,
17408 ) {
17409 let max_severity = ProjectSettings::get_global(cx)
17410 .diagnostics
17411 .inline
17412 .max_severity
17413 .unwrap_or(self.diagnostics_max_severity);
17414
17415 if !self.inline_diagnostics_enabled()
17416 || !self.show_inline_diagnostics
17417 || max_severity == DiagnosticSeverity::Off
17418 {
17419 self.inline_diagnostics_update = Task::ready(());
17420 self.inline_diagnostics.clear();
17421 return;
17422 }
17423
17424 let debounce_ms = ProjectSettings::get_global(cx)
17425 .diagnostics
17426 .inline
17427 .update_debounce_ms;
17428 let debounce = if debounce && debounce_ms > 0 {
17429 Some(Duration::from_millis(debounce_ms))
17430 } else {
17431 None
17432 };
17433 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17434 if let Some(debounce) = debounce {
17435 cx.background_executor().timer(debounce).await;
17436 }
17437 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17438 editor
17439 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17440 .ok()
17441 }) else {
17442 return;
17443 };
17444
17445 let new_inline_diagnostics = cx
17446 .background_spawn(async move {
17447 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17448 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17449 let message = diagnostic_entry
17450 .diagnostic
17451 .message
17452 .split_once('\n')
17453 .map(|(line, _)| line)
17454 .map(SharedString::new)
17455 .unwrap_or_else(|| {
17456 SharedString::from(diagnostic_entry.diagnostic.message)
17457 });
17458 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17459 let (Ok(i) | Err(i)) = inline_diagnostics
17460 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17461 inline_diagnostics.insert(
17462 i,
17463 (
17464 start_anchor,
17465 InlineDiagnostic {
17466 message,
17467 group_id: diagnostic_entry.diagnostic.group_id,
17468 start: diagnostic_entry.range.start.to_point(&snapshot),
17469 is_primary: diagnostic_entry.diagnostic.is_primary,
17470 severity: diagnostic_entry.diagnostic.severity,
17471 },
17472 ),
17473 );
17474 }
17475 inline_diagnostics
17476 })
17477 .await;
17478
17479 editor
17480 .update(cx, |editor, cx| {
17481 editor.inline_diagnostics = new_inline_diagnostics;
17482 cx.notify();
17483 })
17484 .ok();
17485 });
17486 }
17487
17488 fn pull_diagnostics(
17489 &mut self,
17490 buffer_id: Option<BufferId>,
17491 window: &Window,
17492 cx: &mut Context<Self>,
17493 ) -> Option<()> {
17494 if !self.mode().is_full() {
17495 return None;
17496 }
17497 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17498 .diagnostics
17499 .lsp_pull_diagnostics;
17500 if !pull_diagnostics_settings.enabled {
17501 return None;
17502 }
17503 let project = self.project()?.downgrade();
17504 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17505 let mut buffers = self.buffer.read(cx).all_buffers();
17506 if let Some(buffer_id) = buffer_id {
17507 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17508 }
17509
17510 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17511 cx.background_executor().timer(debounce).await;
17512
17513 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17514 buffers
17515 .into_iter()
17516 .filter_map(|buffer| {
17517 project
17518 .update(cx, |project, cx| {
17519 project.lsp_store().update(cx, |lsp_store, cx| {
17520 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17521 })
17522 })
17523 .ok()
17524 })
17525 .collect::<FuturesUnordered<_>>()
17526 }) else {
17527 return;
17528 };
17529
17530 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17531 match pull_task {
17532 Ok(()) => {
17533 if editor
17534 .update_in(cx, |editor, window, cx| {
17535 editor.update_diagnostics_state(window, cx);
17536 })
17537 .is_err()
17538 {
17539 return;
17540 }
17541 }
17542 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17543 }
17544 }
17545 });
17546
17547 Some(())
17548 }
17549
17550 pub fn set_selections_from_remote(
17551 &mut self,
17552 selections: Vec<Selection<Anchor>>,
17553 pending_selection: Option<Selection<Anchor>>,
17554 window: &mut Window,
17555 cx: &mut Context<Self>,
17556 ) {
17557 let old_cursor_position = self.selections.newest_anchor().head();
17558 self.selections.change_with(cx, |s| {
17559 s.select_anchors(selections);
17560 if let Some(pending_selection) = pending_selection {
17561 s.set_pending(pending_selection, SelectMode::Character);
17562 } else {
17563 s.clear_pending();
17564 }
17565 });
17566 self.selections_did_change(
17567 false,
17568 &old_cursor_position,
17569 SelectionEffects::default(),
17570 window,
17571 cx,
17572 );
17573 }
17574
17575 pub fn transact(
17576 &mut self,
17577 window: &mut Window,
17578 cx: &mut Context<Self>,
17579 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17580 ) -> Option<TransactionId> {
17581 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17582 this.start_transaction_at(Instant::now(), window, cx);
17583 update(this, window, cx);
17584 this.end_transaction_at(Instant::now(), cx)
17585 })
17586 }
17587
17588 pub fn start_transaction_at(
17589 &mut self,
17590 now: Instant,
17591 window: &mut Window,
17592 cx: &mut Context<Self>,
17593 ) -> Option<TransactionId> {
17594 self.end_selection(window, cx);
17595 if let Some(tx_id) = self
17596 .buffer
17597 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17598 {
17599 self.selection_history
17600 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17601 cx.emit(EditorEvent::TransactionBegun {
17602 transaction_id: tx_id,
17603 });
17604 Some(tx_id)
17605 } else {
17606 None
17607 }
17608 }
17609
17610 pub fn end_transaction_at(
17611 &mut self,
17612 now: Instant,
17613 cx: &mut Context<Self>,
17614 ) -> Option<TransactionId> {
17615 if let Some(transaction_id) = self
17616 .buffer
17617 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17618 {
17619 if let Some((_, end_selections)) =
17620 self.selection_history.transaction_mut(transaction_id)
17621 {
17622 *end_selections = Some(self.selections.disjoint_anchors());
17623 } else {
17624 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17625 }
17626
17627 cx.emit(EditorEvent::Edited { transaction_id });
17628 Some(transaction_id)
17629 } else {
17630 None
17631 }
17632 }
17633
17634 pub fn modify_transaction_selection_history(
17635 &mut self,
17636 transaction_id: TransactionId,
17637 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17638 ) -> bool {
17639 self.selection_history
17640 .transaction_mut(transaction_id)
17641 .map(modify)
17642 .is_some()
17643 }
17644
17645 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17646 if self.selection_mark_mode {
17647 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17648 s.move_with(|_, sel| {
17649 sel.collapse_to(sel.head(), SelectionGoal::None);
17650 });
17651 })
17652 }
17653 self.selection_mark_mode = true;
17654 cx.notify();
17655 }
17656
17657 pub fn swap_selection_ends(
17658 &mut self,
17659 _: &actions::SwapSelectionEnds,
17660 window: &mut Window,
17661 cx: &mut Context<Self>,
17662 ) {
17663 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17664 s.move_with(|_, sel| {
17665 if sel.start != sel.end {
17666 sel.reversed = !sel.reversed
17667 }
17668 });
17669 });
17670 self.request_autoscroll(Autoscroll::newest(), cx);
17671 cx.notify();
17672 }
17673
17674 pub fn toggle_focus(
17675 workspace: &mut Workspace,
17676 _: &actions::ToggleFocus,
17677 window: &mut Window,
17678 cx: &mut Context<Workspace>,
17679 ) {
17680 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17681 return;
17682 };
17683 workspace.activate_item(&item, true, true, window, cx);
17684 }
17685
17686 pub fn toggle_fold(
17687 &mut self,
17688 _: &actions::ToggleFold,
17689 window: &mut Window,
17690 cx: &mut Context<Self>,
17691 ) {
17692 if self.is_singleton(cx) {
17693 let selection = self.selections.newest::<Point>(cx);
17694
17695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17696 let range = if selection.is_empty() {
17697 let point = selection.head().to_display_point(&display_map);
17698 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17699 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17700 .to_point(&display_map);
17701 start..end
17702 } else {
17703 selection.range()
17704 };
17705 if display_map.folds_in_range(range).next().is_some() {
17706 self.unfold_lines(&Default::default(), window, cx)
17707 } else {
17708 self.fold(&Default::default(), window, cx)
17709 }
17710 } else {
17711 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17712 let buffer_ids: HashSet<_> = self
17713 .selections
17714 .disjoint_anchor_ranges()
17715 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17716 .collect();
17717
17718 let should_unfold = buffer_ids
17719 .iter()
17720 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17721
17722 for buffer_id in buffer_ids {
17723 if should_unfold {
17724 self.unfold_buffer(buffer_id, cx);
17725 } else {
17726 self.fold_buffer(buffer_id, cx);
17727 }
17728 }
17729 }
17730 }
17731
17732 pub fn toggle_fold_recursive(
17733 &mut self,
17734 _: &actions::ToggleFoldRecursive,
17735 window: &mut Window,
17736 cx: &mut Context<Self>,
17737 ) {
17738 let selection = self.selections.newest::<Point>(cx);
17739
17740 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17741 let range = if selection.is_empty() {
17742 let point = selection.head().to_display_point(&display_map);
17743 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17744 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17745 .to_point(&display_map);
17746 start..end
17747 } else {
17748 selection.range()
17749 };
17750 if display_map.folds_in_range(range).next().is_some() {
17751 self.unfold_recursive(&Default::default(), window, cx)
17752 } else {
17753 self.fold_recursive(&Default::default(), window, cx)
17754 }
17755 }
17756
17757 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17758 if self.is_singleton(cx) {
17759 let mut to_fold = Vec::new();
17760 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17761 let selections = self.selections.all_adjusted(cx);
17762
17763 for selection in selections {
17764 let range = selection.range().sorted();
17765 let buffer_start_row = range.start.row;
17766
17767 if range.start.row != range.end.row {
17768 let mut found = false;
17769 let mut row = range.start.row;
17770 while row <= range.end.row {
17771 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17772 {
17773 found = true;
17774 row = crease.range().end.row + 1;
17775 to_fold.push(crease);
17776 } else {
17777 row += 1
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 && crease.range().end.row >= buffer_start_row
17788 {
17789 to_fold.push(crease);
17790 if row <= range.start.row {
17791 break;
17792 }
17793 }
17794 }
17795 }
17796
17797 self.fold_creases(to_fold, true, window, cx);
17798 } else {
17799 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17800 let buffer_ids = self
17801 .selections
17802 .disjoint_anchor_ranges()
17803 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17804 .collect::<HashSet<_>>();
17805 for buffer_id in buffer_ids {
17806 self.fold_buffer(buffer_id, cx);
17807 }
17808 }
17809 }
17810
17811 pub fn toggle_fold_all(
17812 &mut self,
17813 _: &actions::ToggleFoldAll,
17814 window: &mut Window,
17815 cx: &mut Context<Self>,
17816 ) {
17817 if self.buffer.read(cx).is_singleton() {
17818 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17819 let has_folds = display_map
17820 .folds_in_range(0..display_map.buffer_snapshot.len())
17821 .next()
17822 .is_some();
17823
17824 if has_folds {
17825 self.unfold_all(&actions::UnfoldAll, window, cx);
17826 } else {
17827 self.fold_all(&actions::FoldAll, window, cx);
17828 }
17829 } else {
17830 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17831 let should_unfold = buffer_ids
17832 .iter()
17833 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17834
17835 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17836 editor
17837 .update_in(cx, |editor, _, cx| {
17838 for buffer_id in buffer_ids {
17839 if should_unfold {
17840 editor.unfold_buffer(buffer_id, cx);
17841 } else {
17842 editor.fold_buffer(buffer_id, cx);
17843 }
17844 }
17845 })
17846 .ok();
17847 });
17848 }
17849 }
17850
17851 fn fold_at_level(
17852 &mut self,
17853 fold_at: &FoldAtLevel,
17854 window: &mut Window,
17855 cx: &mut Context<Self>,
17856 ) {
17857 if !self.buffer.read(cx).is_singleton() {
17858 return;
17859 }
17860
17861 let fold_at_level = fold_at.0;
17862 let snapshot = self.buffer.read(cx).snapshot(cx);
17863 let mut to_fold = Vec::new();
17864 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17865
17866 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17867 while start_row < end_row {
17868 match self
17869 .snapshot(window, cx)
17870 .crease_for_buffer_row(MultiBufferRow(start_row))
17871 {
17872 Some(crease) => {
17873 let nested_start_row = crease.range().start.row + 1;
17874 let nested_end_row = crease.range().end.row;
17875
17876 if current_level < fold_at_level {
17877 stack.push((nested_start_row, nested_end_row, current_level + 1));
17878 } else if current_level == fold_at_level {
17879 to_fold.push(crease);
17880 }
17881
17882 start_row = nested_end_row + 1;
17883 }
17884 None => start_row += 1,
17885 }
17886 }
17887 }
17888
17889 self.fold_creases(to_fold, true, window, cx);
17890 }
17891
17892 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17893 if self.buffer.read(cx).is_singleton() {
17894 let mut fold_ranges = Vec::new();
17895 let snapshot = self.buffer.read(cx).snapshot(cx);
17896
17897 for row in 0..snapshot.max_row().0 {
17898 if let Some(foldable_range) = self
17899 .snapshot(window, cx)
17900 .crease_for_buffer_row(MultiBufferRow(row))
17901 {
17902 fold_ranges.push(foldable_range);
17903 }
17904 }
17905
17906 self.fold_creases(fold_ranges, true, window, cx);
17907 } else {
17908 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17909 editor
17910 .update_in(cx, |editor, _, cx| {
17911 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17912 editor.fold_buffer(buffer_id, cx);
17913 }
17914 })
17915 .ok();
17916 });
17917 }
17918 }
17919
17920 pub fn fold_function_bodies(
17921 &mut self,
17922 _: &actions::FoldFunctionBodies,
17923 window: &mut Window,
17924 cx: &mut Context<Self>,
17925 ) {
17926 let snapshot = self.buffer.read(cx).snapshot(cx);
17927
17928 let ranges = snapshot
17929 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17930 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17931 .collect::<Vec<_>>();
17932
17933 let creases = ranges
17934 .into_iter()
17935 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17936 .collect();
17937
17938 self.fold_creases(creases, true, window, cx);
17939 }
17940
17941 pub fn fold_recursive(
17942 &mut self,
17943 _: &actions::FoldRecursive,
17944 window: &mut Window,
17945 cx: &mut Context<Self>,
17946 ) {
17947 let mut to_fold = Vec::new();
17948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17949 let selections = self.selections.all_adjusted(cx);
17950
17951 for selection in selections {
17952 let range = selection.range().sorted();
17953 let buffer_start_row = range.start.row;
17954
17955 if range.start.row != range.end.row {
17956 let mut found = false;
17957 for row in range.start.row..=range.end.row {
17958 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17959 found = true;
17960 to_fold.push(crease);
17961 }
17962 }
17963 if found {
17964 continue;
17965 }
17966 }
17967
17968 for row in (0..=range.start.row).rev() {
17969 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17970 if crease.range().end.row >= buffer_start_row {
17971 to_fold.push(crease);
17972 } else {
17973 break;
17974 }
17975 }
17976 }
17977 }
17978
17979 self.fold_creases(to_fold, true, window, cx);
17980 }
17981
17982 pub fn fold_at(
17983 &mut self,
17984 buffer_row: MultiBufferRow,
17985 window: &mut Window,
17986 cx: &mut Context<Self>,
17987 ) {
17988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17989
17990 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17991 let autoscroll = self
17992 .selections
17993 .all::<Point>(cx)
17994 .iter()
17995 .any(|selection| crease.range().overlaps(&selection.range()));
17996
17997 self.fold_creases(vec![crease], autoscroll, window, cx);
17998 }
17999 }
18000
18001 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18002 if self.is_singleton(cx) {
18003 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18004 let buffer = &display_map.buffer_snapshot;
18005 let selections = self.selections.all::<Point>(cx);
18006 let ranges = selections
18007 .iter()
18008 .map(|s| {
18009 let range = s.display_range(&display_map).sorted();
18010 let mut start = range.start.to_point(&display_map);
18011 let mut end = range.end.to_point(&display_map);
18012 start.column = 0;
18013 end.column = buffer.line_len(MultiBufferRow(end.row));
18014 start..end
18015 })
18016 .collect::<Vec<_>>();
18017
18018 self.unfold_ranges(&ranges, true, true, cx);
18019 } else {
18020 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18021 let buffer_ids = self
18022 .selections
18023 .disjoint_anchor_ranges()
18024 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18025 .collect::<HashSet<_>>();
18026 for buffer_id in buffer_ids {
18027 self.unfold_buffer(buffer_id, cx);
18028 }
18029 }
18030 }
18031
18032 pub fn unfold_recursive(
18033 &mut self,
18034 _: &UnfoldRecursive,
18035 _window: &mut Window,
18036 cx: &mut Context<Self>,
18037 ) {
18038 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18039 let selections = self.selections.all::<Point>(cx);
18040 let ranges = selections
18041 .iter()
18042 .map(|s| {
18043 let mut range = s.display_range(&display_map).sorted();
18044 *range.start.column_mut() = 0;
18045 *range.end.column_mut() = display_map.line_len(range.end.row());
18046 let start = range.start.to_point(&display_map);
18047 let end = range.end.to_point(&display_map);
18048 start..end
18049 })
18050 .collect::<Vec<_>>();
18051
18052 self.unfold_ranges(&ranges, true, true, cx);
18053 }
18054
18055 pub fn unfold_at(
18056 &mut self,
18057 buffer_row: MultiBufferRow,
18058 _window: &mut Window,
18059 cx: &mut Context<Self>,
18060 ) {
18061 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18062
18063 let intersection_range = Point::new(buffer_row.0, 0)
18064 ..Point::new(
18065 buffer_row.0,
18066 display_map.buffer_snapshot.line_len(buffer_row),
18067 );
18068
18069 let autoscroll = self
18070 .selections
18071 .all::<Point>(cx)
18072 .iter()
18073 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18074
18075 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18076 }
18077
18078 pub fn unfold_all(
18079 &mut self,
18080 _: &actions::UnfoldAll,
18081 _window: &mut Window,
18082 cx: &mut Context<Self>,
18083 ) {
18084 if self.buffer.read(cx).is_singleton() {
18085 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18086 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18087 } else {
18088 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18089 editor
18090 .update(cx, |editor, cx| {
18091 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18092 editor.unfold_buffer(buffer_id, cx);
18093 }
18094 })
18095 .ok();
18096 });
18097 }
18098 }
18099
18100 pub fn fold_selected_ranges(
18101 &mut self,
18102 _: &FoldSelectedRanges,
18103 window: &mut Window,
18104 cx: &mut Context<Self>,
18105 ) {
18106 let selections = self.selections.all_adjusted(cx);
18107 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18108 let ranges = selections
18109 .into_iter()
18110 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18111 .collect::<Vec<_>>();
18112 self.fold_creases(ranges, true, window, cx);
18113 }
18114
18115 pub fn fold_ranges<T: ToOffset + Clone>(
18116 &mut self,
18117 ranges: Vec<Range<T>>,
18118 auto_scroll: bool,
18119 window: &mut Window,
18120 cx: &mut Context<Self>,
18121 ) {
18122 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18123 let ranges = ranges
18124 .into_iter()
18125 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18126 .collect::<Vec<_>>();
18127 self.fold_creases(ranges, auto_scroll, window, cx);
18128 }
18129
18130 pub fn fold_creases<T: ToOffset + Clone>(
18131 &mut self,
18132 creases: Vec<Crease<T>>,
18133 auto_scroll: bool,
18134 _window: &mut Window,
18135 cx: &mut Context<Self>,
18136 ) {
18137 if creases.is_empty() {
18138 return;
18139 }
18140
18141 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18142
18143 if auto_scroll {
18144 self.request_autoscroll(Autoscroll::fit(), cx);
18145 }
18146
18147 cx.notify();
18148
18149 self.scrollbar_marker_state.dirty = true;
18150 self.folds_did_change(cx);
18151 }
18152
18153 /// Removes any folds whose ranges intersect any of the given ranges.
18154 pub fn unfold_ranges<T: ToOffset + Clone>(
18155 &mut self,
18156 ranges: &[Range<T>],
18157 inclusive: bool,
18158 auto_scroll: bool,
18159 cx: &mut Context<Self>,
18160 ) {
18161 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18162 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18163 });
18164 self.folds_did_change(cx);
18165 }
18166
18167 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18168 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18169 return;
18170 }
18171 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18172 self.display_map.update(cx, |display_map, cx| {
18173 display_map.fold_buffers([buffer_id], cx)
18174 });
18175 cx.emit(EditorEvent::BufferFoldToggled {
18176 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18177 folded: true,
18178 });
18179 cx.notify();
18180 }
18181
18182 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18183 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18184 return;
18185 }
18186 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18187 self.display_map.update(cx, |display_map, cx| {
18188 display_map.unfold_buffers([buffer_id], cx);
18189 });
18190 cx.emit(EditorEvent::BufferFoldToggled {
18191 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18192 folded: false,
18193 });
18194 cx.notify();
18195 }
18196
18197 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18198 self.display_map.read(cx).is_buffer_folded(buffer)
18199 }
18200
18201 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18202 self.display_map.read(cx).folded_buffers()
18203 }
18204
18205 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18206 self.display_map.update(cx, |display_map, cx| {
18207 display_map.disable_header_for_buffer(buffer_id, cx);
18208 });
18209 cx.notify();
18210 }
18211
18212 /// Removes any folds with the given ranges.
18213 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18214 &mut self,
18215 ranges: &[Range<T>],
18216 type_id: TypeId,
18217 auto_scroll: bool,
18218 cx: &mut Context<Self>,
18219 ) {
18220 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18221 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18222 });
18223 self.folds_did_change(cx);
18224 }
18225
18226 fn remove_folds_with<T: ToOffset + Clone>(
18227 &mut self,
18228 ranges: &[Range<T>],
18229 auto_scroll: bool,
18230 cx: &mut Context<Self>,
18231 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18232 ) {
18233 if ranges.is_empty() {
18234 return;
18235 }
18236
18237 let mut buffers_affected = HashSet::default();
18238 let multi_buffer = self.buffer().read(cx);
18239 for range in ranges {
18240 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18241 buffers_affected.insert(buffer.read(cx).remote_id());
18242 };
18243 }
18244
18245 self.display_map.update(cx, update);
18246
18247 if auto_scroll {
18248 self.request_autoscroll(Autoscroll::fit(), cx);
18249 }
18250
18251 cx.notify();
18252 self.scrollbar_marker_state.dirty = true;
18253 self.active_indent_guides_state.dirty = true;
18254 }
18255
18256 pub fn update_renderer_widths(
18257 &mut self,
18258 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18259 cx: &mut Context<Self>,
18260 ) -> bool {
18261 self.display_map
18262 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18263 }
18264
18265 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18266 self.display_map.read(cx).fold_placeholder.clone()
18267 }
18268
18269 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18270 self.buffer.update(cx, |buffer, cx| {
18271 buffer.set_all_diff_hunks_expanded(cx);
18272 });
18273 }
18274
18275 pub fn expand_all_diff_hunks(
18276 &mut self,
18277 _: &ExpandAllDiffHunks,
18278 _window: &mut Window,
18279 cx: &mut Context<Self>,
18280 ) {
18281 self.buffer.update(cx, |buffer, cx| {
18282 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18283 });
18284 }
18285
18286 pub fn toggle_selected_diff_hunks(
18287 &mut self,
18288 _: &ToggleSelectedDiffHunks,
18289 _window: &mut Window,
18290 cx: &mut Context<Self>,
18291 ) {
18292 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18293 self.toggle_diff_hunks_in_ranges(ranges, cx);
18294 }
18295
18296 pub fn diff_hunks_in_ranges<'a>(
18297 &'a self,
18298 ranges: &'a [Range<Anchor>],
18299 buffer: &'a MultiBufferSnapshot,
18300 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18301 ranges.iter().flat_map(move |range| {
18302 let end_excerpt_id = range.end.excerpt_id;
18303 let range = range.to_point(buffer);
18304 let mut peek_end = range.end;
18305 if range.end.row < buffer.max_row().0 {
18306 peek_end = Point::new(range.end.row + 1, 0);
18307 }
18308 buffer
18309 .diff_hunks_in_range(range.start..peek_end)
18310 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18311 })
18312 }
18313
18314 pub fn has_stageable_diff_hunks_in_ranges(
18315 &self,
18316 ranges: &[Range<Anchor>],
18317 snapshot: &MultiBufferSnapshot,
18318 ) -> bool {
18319 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18320 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18321 }
18322
18323 pub fn toggle_staged_selected_diff_hunks(
18324 &mut self,
18325 _: &::git::ToggleStaged,
18326 _: &mut Window,
18327 cx: &mut Context<Self>,
18328 ) {
18329 let snapshot = self.buffer.read(cx).snapshot(cx);
18330 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18331 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18332 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18333 }
18334
18335 pub fn set_render_diff_hunk_controls(
18336 &mut self,
18337 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18338 cx: &mut Context<Self>,
18339 ) {
18340 self.render_diff_hunk_controls = render_diff_hunk_controls;
18341 cx.notify();
18342 }
18343
18344 pub fn stage_and_next(
18345 &mut self,
18346 _: &::git::StageAndNext,
18347 window: &mut Window,
18348 cx: &mut Context<Self>,
18349 ) {
18350 self.do_stage_or_unstage_and_next(true, window, cx);
18351 }
18352
18353 pub fn unstage_and_next(
18354 &mut self,
18355 _: &::git::UnstageAndNext,
18356 window: &mut Window,
18357 cx: &mut Context<Self>,
18358 ) {
18359 self.do_stage_or_unstage_and_next(false, window, cx);
18360 }
18361
18362 pub fn stage_or_unstage_diff_hunks(
18363 &mut self,
18364 stage: bool,
18365 ranges: Vec<Range<Anchor>>,
18366 cx: &mut Context<Self>,
18367 ) {
18368 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18369 cx.spawn(async move |this, cx| {
18370 task.await?;
18371 this.update(cx, |this, cx| {
18372 let snapshot = this.buffer.read(cx).snapshot(cx);
18373 let chunk_by = this
18374 .diff_hunks_in_ranges(&ranges, &snapshot)
18375 .chunk_by(|hunk| hunk.buffer_id);
18376 for (buffer_id, hunks) in &chunk_by {
18377 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18378 }
18379 })
18380 })
18381 .detach_and_log_err(cx);
18382 }
18383
18384 fn save_buffers_for_ranges_if_needed(
18385 &mut self,
18386 ranges: &[Range<Anchor>],
18387 cx: &mut Context<Editor>,
18388 ) -> Task<Result<()>> {
18389 let multibuffer = self.buffer.read(cx);
18390 let snapshot = multibuffer.read(cx);
18391 let buffer_ids: HashSet<_> = ranges
18392 .iter()
18393 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18394 .collect();
18395 drop(snapshot);
18396
18397 let mut buffers = HashSet::default();
18398 for buffer_id in buffer_ids {
18399 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18400 let buffer = buffer_entity.read(cx);
18401 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18402 {
18403 buffers.insert(buffer_entity);
18404 }
18405 }
18406 }
18407
18408 if let Some(project) = &self.project {
18409 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18410 } else {
18411 Task::ready(Ok(()))
18412 }
18413 }
18414
18415 fn do_stage_or_unstage_and_next(
18416 &mut self,
18417 stage: bool,
18418 window: &mut Window,
18419 cx: &mut Context<Self>,
18420 ) {
18421 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18422
18423 if ranges.iter().any(|range| range.start != range.end) {
18424 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18425 return;
18426 }
18427
18428 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18429 let snapshot = self.snapshot(window, cx);
18430 let position = self.selections.newest::<Point>(cx).head();
18431 let mut row = snapshot
18432 .buffer_snapshot
18433 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18434 .find(|hunk| hunk.row_range.start.0 > position.row)
18435 .map(|hunk| hunk.row_range.start);
18436
18437 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18438 // Outside of the project diff editor, wrap around to the beginning.
18439 if !all_diff_hunks_expanded {
18440 row = row.or_else(|| {
18441 snapshot
18442 .buffer_snapshot
18443 .diff_hunks_in_range(Point::zero()..position)
18444 .find(|hunk| hunk.row_range.end.0 < position.row)
18445 .map(|hunk| hunk.row_range.start)
18446 });
18447 }
18448
18449 if let Some(row) = row {
18450 let destination = Point::new(row.0, 0);
18451 let autoscroll = Autoscroll::center();
18452
18453 self.unfold_ranges(&[destination..destination], false, false, cx);
18454 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18455 s.select_ranges([destination..destination]);
18456 });
18457 }
18458 }
18459
18460 fn do_stage_or_unstage(
18461 &self,
18462 stage: bool,
18463 buffer_id: BufferId,
18464 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18465 cx: &mut App,
18466 ) -> Option<()> {
18467 let project = self.project()?;
18468 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18469 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18470 let buffer_snapshot = buffer.read(cx).snapshot();
18471 let file_exists = buffer_snapshot
18472 .file()
18473 .is_some_and(|file| file.disk_state().exists());
18474 diff.update(cx, |diff, cx| {
18475 diff.stage_or_unstage_hunks(
18476 stage,
18477 &hunks
18478 .map(|hunk| buffer_diff::DiffHunk {
18479 buffer_range: hunk.buffer_range,
18480 diff_base_byte_range: hunk.diff_base_byte_range,
18481 secondary_status: hunk.secondary_status,
18482 range: Point::zero()..Point::zero(), // unused
18483 })
18484 .collect::<Vec<_>>(),
18485 &buffer_snapshot,
18486 file_exists,
18487 cx,
18488 )
18489 });
18490 None
18491 }
18492
18493 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18494 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18495 self.buffer
18496 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18497 }
18498
18499 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18500 self.buffer.update(cx, |buffer, cx| {
18501 let ranges = vec![Anchor::min()..Anchor::max()];
18502 if !buffer.all_diff_hunks_expanded()
18503 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18504 {
18505 buffer.collapse_diff_hunks(ranges, cx);
18506 true
18507 } else {
18508 false
18509 }
18510 })
18511 }
18512
18513 fn toggle_diff_hunks_in_ranges(
18514 &mut self,
18515 ranges: Vec<Range<Anchor>>,
18516 cx: &mut Context<Editor>,
18517 ) {
18518 self.buffer.update(cx, |buffer, cx| {
18519 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18520 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18521 })
18522 }
18523
18524 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18525 self.buffer.update(cx, |buffer, cx| {
18526 let snapshot = buffer.snapshot(cx);
18527 let excerpt_id = range.end.excerpt_id;
18528 let point_range = range.to_point(&snapshot);
18529 let expand = !buffer.single_hunk_is_expanded(range, cx);
18530 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18531 })
18532 }
18533
18534 pub(crate) fn apply_all_diff_hunks(
18535 &mut self,
18536 _: &ApplyAllDiffHunks,
18537 window: &mut Window,
18538 cx: &mut Context<Self>,
18539 ) {
18540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18541
18542 let buffers = self.buffer.read(cx).all_buffers();
18543 for branch_buffer in buffers {
18544 branch_buffer.update(cx, |branch_buffer, cx| {
18545 branch_buffer.merge_into_base(Vec::new(), cx);
18546 });
18547 }
18548
18549 if let Some(project) = self.project.clone() {
18550 self.save(
18551 SaveOptions {
18552 format: true,
18553 autosave: false,
18554 },
18555 project,
18556 window,
18557 cx,
18558 )
18559 .detach_and_log_err(cx);
18560 }
18561 }
18562
18563 pub(crate) fn apply_selected_diff_hunks(
18564 &mut self,
18565 _: &ApplyDiffHunk,
18566 window: &mut Window,
18567 cx: &mut Context<Self>,
18568 ) {
18569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18570 let snapshot = self.snapshot(window, cx);
18571 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18572 let mut ranges_by_buffer = HashMap::default();
18573 self.transact(window, cx, |editor, _window, cx| {
18574 for hunk in hunks {
18575 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18576 ranges_by_buffer
18577 .entry(buffer.clone())
18578 .or_insert_with(Vec::new)
18579 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18580 }
18581 }
18582
18583 for (buffer, ranges) in ranges_by_buffer {
18584 buffer.update(cx, |buffer, cx| {
18585 buffer.merge_into_base(ranges, cx);
18586 });
18587 }
18588 });
18589
18590 if let Some(project) = self.project.clone() {
18591 self.save(
18592 SaveOptions {
18593 format: true,
18594 autosave: false,
18595 },
18596 project,
18597 window,
18598 cx,
18599 )
18600 .detach_and_log_err(cx);
18601 }
18602 }
18603
18604 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18605 if hovered != self.gutter_hovered {
18606 self.gutter_hovered = hovered;
18607 cx.notify();
18608 }
18609 }
18610
18611 pub fn insert_blocks(
18612 &mut self,
18613 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18614 autoscroll: Option<Autoscroll>,
18615 cx: &mut Context<Self>,
18616 ) -> Vec<CustomBlockId> {
18617 let blocks = self
18618 .display_map
18619 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18620 if let Some(autoscroll) = autoscroll {
18621 self.request_autoscroll(autoscroll, cx);
18622 }
18623 cx.notify();
18624 blocks
18625 }
18626
18627 pub fn resize_blocks(
18628 &mut self,
18629 heights: HashMap<CustomBlockId, u32>,
18630 autoscroll: Option<Autoscroll>,
18631 cx: &mut Context<Self>,
18632 ) {
18633 self.display_map
18634 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18635 if let Some(autoscroll) = autoscroll {
18636 self.request_autoscroll(autoscroll, cx);
18637 }
18638 cx.notify();
18639 }
18640
18641 pub fn replace_blocks(
18642 &mut self,
18643 renderers: HashMap<CustomBlockId, RenderBlock>,
18644 autoscroll: Option<Autoscroll>,
18645 cx: &mut Context<Self>,
18646 ) {
18647 self.display_map
18648 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18649 if let Some(autoscroll) = autoscroll {
18650 self.request_autoscroll(autoscroll, cx);
18651 }
18652 cx.notify();
18653 }
18654
18655 pub fn remove_blocks(
18656 &mut self,
18657 block_ids: HashSet<CustomBlockId>,
18658 autoscroll: Option<Autoscroll>,
18659 cx: &mut Context<Self>,
18660 ) {
18661 self.display_map.update(cx, |display_map, cx| {
18662 display_map.remove_blocks(block_ids, cx)
18663 });
18664 if let Some(autoscroll) = autoscroll {
18665 self.request_autoscroll(autoscroll, cx);
18666 }
18667 cx.notify();
18668 }
18669
18670 pub fn row_for_block(
18671 &self,
18672 block_id: CustomBlockId,
18673 cx: &mut Context<Self>,
18674 ) -> Option<DisplayRow> {
18675 self.display_map
18676 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18677 }
18678
18679 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18680 self.focused_block = Some(focused_block);
18681 }
18682
18683 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18684 self.focused_block.take()
18685 }
18686
18687 pub fn insert_creases(
18688 &mut self,
18689 creases: impl IntoIterator<Item = Crease<Anchor>>,
18690 cx: &mut Context<Self>,
18691 ) -> Vec<CreaseId> {
18692 self.display_map
18693 .update(cx, |map, cx| map.insert_creases(creases, cx))
18694 }
18695
18696 pub fn remove_creases(
18697 &mut self,
18698 ids: impl IntoIterator<Item = CreaseId>,
18699 cx: &mut Context<Self>,
18700 ) -> Vec<(CreaseId, Range<Anchor>)> {
18701 self.display_map
18702 .update(cx, |map, cx| map.remove_creases(ids, cx))
18703 }
18704
18705 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18706 self.display_map
18707 .update(cx, |map, cx| map.snapshot(cx))
18708 .longest_row()
18709 }
18710
18711 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18712 self.display_map
18713 .update(cx, |map, cx| map.snapshot(cx))
18714 .max_point()
18715 }
18716
18717 pub fn text(&self, cx: &App) -> String {
18718 self.buffer.read(cx).read(cx).text()
18719 }
18720
18721 pub fn is_empty(&self, cx: &App) -> bool {
18722 self.buffer.read(cx).read(cx).is_empty()
18723 }
18724
18725 pub fn text_option(&self, cx: &App) -> Option<String> {
18726 let text = self.text(cx);
18727 let text = text.trim();
18728
18729 if text.is_empty() {
18730 return None;
18731 }
18732
18733 Some(text.to_string())
18734 }
18735
18736 pub fn set_text(
18737 &mut self,
18738 text: impl Into<Arc<str>>,
18739 window: &mut Window,
18740 cx: &mut Context<Self>,
18741 ) {
18742 self.transact(window, cx, |this, _, cx| {
18743 this.buffer
18744 .read(cx)
18745 .as_singleton()
18746 .expect("you can only call set_text on editors for singleton buffers")
18747 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18748 });
18749 }
18750
18751 pub fn display_text(&self, cx: &mut App) -> String {
18752 self.display_map
18753 .update(cx, |map, cx| map.snapshot(cx))
18754 .text()
18755 }
18756
18757 fn create_minimap(
18758 &self,
18759 minimap_settings: MinimapSettings,
18760 window: &mut Window,
18761 cx: &mut Context<Self>,
18762 ) -> Option<Entity<Self>> {
18763 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18764 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18765 }
18766
18767 fn initialize_new_minimap(
18768 &self,
18769 minimap_settings: MinimapSettings,
18770 window: &mut Window,
18771 cx: &mut Context<Self>,
18772 ) -> Entity<Self> {
18773 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18774
18775 let mut minimap = Editor::new_internal(
18776 EditorMode::Minimap {
18777 parent: cx.weak_entity(),
18778 },
18779 self.buffer.clone(),
18780 None,
18781 Some(self.display_map.clone()),
18782 window,
18783 cx,
18784 );
18785 minimap.scroll_manager.clone_state(&self.scroll_manager);
18786 minimap.set_text_style_refinement(TextStyleRefinement {
18787 font_size: Some(MINIMAP_FONT_SIZE),
18788 font_weight: Some(MINIMAP_FONT_WEIGHT),
18789 ..Default::default()
18790 });
18791 minimap.update_minimap_configuration(minimap_settings, cx);
18792 cx.new(|_| minimap)
18793 }
18794
18795 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18796 let current_line_highlight = minimap_settings
18797 .current_line_highlight
18798 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18799 self.set_current_line_highlight(Some(current_line_highlight));
18800 }
18801
18802 pub fn minimap(&self) -> Option<&Entity<Self>> {
18803 self.minimap
18804 .as_ref()
18805 .filter(|_| self.minimap_visibility.visible())
18806 }
18807
18808 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18809 let mut wrap_guides = smallvec![];
18810
18811 if self.show_wrap_guides == Some(false) {
18812 return wrap_guides;
18813 }
18814
18815 let settings = self.buffer.read(cx).language_settings(cx);
18816 if settings.show_wrap_guides {
18817 match self.soft_wrap_mode(cx) {
18818 SoftWrap::Column(soft_wrap) => {
18819 wrap_guides.push((soft_wrap as usize, true));
18820 }
18821 SoftWrap::Bounded(soft_wrap) => {
18822 wrap_guides.push((soft_wrap as usize, true));
18823 }
18824 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18825 }
18826 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18827 }
18828
18829 wrap_guides
18830 }
18831
18832 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18833 let settings = self.buffer.read(cx).language_settings(cx);
18834 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18835 match mode {
18836 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18837 SoftWrap::None
18838 }
18839 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18840 language_settings::SoftWrap::PreferredLineLength => {
18841 SoftWrap::Column(settings.preferred_line_length)
18842 }
18843 language_settings::SoftWrap::Bounded => {
18844 SoftWrap::Bounded(settings.preferred_line_length)
18845 }
18846 }
18847 }
18848
18849 pub fn set_soft_wrap_mode(
18850 &mut self,
18851 mode: language_settings::SoftWrap,
18852
18853 cx: &mut Context<Self>,
18854 ) {
18855 self.soft_wrap_mode_override = Some(mode);
18856 cx.notify();
18857 }
18858
18859 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18860 self.hard_wrap = hard_wrap;
18861 cx.notify();
18862 }
18863
18864 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18865 self.text_style_refinement = Some(style);
18866 }
18867
18868 /// called by the Element so we know what style we were most recently rendered with.
18869 pub(crate) fn set_style(
18870 &mut self,
18871 style: EditorStyle,
18872 window: &mut Window,
18873 cx: &mut Context<Self>,
18874 ) {
18875 // We intentionally do not inform the display map about the minimap style
18876 // so that wrapping is not recalculated and stays consistent for the editor
18877 // and its linked minimap.
18878 if !self.mode.is_minimap() {
18879 let rem_size = window.rem_size();
18880 self.display_map.update(cx, |map, cx| {
18881 map.set_font(
18882 style.text.font(),
18883 style.text.font_size.to_pixels(rem_size),
18884 cx,
18885 )
18886 });
18887 }
18888 self.style = Some(style);
18889 }
18890
18891 pub fn style(&self) -> Option<&EditorStyle> {
18892 self.style.as_ref()
18893 }
18894
18895 // Called by the element. This method is not designed to be called outside of the editor
18896 // element's layout code because it does not notify when rewrapping is computed synchronously.
18897 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18898 self.display_map
18899 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18900 }
18901
18902 pub fn set_soft_wrap(&mut self) {
18903 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18904 }
18905
18906 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18907 if self.soft_wrap_mode_override.is_some() {
18908 self.soft_wrap_mode_override.take();
18909 } else {
18910 let soft_wrap = match self.soft_wrap_mode(cx) {
18911 SoftWrap::GitDiff => return,
18912 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18913 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18914 language_settings::SoftWrap::None
18915 }
18916 };
18917 self.soft_wrap_mode_override = Some(soft_wrap);
18918 }
18919 cx.notify();
18920 }
18921
18922 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18923 let Some(workspace) = self.workspace() else {
18924 return;
18925 };
18926 let fs = workspace.read(cx).app_state().fs.clone();
18927 let current_show = TabBarSettings::get_global(cx).show;
18928 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18929 setting.show = Some(!current_show);
18930 });
18931 }
18932
18933 pub fn toggle_indent_guides(
18934 &mut self,
18935 _: &ToggleIndentGuides,
18936 _: &mut Window,
18937 cx: &mut Context<Self>,
18938 ) {
18939 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18940 self.buffer
18941 .read(cx)
18942 .language_settings(cx)
18943 .indent_guides
18944 .enabled
18945 });
18946 self.show_indent_guides = Some(!currently_enabled);
18947 cx.notify();
18948 }
18949
18950 fn should_show_indent_guides(&self) -> Option<bool> {
18951 self.show_indent_guides
18952 }
18953
18954 pub fn toggle_line_numbers(
18955 &mut self,
18956 _: &ToggleLineNumbers,
18957 _: &mut Window,
18958 cx: &mut Context<Self>,
18959 ) {
18960 let mut editor_settings = EditorSettings::get_global(cx).clone();
18961 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18962 EditorSettings::override_global(editor_settings, cx);
18963 }
18964
18965 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18966 if let Some(show_line_numbers) = self.show_line_numbers {
18967 return show_line_numbers;
18968 }
18969 EditorSettings::get_global(cx).gutter.line_numbers
18970 }
18971
18972 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18973 self.use_relative_line_numbers
18974 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18975 }
18976
18977 pub fn toggle_relative_line_numbers(
18978 &mut self,
18979 _: &ToggleRelativeLineNumbers,
18980 _: &mut Window,
18981 cx: &mut Context<Self>,
18982 ) {
18983 let is_relative = self.should_use_relative_line_numbers(cx);
18984 self.set_relative_line_number(Some(!is_relative), cx)
18985 }
18986
18987 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18988 self.use_relative_line_numbers = is_relative;
18989 cx.notify();
18990 }
18991
18992 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18993 self.show_gutter = show_gutter;
18994 cx.notify();
18995 }
18996
18997 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18998 self.show_scrollbars = ScrollbarAxes {
18999 horizontal: show,
19000 vertical: show,
19001 };
19002 cx.notify();
19003 }
19004
19005 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19006 self.show_scrollbars.vertical = show;
19007 cx.notify();
19008 }
19009
19010 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19011 self.show_scrollbars.horizontal = show;
19012 cx.notify();
19013 }
19014
19015 pub fn set_minimap_visibility(
19016 &mut self,
19017 minimap_visibility: MinimapVisibility,
19018 window: &mut Window,
19019 cx: &mut Context<Self>,
19020 ) {
19021 if self.minimap_visibility != minimap_visibility {
19022 if minimap_visibility.visible() && self.minimap.is_none() {
19023 let minimap_settings = EditorSettings::get_global(cx).minimap;
19024 self.minimap =
19025 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19026 }
19027 self.minimap_visibility = minimap_visibility;
19028 cx.notify();
19029 }
19030 }
19031
19032 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19033 self.set_show_scrollbars(false, cx);
19034 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19035 }
19036
19037 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19038 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19039 }
19040
19041 /// Normally the text in full mode and auto height editors is padded on the
19042 /// left side by roughly half a character width for improved hit testing.
19043 ///
19044 /// Use this method to disable this for cases where this is not wanted (e.g.
19045 /// if you want to align the editor text with some other text above or below)
19046 /// or if you want to add this padding to single-line editors.
19047 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19048 self.offset_content = offset_content;
19049 cx.notify();
19050 }
19051
19052 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19053 self.show_line_numbers = Some(show_line_numbers);
19054 cx.notify();
19055 }
19056
19057 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19058 self.disable_expand_excerpt_buttons = true;
19059 cx.notify();
19060 }
19061
19062 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19063 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19064 cx.notify();
19065 }
19066
19067 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19068 self.show_code_actions = Some(show_code_actions);
19069 cx.notify();
19070 }
19071
19072 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19073 self.show_runnables = Some(show_runnables);
19074 cx.notify();
19075 }
19076
19077 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19078 self.show_breakpoints = Some(show_breakpoints);
19079 cx.notify();
19080 }
19081
19082 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19083 if self.display_map.read(cx).masked != masked {
19084 self.display_map.update(cx, |map, _| map.masked = masked);
19085 }
19086 cx.notify()
19087 }
19088
19089 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19090 self.show_wrap_guides = Some(show_wrap_guides);
19091 cx.notify();
19092 }
19093
19094 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19095 self.show_indent_guides = Some(show_indent_guides);
19096 cx.notify();
19097 }
19098
19099 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19100 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19101 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19102 && let Some(dir) = file.abs_path(cx).parent()
19103 {
19104 return Some(dir.to_owned());
19105 }
19106
19107 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19108 return Some(project_path.path.to_path_buf());
19109 }
19110 }
19111
19112 None
19113 }
19114
19115 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19116 self.active_excerpt(cx)?
19117 .1
19118 .read(cx)
19119 .file()
19120 .and_then(|f| f.as_local())
19121 }
19122
19123 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19124 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19125 let buffer = buffer.read(cx);
19126 if let Some(project_path) = buffer.project_path(cx) {
19127 let project = self.project()?.read(cx);
19128 project.absolute_path(&project_path, cx)
19129 } else {
19130 buffer
19131 .file()
19132 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19133 }
19134 })
19135 }
19136
19137 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19138 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19139 let project_path = buffer.read(cx).project_path(cx)?;
19140 let project = self.project()?.read(cx);
19141 let entry = project.entry_for_path(&project_path, cx)?;
19142 let path = entry.path.to_path_buf();
19143 Some(path)
19144 })
19145 }
19146
19147 pub fn reveal_in_finder(
19148 &mut self,
19149 _: &RevealInFileManager,
19150 _window: &mut Window,
19151 cx: &mut Context<Self>,
19152 ) {
19153 if let Some(target) = self.target_file(cx) {
19154 cx.reveal_path(&target.abs_path(cx));
19155 }
19156 }
19157
19158 pub fn copy_path(
19159 &mut self,
19160 _: &zed_actions::workspace::CopyPath,
19161 _window: &mut Window,
19162 cx: &mut Context<Self>,
19163 ) {
19164 if let Some(path) = self.target_file_abs_path(cx)
19165 && let Some(path) = path.to_str()
19166 {
19167 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19168 }
19169 }
19170
19171 pub fn copy_relative_path(
19172 &mut self,
19173 _: &zed_actions::workspace::CopyRelativePath,
19174 _window: &mut Window,
19175 cx: &mut Context<Self>,
19176 ) {
19177 if let Some(path) = self.target_file_path(cx)
19178 && let Some(path) = path.to_str()
19179 {
19180 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19181 }
19182 }
19183
19184 /// Returns the project path for the editor's buffer, if any buffer is
19185 /// opened in the editor.
19186 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19187 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19188 buffer.read(cx).project_path(cx)
19189 } else {
19190 None
19191 }
19192 }
19193
19194 // Returns true if the editor handled a go-to-line request
19195 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19196 maybe!({
19197 let breakpoint_store = self.breakpoint_store.as_ref()?;
19198
19199 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19200 else {
19201 self.clear_row_highlights::<ActiveDebugLine>();
19202 return None;
19203 };
19204
19205 let position = active_stack_frame.position;
19206 let buffer_id = position.buffer_id?;
19207 let snapshot = self
19208 .project
19209 .as_ref()?
19210 .read(cx)
19211 .buffer_for_id(buffer_id, cx)?
19212 .read(cx)
19213 .snapshot();
19214
19215 let mut handled = false;
19216 for (id, ExcerptRange { context, .. }) in
19217 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19218 {
19219 if context.start.cmp(&position, &snapshot).is_ge()
19220 || context.end.cmp(&position, &snapshot).is_lt()
19221 {
19222 continue;
19223 }
19224 let snapshot = self.buffer.read(cx).snapshot(cx);
19225 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19226
19227 handled = true;
19228 self.clear_row_highlights::<ActiveDebugLine>();
19229
19230 self.go_to_line::<ActiveDebugLine>(
19231 multibuffer_anchor,
19232 Some(cx.theme().colors().editor_debugger_active_line_background),
19233 window,
19234 cx,
19235 );
19236
19237 cx.notify();
19238 }
19239
19240 handled.then_some(())
19241 })
19242 .is_some()
19243 }
19244
19245 pub fn copy_file_name_without_extension(
19246 &mut self,
19247 _: &CopyFileNameWithoutExtension,
19248 _: &mut Window,
19249 cx: &mut Context<Self>,
19250 ) {
19251 if let Some(file) = self.target_file(cx)
19252 && let Some(file_stem) = file.path().file_stem()
19253 && let Some(name) = file_stem.to_str()
19254 {
19255 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19256 }
19257 }
19258
19259 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19260 if let Some(file) = self.target_file(cx)
19261 && let Some(file_name) = file.path().file_name()
19262 && let Some(name) = file_name.to_str()
19263 {
19264 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19265 }
19266 }
19267
19268 pub fn toggle_git_blame(
19269 &mut self,
19270 _: &::git::Blame,
19271 window: &mut Window,
19272 cx: &mut Context<Self>,
19273 ) {
19274 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19275
19276 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19277 self.start_git_blame(true, window, cx);
19278 }
19279
19280 cx.notify();
19281 }
19282
19283 pub fn toggle_git_blame_inline(
19284 &mut self,
19285 _: &ToggleGitBlameInline,
19286 window: &mut Window,
19287 cx: &mut Context<Self>,
19288 ) {
19289 self.toggle_git_blame_inline_internal(true, window, cx);
19290 cx.notify();
19291 }
19292
19293 pub fn open_git_blame_commit(
19294 &mut self,
19295 _: &OpenGitBlameCommit,
19296 window: &mut Window,
19297 cx: &mut Context<Self>,
19298 ) {
19299 self.open_git_blame_commit_internal(window, cx);
19300 }
19301
19302 fn open_git_blame_commit_internal(
19303 &mut self,
19304 window: &mut Window,
19305 cx: &mut Context<Self>,
19306 ) -> Option<()> {
19307 let blame = self.blame.as_ref()?;
19308 let snapshot = self.snapshot(window, cx);
19309 let cursor = self.selections.newest::<Point>(cx).head();
19310 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19311 let (_, blame_entry) = blame
19312 .update(cx, |blame, cx| {
19313 blame
19314 .blame_for_rows(
19315 &[RowInfo {
19316 buffer_id: Some(buffer.remote_id()),
19317 buffer_row: Some(point.row),
19318 ..Default::default()
19319 }],
19320 cx,
19321 )
19322 .next()
19323 })
19324 .flatten()?;
19325 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19326 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19327 let workspace = self.workspace()?.downgrade();
19328 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19329 None
19330 }
19331
19332 pub fn git_blame_inline_enabled(&self) -> bool {
19333 self.git_blame_inline_enabled
19334 }
19335
19336 pub fn toggle_selection_menu(
19337 &mut self,
19338 _: &ToggleSelectionMenu,
19339 _: &mut Window,
19340 cx: &mut Context<Self>,
19341 ) {
19342 self.show_selection_menu = self
19343 .show_selection_menu
19344 .map(|show_selections_menu| !show_selections_menu)
19345 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19346
19347 cx.notify();
19348 }
19349
19350 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19351 self.show_selection_menu
19352 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19353 }
19354
19355 fn start_git_blame(
19356 &mut self,
19357 user_triggered: bool,
19358 window: &mut Window,
19359 cx: &mut Context<Self>,
19360 ) {
19361 if let Some(project) = self.project() {
19362 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19363 && buffer.read(cx).file().is_none()
19364 {
19365 return;
19366 }
19367
19368 let focused = self.focus_handle(cx).contains_focused(window, cx);
19369
19370 let project = project.clone();
19371 let blame = cx
19372 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19373 self.blame_subscription =
19374 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19375 self.blame = Some(blame);
19376 }
19377 }
19378
19379 fn toggle_git_blame_inline_internal(
19380 &mut self,
19381 user_triggered: bool,
19382 window: &mut Window,
19383 cx: &mut Context<Self>,
19384 ) {
19385 if self.git_blame_inline_enabled {
19386 self.git_blame_inline_enabled = false;
19387 self.show_git_blame_inline = false;
19388 self.show_git_blame_inline_delay_task.take();
19389 } else {
19390 self.git_blame_inline_enabled = true;
19391 self.start_git_blame_inline(user_triggered, window, cx);
19392 }
19393
19394 cx.notify();
19395 }
19396
19397 fn start_git_blame_inline(
19398 &mut self,
19399 user_triggered: bool,
19400 window: &mut Window,
19401 cx: &mut Context<Self>,
19402 ) {
19403 self.start_git_blame(user_triggered, window, cx);
19404
19405 if ProjectSettings::get_global(cx)
19406 .git
19407 .inline_blame_delay()
19408 .is_some()
19409 {
19410 self.start_inline_blame_timer(window, cx);
19411 } else {
19412 self.show_git_blame_inline = true
19413 }
19414 }
19415
19416 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19417 self.blame.as_ref()
19418 }
19419
19420 pub fn show_git_blame_gutter(&self) -> bool {
19421 self.show_git_blame_gutter
19422 }
19423
19424 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19425 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19426 }
19427
19428 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19429 self.show_git_blame_inline
19430 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19431 && !self.newest_selection_head_on_empty_line(cx)
19432 && self.has_blame_entries(cx)
19433 }
19434
19435 fn has_blame_entries(&self, cx: &App) -> bool {
19436 self.blame()
19437 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19438 }
19439
19440 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19441 let cursor_anchor = self.selections.newest_anchor().head();
19442
19443 let snapshot = self.buffer.read(cx).snapshot(cx);
19444 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19445
19446 snapshot.line_len(buffer_row) == 0
19447 }
19448
19449 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19450 let buffer_and_selection = maybe!({
19451 let selection = self.selections.newest::<Point>(cx);
19452 let selection_range = selection.range();
19453
19454 let multi_buffer = self.buffer().read(cx);
19455 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19456 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19457
19458 let (buffer, range, _) = if selection.reversed {
19459 buffer_ranges.first()
19460 } else {
19461 buffer_ranges.last()
19462 }?;
19463
19464 let selection = text::ToPoint::to_point(&range.start, buffer).row
19465 ..text::ToPoint::to_point(&range.end, buffer).row;
19466 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19467 });
19468
19469 let Some((buffer, selection)) = buffer_and_selection else {
19470 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19471 };
19472
19473 let Some(project) = self.project() else {
19474 return Task::ready(Err(anyhow!("editor does not have project")));
19475 };
19476
19477 project.update(cx, |project, cx| {
19478 project.get_permalink_to_line(&buffer, selection, cx)
19479 })
19480 }
19481
19482 pub fn copy_permalink_to_line(
19483 &mut self,
19484 _: &CopyPermalinkToLine,
19485 window: &mut Window,
19486 cx: &mut Context<Self>,
19487 ) {
19488 let permalink_task = self.get_permalink_to_line(cx);
19489 let workspace = self.workspace();
19490
19491 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19492 Ok(permalink) => {
19493 cx.update(|_, cx| {
19494 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19495 })
19496 .ok();
19497 }
19498 Err(err) => {
19499 let message = format!("Failed to copy permalink: {err}");
19500
19501 anyhow::Result::<()>::Err(err).log_err();
19502
19503 if let Some(workspace) = workspace {
19504 workspace
19505 .update_in(cx, |workspace, _, cx| {
19506 struct CopyPermalinkToLine;
19507
19508 workspace.show_toast(
19509 Toast::new(
19510 NotificationId::unique::<CopyPermalinkToLine>(),
19511 message,
19512 ),
19513 cx,
19514 )
19515 })
19516 .ok();
19517 }
19518 }
19519 })
19520 .detach();
19521 }
19522
19523 pub fn copy_file_location(
19524 &mut self,
19525 _: &CopyFileLocation,
19526 _: &mut Window,
19527 cx: &mut Context<Self>,
19528 ) {
19529 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19530 if let Some(file) = self.target_file(cx)
19531 && let Some(path) = file.path().to_str()
19532 {
19533 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19534 }
19535 }
19536
19537 pub fn open_permalink_to_line(
19538 &mut self,
19539 _: &OpenPermalinkToLine,
19540 window: &mut Window,
19541 cx: &mut Context<Self>,
19542 ) {
19543 let permalink_task = self.get_permalink_to_line(cx);
19544 let workspace = self.workspace();
19545
19546 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19547 Ok(permalink) => {
19548 cx.update(|_, cx| {
19549 cx.open_url(permalink.as_ref());
19550 })
19551 .ok();
19552 }
19553 Err(err) => {
19554 let message = format!("Failed to open permalink: {err}");
19555
19556 anyhow::Result::<()>::Err(err).log_err();
19557
19558 if let Some(workspace) = workspace {
19559 workspace
19560 .update(cx, |workspace, cx| {
19561 struct OpenPermalinkToLine;
19562
19563 workspace.show_toast(
19564 Toast::new(
19565 NotificationId::unique::<OpenPermalinkToLine>(),
19566 message,
19567 ),
19568 cx,
19569 )
19570 })
19571 .ok();
19572 }
19573 }
19574 })
19575 .detach();
19576 }
19577
19578 pub fn insert_uuid_v4(
19579 &mut self,
19580 _: &InsertUuidV4,
19581 window: &mut Window,
19582 cx: &mut Context<Self>,
19583 ) {
19584 self.insert_uuid(UuidVersion::V4, window, cx);
19585 }
19586
19587 pub fn insert_uuid_v7(
19588 &mut self,
19589 _: &InsertUuidV7,
19590 window: &mut Window,
19591 cx: &mut Context<Self>,
19592 ) {
19593 self.insert_uuid(UuidVersion::V7, window, cx);
19594 }
19595
19596 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19597 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19598 self.transact(window, cx, |this, window, cx| {
19599 let edits = this
19600 .selections
19601 .all::<Point>(cx)
19602 .into_iter()
19603 .map(|selection| {
19604 let uuid = match version {
19605 UuidVersion::V4 => uuid::Uuid::new_v4(),
19606 UuidVersion::V7 => uuid::Uuid::now_v7(),
19607 };
19608
19609 (selection.range(), uuid.to_string())
19610 });
19611 this.edit(edits, cx);
19612 this.refresh_edit_prediction(true, false, window, cx);
19613 });
19614 }
19615
19616 pub fn open_selections_in_multibuffer(
19617 &mut self,
19618 _: &OpenSelectionsInMultibuffer,
19619 window: &mut Window,
19620 cx: &mut Context<Self>,
19621 ) {
19622 let multibuffer = self.buffer.read(cx);
19623
19624 let Some(buffer) = multibuffer.as_singleton() else {
19625 return;
19626 };
19627
19628 let Some(workspace) = self.workspace() else {
19629 return;
19630 };
19631
19632 let title = multibuffer.title(cx).to_string();
19633
19634 let locations = self
19635 .selections
19636 .all_anchors(cx)
19637 .iter()
19638 .map(|selection| Location {
19639 buffer: buffer.clone(),
19640 range: selection.start.text_anchor..selection.end.text_anchor,
19641 })
19642 .collect::<Vec<_>>();
19643
19644 cx.spawn_in(window, async move |_, cx| {
19645 workspace.update_in(cx, |workspace, window, cx| {
19646 Self::open_locations_in_multibuffer(
19647 workspace,
19648 locations,
19649 format!("Selections for '{title}'"),
19650 false,
19651 MultibufferSelectionMode::All,
19652 window,
19653 cx,
19654 );
19655 })
19656 })
19657 .detach();
19658 }
19659
19660 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19661 /// last highlight added will be used.
19662 ///
19663 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19664 pub fn highlight_rows<T: 'static>(
19665 &mut self,
19666 range: Range<Anchor>,
19667 color: Hsla,
19668 options: RowHighlightOptions,
19669 cx: &mut Context<Self>,
19670 ) {
19671 let snapshot = self.buffer().read(cx).snapshot(cx);
19672 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19673 let ix = row_highlights.binary_search_by(|highlight| {
19674 Ordering::Equal
19675 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19676 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19677 });
19678
19679 if let Err(mut ix) = ix {
19680 let index = post_inc(&mut self.highlight_order);
19681
19682 // If this range intersects with the preceding highlight, then merge it with
19683 // the preceding highlight. Otherwise insert a new highlight.
19684 let mut merged = false;
19685 if ix > 0 {
19686 let prev_highlight = &mut row_highlights[ix - 1];
19687 if prev_highlight
19688 .range
19689 .end
19690 .cmp(&range.start, &snapshot)
19691 .is_ge()
19692 {
19693 ix -= 1;
19694 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19695 prev_highlight.range.end = range.end;
19696 }
19697 merged = true;
19698 prev_highlight.index = index;
19699 prev_highlight.color = color;
19700 prev_highlight.options = options;
19701 }
19702 }
19703
19704 if !merged {
19705 row_highlights.insert(
19706 ix,
19707 RowHighlight {
19708 range,
19709 index,
19710 color,
19711 options,
19712 type_id: TypeId::of::<T>(),
19713 },
19714 );
19715 }
19716
19717 // If any of the following highlights intersect with this one, merge them.
19718 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19719 let highlight = &row_highlights[ix];
19720 if next_highlight
19721 .range
19722 .start
19723 .cmp(&highlight.range.end, &snapshot)
19724 .is_le()
19725 {
19726 if next_highlight
19727 .range
19728 .end
19729 .cmp(&highlight.range.end, &snapshot)
19730 .is_gt()
19731 {
19732 row_highlights[ix].range.end = next_highlight.range.end;
19733 }
19734 row_highlights.remove(ix + 1);
19735 } else {
19736 break;
19737 }
19738 }
19739 }
19740 }
19741
19742 /// Remove any highlighted row ranges of the given type that intersect the
19743 /// given ranges.
19744 pub fn remove_highlighted_rows<T: 'static>(
19745 &mut self,
19746 ranges_to_remove: Vec<Range<Anchor>>,
19747 cx: &mut Context<Self>,
19748 ) {
19749 let snapshot = self.buffer().read(cx).snapshot(cx);
19750 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19751 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19752 row_highlights.retain(|highlight| {
19753 while let Some(range_to_remove) = ranges_to_remove.peek() {
19754 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19755 Ordering::Less | Ordering::Equal => {
19756 ranges_to_remove.next();
19757 }
19758 Ordering::Greater => {
19759 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19760 Ordering::Less | Ordering::Equal => {
19761 return false;
19762 }
19763 Ordering::Greater => break,
19764 }
19765 }
19766 }
19767 }
19768
19769 true
19770 })
19771 }
19772
19773 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19774 pub fn clear_row_highlights<T: 'static>(&mut self) {
19775 self.highlighted_rows.remove(&TypeId::of::<T>());
19776 }
19777
19778 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19779 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19780 self.highlighted_rows
19781 .get(&TypeId::of::<T>())
19782 .map_or(&[] as &[_], |vec| vec.as_slice())
19783 .iter()
19784 .map(|highlight| (highlight.range.clone(), highlight.color))
19785 }
19786
19787 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19788 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19789 /// Allows to ignore certain kinds of highlights.
19790 pub fn highlighted_display_rows(
19791 &self,
19792 window: &mut Window,
19793 cx: &mut App,
19794 ) -> BTreeMap<DisplayRow, LineHighlight> {
19795 let snapshot = self.snapshot(window, cx);
19796 let mut used_highlight_orders = HashMap::default();
19797 self.highlighted_rows
19798 .iter()
19799 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19800 .fold(
19801 BTreeMap::<DisplayRow, LineHighlight>::new(),
19802 |mut unique_rows, highlight| {
19803 let start = highlight.range.start.to_display_point(&snapshot);
19804 let end = highlight.range.end.to_display_point(&snapshot);
19805 let start_row = start.row().0;
19806 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19807 && end.column() == 0
19808 {
19809 end.row().0.saturating_sub(1)
19810 } else {
19811 end.row().0
19812 };
19813 for row in start_row..=end_row {
19814 let used_index =
19815 used_highlight_orders.entry(row).or_insert(highlight.index);
19816 if highlight.index >= *used_index {
19817 *used_index = highlight.index;
19818 unique_rows.insert(
19819 DisplayRow(row),
19820 LineHighlight {
19821 include_gutter: highlight.options.include_gutter,
19822 border: None,
19823 background: highlight.color.into(),
19824 type_id: Some(highlight.type_id),
19825 },
19826 );
19827 }
19828 }
19829 unique_rows
19830 },
19831 )
19832 }
19833
19834 pub fn highlighted_display_row_for_autoscroll(
19835 &self,
19836 snapshot: &DisplaySnapshot,
19837 ) -> Option<DisplayRow> {
19838 self.highlighted_rows
19839 .values()
19840 .flat_map(|highlighted_rows| highlighted_rows.iter())
19841 .filter_map(|highlight| {
19842 if highlight.options.autoscroll {
19843 Some(highlight.range.start.to_display_point(snapshot).row())
19844 } else {
19845 None
19846 }
19847 })
19848 .min()
19849 }
19850
19851 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19852 self.highlight_background::<SearchWithinRange>(
19853 ranges,
19854 |colors| colors.colors().editor_document_highlight_read_background,
19855 cx,
19856 )
19857 }
19858
19859 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19860 self.breadcrumb_header = Some(new_header);
19861 }
19862
19863 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19864 self.clear_background_highlights::<SearchWithinRange>(cx);
19865 }
19866
19867 pub fn highlight_background<T: 'static>(
19868 &mut self,
19869 ranges: &[Range<Anchor>],
19870 color_fetcher: fn(&Theme) -> Hsla,
19871 cx: &mut Context<Self>,
19872 ) {
19873 self.background_highlights.insert(
19874 HighlightKey::Type(TypeId::of::<T>()),
19875 (color_fetcher, Arc::from(ranges)),
19876 );
19877 self.scrollbar_marker_state.dirty = true;
19878 cx.notify();
19879 }
19880
19881 pub fn highlight_background_key<T: 'static>(
19882 &mut self,
19883 key: usize,
19884 ranges: &[Range<Anchor>],
19885 color_fetcher: fn(&Theme) -> Hsla,
19886 cx: &mut Context<Self>,
19887 ) {
19888 self.background_highlights.insert(
19889 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19890 (color_fetcher, Arc::from(ranges)),
19891 );
19892 self.scrollbar_marker_state.dirty = true;
19893 cx.notify();
19894 }
19895
19896 pub fn clear_background_highlights<T: 'static>(
19897 &mut self,
19898 cx: &mut Context<Self>,
19899 ) -> Option<BackgroundHighlight> {
19900 let text_highlights = self
19901 .background_highlights
19902 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19903 if !text_highlights.1.is_empty() {
19904 self.scrollbar_marker_state.dirty = true;
19905 cx.notify();
19906 }
19907 Some(text_highlights)
19908 }
19909
19910 pub fn highlight_gutter<T: 'static>(
19911 &mut self,
19912 ranges: impl Into<Vec<Range<Anchor>>>,
19913 color_fetcher: fn(&App) -> Hsla,
19914 cx: &mut Context<Self>,
19915 ) {
19916 self.gutter_highlights
19917 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19918 cx.notify();
19919 }
19920
19921 pub fn clear_gutter_highlights<T: 'static>(
19922 &mut self,
19923 cx: &mut Context<Self>,
19924 ) -> Option<GutterHighlight> {
19925 cx.notify();
19926 self.gutter_highlights.remove(&TypeId::of::<T>())
19927 }
19928
19929 pub fn insert_gutter_highlight<T: 'static>(
19930 &mut self,
19931 range: Range<Anchor>,
19932 color_fetcher: fn(&App) -> Hsla,
19933 cx: &mut Context<Self>,
19934 ) {
19935 let snapshot = self.buffer().read(cx).snapshot(cx);
19936 let mut highlights = self
19937 .gutter_highlights
19938 .remove(&TypeId::of::<T>())
19939 .map(|(_, highlights)| highlights)
19940 .unwrap_or_default();
19941 let ix = highlights.binary_search_by(|highlight| {
19942 Ordering::Equal
19943 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19944 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19945 });
19946 if let Err(ix) = ix {
19947 highlights.insert(ix, range);
19948 }
19949 self.gutter_highlights
19950 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19951 }
19952
19953 pub fn remove_gutter_highlights<T: 'static>(
19954 &mut self,
19955 ranges_to_remove: Vec<Range<Anchor>>,
19956 cx: &mut Context<Self>,
19957 ) {
19958 let snapshot = self.buffer().read(cx).snapshot(cx);
19959 let Some((color_fetcher, mut gutter_highlights)) =
19960 self.gutter_highlights.remove(&TypeId::of::<T>())
19961 else {
19962 return;
19963 };
19964 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19965 gutter_highlights.retain(|highlight| {
19966 while let Some(range_to_remove) = ranges_to_remove.peek() {
19967 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19968 Ordering::Less | Ordering::Equal => {
19969 ranges_to_remove.next();
19970 }
19971 Ordering::Greater => {
19972 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19973 Ordering::Less | Ordering::Equal => {
19974 return false;
19975 }
19976 Ordering::Greater => break,
19977 }
19978 }
19979 }
19980 }
19981
19982 true
19983 });
19984 self.gutter_highlights
19985 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19986 }
19987
19988 #[cfg(feature = "test-support")]
19989 pub fn all_text_highlights(
19990 &self,
19991 window: &mut Window,
19992 cx: &mut Context<Self>,
19993 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19994 let snapshot = self.snapshot(window, cx);
19995 self.display_map.update(cx, |display_map, _| {
19996 display_map
19997 .all_text_highlights()
19998 .map(|highlight| {
19999 let (style, ranges) = highlight.as_ref();
20000 (
20001 *style,
20002 ranges
20003 .iter()
20004 .map(|range| range.clone().to_display_points(&snapshot))
20005 .collect(),
20006 )
20007 })
20008 .collect()
20009 })
20010 }
20011
20012 #[cfg(feature = "test-support")]
20013 pub fn all_text_background_highlights(
20014 &self,
20015 window: &mut Window,
20016 cx: &mut Context<Self>,
20017 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20018 let snapshot = self.snapshot(window, cx);
20019 let buffer = &snapshot.buffer_snapshot;
20020 let start = buffer.anchor_before(0);
20021 let end = buffer.anchor_after(buffer.len());
20022 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20023 }
20024
20025 #[cfg(any(test, feature = "test-support"))]
20026 pub fn sorted_background_highlights_in_range(
20027 &self,
20028 search_range: Range<Anchor>,
20029 display_snapshot: &DisplaySnapshot,
20030 theme: &Theme,
20031 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20032 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20033 res.sort_by(|a, b| {
20034 a.0.start
20035 .cmp(&b.0.start)
20036 .then_with(|| a.0.end.cmp(&b.0.end))
20037 .then_with(|| a.1.cmp(&b.1))
20038 });
20039 res
20040 }
20041
20042 #[cfg(feature = "test-support")]
20043 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20044 let snapshot = self.buffer().read(cx).snapshot(cx);
20045
20046 let highlights = self
20047 .background_highlights
20048 .get(&HighlightKey::Type(TypeId::of::<
20049 items::BufferSearchHighlights,
20050 >()));
20051
20052 if let Some((_color, ranges)) = highlights {
20053 ranges
20054 .iter()
20055 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20056 .collect_vec()
20057 } else {
20058 vec![]
20059 }
20060 }
20061
20062 fn document_highlights_for_position<'a>(
20063 &'a self,
20064 position: Anchor,
20065 buffer: &'a MultiBufferSnapshot,
20066 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20067 let read_highlights = self
20068 .background_highlights
20069 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20070 .map(|h| &h.1);
20071 let write_highlights = self
20072 .background_highlights
20073 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20074 .map(|h| &h.1);
20075 let left_position = position.bias_left(buffer);
20076 let right_position = position.bias_right(buffer);
20077 read_highlights
20078 .into_iter()
20079 .chain(write_highlights)
20080 .flat_map(move |ranges| {
20081 let start_ix = match ranges.binary_search_by(|probe| {
20082 let cmp = probe.end.cmp(&left_position, buffer);
20083 if cmp.is_ge() {
20084 Ordering::Greater
20085 } else {
20086 Ordering::Less
20087 }
20088 }) {
20089 Ok(i) | Err(i) => i,
20090 };
20091
20092 ranges[start_ix..]
20093 .iter()
20094 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20095 })
20096 }
20097
20098 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20099 self.background_highlights
20100 .get(&HighlightKey::Type(TypeId::of::<T>()))
20101 .is_some_and(|(_, highlights)| !highlights.is_empty())
20102 }
20103
20104 /// Returns all background highlights for a given range.
20105 ///
20106 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20107 pub fn background_highlights_in_range(
20108 &self,
20109 search_range: Range<Anchor>,
20110 display_snapshot: &DisplaySnapshot,
20111 theme: &Theme,
20112 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20113 let mut results = Vec::new();
20114 for (color_fetcher, ranges) in self.background_highlights.values() {
20115 let color = color_fetcher(theme);
20116 let start_ix = match ranges.binary_search_by(|probe| {
20117 let cmp = probe
20118 .end
20119 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20120 if cmp.is_gt() {
20121 Ordering::Greater
20122 } else {
20123 Ordering::Less
20124 }
20125 }) {
20126 Ok(i) | Err(i) => i,
20127 };
20128 for range in &ranges[start_ix..] {
20129 if range
20130 .start
20131 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20132 .is_ge()
20133 {
20134 break;
20135 }
20136
20137 let start = range.start.to_display_point(display_snapshot);
20138 let end = range.end.to_display_point(display_snapshot);
20139 results.push((start..end, color))
20140 }
20141 }
20142 results
20143 }
20144
20145 pub fn gutter_highlights_in_range(
20146 &self,
20147 search_range: Range<Anchor>,
20148 display_snapshot: &DisplaySnapshot,
20149 cx: &App,
20150 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20151 let mut results = Vec::new();
20152 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20153 let color = color_fetcher(cx);
20154 let start_ix = match ranges.binary_search_by(|probe| {
20155 let cmp = probe
20156 .end
20157 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20158 if cmp.is_gt() {
20159 Ordering::Greater
20160 } else {
20161 Ordering::Less
20162 }
20163 }) {
20164 Ok(i) | Err(i) => i,
20165 };
20166 for range in &ranges[start_ix..] {
20167 if range
20168 .start
20169 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20170 .is_ge()
20171 {
20172 break;
20173 }
20174
20175 let start = range.start.to_display_point(display_snapshot);
20176 let end = range.end.to_display_point(display_snapshot);
20177 results.push((start..end, color))
20178 }
20179 }
20180 results
20181 }
20182
20183 /// Get the text ranges corresponding to the redaction query
20184 pub fn redacted_ranges(
20185 &self,
20186 search_range: Range<Anchor>,
20187 display_snapshot: &DisplaySnapshot,
20188 cx: &App,
20189 ) -> Vec<Range<DisplayPoint>> {
20190 display_snapshot
20191 .buffer_snapshot
20192 .redacted_ranges(search_range, |file| {
20193 if let Some(file) = file {
20194 file.is_private()
20195 && EditorSettings::get(
20196 Some(SettingsLocation {
20197 worktree_id: file.worktree_id(cx),
20198 path: file.path().as_ref(),
20199 }),
20200 cx,
20201 )
20202 .redact_private_values
20203 } else {
20204 false
20205 }
20206 })
20207 .map(|range| {
20208 range.start.to_display_point(display_snapshot)
20209 ..range.end.to_display_point(display_snapshot)
20210 })
20211 .collect()
20212 }
20213
20214 pub fn highlight_text_key<T: 'static>(
20215 &mut self,
20216 key: usize,
20217 ranges: Vec<Range<Anchor>>,
20218 style: HighlightStyle,
20219 cx: &mut Context<Self>,
20220 ) {
20221 self.display_map.update(cx, |map, _| {
20222 map.highlight_text(
20223 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20224 ranges,
20225 style,
20226 );
20227 });
20228 cx.notify();
20229 }
20230
20231 pub fn highlight_text<T: 'static>(
20232 &mut self,
20233 ranges: Vec<Range<Anchor>>,
20234 style: HighlightStyle,
20235 cx: &mut Context<Self>,
20236 ) {
20237 self.display_map.update(cx, |map, _| {
20238 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20239 });
20240 cx.notify();
20241 }
20242
20243 pub(crate) fn highlight_inlays<T: 'static>(
20244 &mut self,
20245 highlights: Vec<InlayHighlight>,
20246 style: HighlightStyle,
20247 cx: &mut Context<Self>,
20248 ) {
20249 self.display_map.update(cx, |map, _| {
20250 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20251 });
20252 cx.notify();
20253 }
20254
20255 pub fn text_highlights<'a, T: 'static>(
20256 &'a self,
20257 cx: &'a App,
20258 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20259 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20260 }
20261
20262 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20263 let cleared = self
20264 .display_map
20265 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20266 if cleared {
20267 cx.notify();
20268 }
20269 }
20270
20271 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20272 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20273 && self.focus_handle.is_focused(window)
20274 }
20275
20276 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20277 self.show_cursor_when_unfocused = is_enabled;
20278 cx.notify();
20279 }
20280
20281 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20282 cx.notify();
20283 }
20284
20285 fn on_debug_session_event(
20286 &mut self,
20287 _session: Entity<Session>,
20288 event: &SessionEvent,
20289 cx: &mut Context<Self>,
20290 ) {
20291 if let SessionEvent::InvalidateInlineValue = event {
20292 self.refresh_inline_values(cx);
20293 }
20294 }
20295
20296 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20297 let Some(project) = self.project.clone() else {
20298 return;
20299 };
20300
20301 if !self.inline_value_cache.enabled {
20302 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20303 self.splice_inlays(&inlays, Vec::new(), cx);
20304 return;
20305 }
20306
20307 let current_execution_position = self
20308 .highlighted_rows
20309 .get(&TypeId::of::<ActiveDebugLine>())
20310 .and_then(|lines| lines.last().map(|line| line.range.end));
20311
20312 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20313 let inline_values = editor
20314 .update(cx, |editor, cx| {
20315 let Some(current_execution_position) = current_execution_position else {
20316 return Some(Task::ready(Ok(Vec::new())));
20317 };
20318
20319 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20320 let snapshot = buffer.snapshot(cx);
20321
20322 let excerpt = snapshot.excerpt_containing(
20323 current_execution_position..current_execution_position,
20324 )?;
20325
20326 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20327 })?;
20328
20329 let range =
20330 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20331
20332 project.inline_values(buffer, range, cx)
20333 })
20334 .ok()
20335 .flatten()?
20336 .await
20337 .context("refreshing debugger inlays")
20338 .log_err()?;
20339
20340 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20341
20342 for (buffer_id, inline_value) in inline_values
20343 .into_iter()
20344 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20345 {
20346 buffer_inline_values
20347 .entry(buffer_id)
20348 .or_default()
20349 .push(inline_value);
20350 }
20351
20352 editor
20353 .update(cx, |editor, cx| {
20354 let snapshot = editor.buffer.read(cx).snapshot(cx);
20355 let mut new_inlays = Vec::default();
20356
20357 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20358 let buffer_id = buffer_snapshot.remote_id();
20359 buffer_inline_values
20360 .get(&buffer_id)
20361 .into_iter()
20362 .flatten()
20363 .for_each(|hint| {
20364 let inlay = Inlay::debugger(
20365 post_inc(&mut editor.next_inlay_id),
20366 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20367 hint.text(),
20368 );
20369 if !inlay.text.chars().contains(&'\n') {
20370 new_inlays.push(inlay);
20371 }
20372 });
20373 }
20374
20375 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20376 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20377
20378 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20379 })
20380 .ok()?;
20381 Some(())
20382 });
20383 }
20384
20385 fn on_buffer_event(
20386 &mut self,
20387 multibuffer: &Entity<MultiBuffer>,
20388 event: &multi_buffer::Event,
20389 window: &mut Window,
20390 cx: &mut Context<Self>,
20391 ) {
20392 match event {
20393 multi_buffer::Event::Edited {
20394 singleton_buffer_edited,
20395 edited_buffer,
20396 } => {
20397 self.scrollbar_marker_state.dirty = true;
20398 self.active_indent_guides_state.dirty = true;
20399 self.refresh_active_diagnostics(cx);
20400 self.refresh_code_actions(window, cx);
20401 self.refresh_selected_text_highlights(true, window, cx);
20402 self.refresh_single_line_folds(window, cx);
20403 refresh_matching_bracket_highlights(self, window, cx);
20404 if self.has_active_edit_prediction() {
20405 self.update_visible_edit_prediction(window, cx);
20406 }
20407 if let Some(project) = self.project.as_ref()
20408 && let Some(edited_buffer) = edited_buffer
20409 {
20410 project.update(cx, |project, cx| {
20411 self.registered_buffers
20412 .entry(edited_buffer.read(cx).remote_id())
20413 .or_insert_with(|| {
20414 project.register_buffer_with_language_servers(edited_buffer, cx)
20415 });
20416 });
20417 }
20418 cx.emit(EditorEvent::BufferEdited);
20419 cx.emit(SearchEvent::MatchesInvalidated);
20420
20421 if let Some(buffer) = edited_buffer {
20422 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20423 }
20424
20425 if *singleton_buffer_edited {
20426 if let Some(buffer) = edited_buffer
20427 && buffer.read(cx).file().is_none()
20428 {
20429 cx.emit(EditorEvent::TitleChanged);
20430 }
20431 if let Some(project) = &self.project {
20432 #[allow(clippy::mutable_key_type)]
20433 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20434 multibuffer
20435 .all_buffers()
20436 .into_iter()
20437 .filter_map(|buffer| {
20438 buffer.update(cx, |buffer, cx| {
20439 let language = buffer.language()?;
20440 let should_discard = project.update(cx, |project, cx| {
20441 project.is_local()
20442 && !project.has_language_servers_for(buffer, cx)
20443 });
20444 should_discard.not().then_some(language.clone())
20445 })
20446 })
20447 .collect::<HashSet<_>>()
20448 });
20449 if !languages_affected.is_empty() {
20450 self.refresh_inlay_hints(
20451 InlayHintRefreshReason::BufferEdited(languages_affected),
20452 cx,
20453 );
20454 }
20455 }
20456 }
20457
20458 let Some(project) = &self.project else { return };
20459 let (telemetry, is_via_ssh) = {
20460 let project = project.read(cx);
20461 let telemetry = project.client().telemetry().clone();
20462 let is_via_ssh = project.is_via_remote_server();
20463 (telemetry, is_via_ssh)
20464 };
20465 refresh_linked_ranges(self, window, cx);
20466 telemetry.log_edit_event("editor", is_via_ssh);
20467 }
20468 multi_buffer::Event::ExcerptsAdded {
20469 buffer,
20470 predecessor,
20471 excerpts,
20472 } => {
20473 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20474 let buffer_id = buffer.read(cx).remote_id();
20475 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20476 && let Some(project) = &self.project
20477 {
20478 update_uncommitted_diff_for_buffer(
20479 cx.entity(),
20480 project,
20481 [buffer.clone()],
20482 self.buffer.clone(),
20483 cx,
20484 )
20485 .detach();
20486 }
20487 self.update_lsp_data(false, Some(buffer_id), window, cx);
20488 cx.emit(EditorEvent::ExcerptsAdded {
20489 buffer: buffer.clone(),
20490 predecessor: *predecessor,
20491 excerpts: excerpts.clone(),
20492 });
20493 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20494 }
20495 multi_buffer::Event::ExcerptsRemoved {
20496 ids,
20497 removed_buffer_ids,
20498 } => {
20499 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20500 let buffer = self.buffer.read(cx);
20501 self.registered_buffers
20502 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20503 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20504 cx.emit(EditorEvent::ExcerptsRemoved {
20505 ids: ids.clone(),
20506 removed_buffer_ids: removed_buffer_ids.clone(),
20507 });
20508 }
20509 multi_buffer::Event::ExcerptsEdited {
20510 excerpt_ids,
20511 buffer_ids,
20512 } => {
20513 self.display_map.update(cx, |map, cx| {
20514 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20515 });
20516 cx.emit(EditorEvent::ExcerptsEdited {
20517 ids: excerpt_ids.clone(),
20518 });
20519 }
20520 multi_buffer::Event::ExcerptsExpanded { ids } => {
20521 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20522 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20523 }
20524 multi_buffer::Event::Reparsed(buffer_id) => {
20525 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20526 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20527
20528 cx.emit(EditorEvent::Reparsed(*buffer_id));
20529 }
20530 multi_buffer::Event::DiffHunksToggled => {
20531 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20532 }
20533 multi_buffer::Event::LanguageChanged(buffer_id) => {
20534 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20535 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20536 cx.emit(EditorEvent::Reparsed(*buffer_id));
20537 cx.notify();
20538 }
20539 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20540 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20541 multi_buffer::Event::FileHandleChanged
20542 | multi_buffer::Event::Reloaded
20543 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20544 multi_buffer::Event::DiagnosticsUpdated => {
20545 self.update_diagnostics_state(window, cx);
20546 }
20547 _ => {}
20548 };
20549 }
20550
20551 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20552 if !self.diagnostics_enabled() {
20553 return;
20554 }
20555 self.refresh_active_diagnostics(cx);
20556 self.refresh_inline_diagnostics(true, window, cx);
20557 self.scrollbar_marker_state.dirty = true;
20558 cx.notify();
20559 }
20560
20561 pub fn start_temporary_diff_override(&mut self) {
20562 self.load_diff_task.take();
20563 self.temporary_diff_override = true;
20564 }
20565
20566 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20567 self.temporary_diff_override = false;
20568 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20569 self.buffer.update(cx, |buffer, cx| {
20570 buffer.set_all_diff_hunks_collapsed(cx);
20571 });
20572
20573 if let Some(project) = self.project.clone() {
20574 self.load_diff_task = Some(
20575 update_uncommitted_diff_for_buffer(
20576 cx.entity(),
20577 &project,
20578 self.buffer.read(cx).all_buffers(),
20579 self.buffer.clone(),
20580 cx,
20581 )
20582 .shared(),
20583 );
20584 }
20585 }
20586
20587 fn on_display_map_changed(
20588 &mut self,
20589 _: Entity<DisplayMap>,
20590 _: &mut Window,
20591 cx: &mut Context<Self>,
20592 ) {
20593 cx.notify();
20594 }
20595
20596 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20597 if self.diagnostics_enabled() {
20598 let new_severity = EditorSettings::get_global(cx)
20599 .diagnostics_max_severity
20600 .unwrap_or(DiagnosticSeverity::Hint);
20601 self.set_max_diagnostics_severity(new_severity, cx);
20602 }
20603 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20604 self.update_edit_prediction_settings(cx);
20605 self.refresh_edit_prediction(true, false, window, cx);
20606 self.refresh_inline_values(cx);
20607 self.refresh_inlay_hints(
20608 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20609 self.selections.newest_anchor().head(),
20610 &self.buffer.read(cx).snapshot(cx),
20611 cx,
20612 )),
20613 cx,
20614 );
20615
20616 let old_cursor_shape = self.cursor_shape;
20617 let old_show_breadcrumbs = self.show_breadcrumbs;
20618
20619 {
20620 let editor_settings = EditorSettings::get_global(cx);
20621 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20622 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20623 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20624 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20625 }
20626
20627 if old_cursor_shape != self.cursor_shape {
20628 cx.emit(EditorEvent::CursorShapeChanged);
20629 }
20630
20631 if old_show_breadcrumbs != self.show_breadcrumbs {
20632 cx.emit(EditorEvent::BreadcrumbsChanged);
20633 }
20634
20635 let project_settings = ProjectSettings::get_global(cx);
20636 self.serialize_dirty_buffers =
20637 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20638
20639 if self.mode.is_full() {
20640 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20641 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20642 if self.show_inline_diagnostics != show_inline_diagnostics {
20643 self.show_inline_diagnostics = show_inline_diagnostics;
20644 self.refresh_inline_diagnostics(false, window, cx);
20645 }
20646
20647 if self.git_blame_inline_enabled != inline_blame_enabled {
20648 self.toggle_git_blame_inline_internal(false, window, cx);
20649 }
20650
20651 let minimap_settings = EditorSettings::get_global(cx).minimap;
20652 if self.minimap_visibility != MinimapVisibility::Disabled {
20653 if self.minimap_visibility.settings_visibility()
20654 != minimap_settings.minimap_enabled()
20655 {
20656 self.set_minimap_visibility(
20657 MinimapVisibility::for_mode(self.mode(), cx),
20658 window,
20659 cx,
20660 );
20661 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20662 minimap_entity.update(cx, |minimap_editor, cx| {
20663 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20664 })
20665 }
20666 }
20667 }
20668
20669 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20670 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20671 }) {
20672 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20673 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20674 }
20675 self.refresh_colors(false, None, window, cx);
20676 }
20677
20678 cx.notify();
20679 }
20680
20681 pub fn set_searchable(&mut self, searchable: bool) {
20682 self.searchable = searchable;
20683 }
20684
20685 pub fn searchable(&self) -> bool {
20686 self.searchable
20687 }
20688
20689 fn open_proposed_changes_editor(
20690 &mut self,
20691 _: &OpenProposedChangesEditor,
20692 window: &mut Window,
20693 cx: &mut Context<Self>,
20694 ) {
20695 let Some(workspace) = self.workspace() else {
20696 cx.propagate();
20697 return;
20698 };
20699
20700 let selections = self.selections.all::<usize>(cx);
20701 let multi_buffer = self.buffer.read(cx);
20702 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20703 let mut new_selections_by_buffer = HashMap::default();
20704 for selection in selections {
20705 for (buffer, range, _) in
20706 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20707 {
20708 let mut range = range.to_point(buffer);
20709 range.start.column = 0;
20710 range.end.column = buffer.line_len(range.end.row);
20711 new_selections_by_buffer
20712 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20713 .or_insert(Vec::new())
20714 .push(range)
20715 }
20716 }
20717
20718 let proposed_changes_buffers = new_selections_by_buffer
20719 .into_iter()
20720 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20721 .collect::<Vec<_>>();
20722 let proposed_changes_editor = cx.new(|cx| {
20723 ProposedChangesEditor::new(
20724 "Proposed changes",
20725 proposed_changes_buffers,
20726 self.project.clone(),
20727 window,
20728 cx,
20729 )
20730 });
20731
20732 window.defer(cx, move |window, cx| {
20733 workspace.update(cx, |workspace, cx| {
20734 workspace.active_pane().update(cx, |pane, cx| {
20735 pane.add_item(
20736 Box::new(proposed_changes_editor),
20737 true,
20738 true,
20739 None,
20740 window,
20741 cx,
20742 );
20743 });
20744 });
20745 });
20746 }
20747
20748 pub fn open_excerpts_in_split(
20749 &mut self,
20750 _: &OpenExcerptsSplit,
20751 window: &mut Window,
20752 cx: &mut Context<Self>,
20753 ) {
20754 self.open_excerpts_common(None, true, window, cx)
20755 }
20756
20757 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20758 self.open_excerpts_common(None, false, window, cx)
20759 }
20760
20761 fn open_excerpts_common(
20762 &mut self,
20763 jump_data: Option<JumpData>,
20764 split: bool,
20765 window: &mut Window,
20766 cx: &mut Context<Self>,
20767 ) {
20768 let Some(workspace) = self.workspace() else {
20769 cx.propagate();
20770 return;
20771 };
20772
20773 if self.buffer.read(cx).is_singleton() {
20774 cx.propagate();
20775 return;
20776 }
20777
20778 let mut new_selections_by_buffer = HashMap::default();
20779 match &jump_data {
20780 Some(JumpData::MultiBufferPoint {
20781 excerpt_id,
20782 position,
20783 anchor,
20784 line_offset_from_top,
20785 }) => {
20786 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20787 if let Some(buffer) = multi_buffer_snapshot
20788 .buffer_id_for_excerpt(*excerpt_id)
20789 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20790 {
20791 let buffer_snapshot = buffer.read(cx).snapshot();
20792 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20793 language::ToPoint::to_point(anchor, &buffer_snapshot)
20794 } else {
20795 buffer_snapshot.clip_point(*position, Bias::Left)
20796 };
20797 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20798 new_selections_by_buffer.insert(
20799 buffer,
20800 (
20801 vec![jump_to_offset..jump_to_offset],
20802 Some(*line_offset_from_top),
20803 ),
20804 );
20805 }
20806 }
20807 Some(JumpData::MultiBufferRow {
20808 row,
20809 line_offset_from_top,
20810 }) => {
20811 let point = MultiBufferPoint::new(row.0, 0);
20812 if let Some((buffer, buffer_point, _)) =
20813 self.buffer.read(cx).point_to_buffer_point(point, cx)
20814 {
20815 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20816 new_selections_by_buffer
20817 .entry(buffer)
20818 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20819 .0
20820 .push(buffer_offset..buffer_offset)
20821 }
20822 }
20823 None => {
20824 let selections = self.selections.all::<usize>(cx);
20825 let multi_buffer = self.buffer.read(cx);
20826 for selection in selections {
20827 for (snapshot, range, _, anchor) in multi_buffer
20828 .snapshot(cx)
20829 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20830 {
20831 if let Some(anchor) = anchor {
20832 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20833 else {
20834 continue;
20835 };
20836 let offset = text::ToOffset::to_offset(
20837 &anchor.text_anchor,
20838 &buffer_handle.read(cx).snapshot(),
20839 );
20840 let range = offset..offset;
20841 new_selections_by_buffer
20842 .entry(buffer_handle)
20843 .or_insert((Vec::new(), None))
20844 .0
20845 .push(range)
20846 } else {
20847 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20848 else {
20849 continue;
20850 };
20851 new_selections_by_buffer
20852 .entry(buffer_handle)
20853 .or_insert((Vec::new(), None))
20854 .0
20855 .push(range)
20856 }
20857 }
20858 }
20859 }
20860 }
20861
20862 new_selections_by_buffer
20863 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20864
20865 if new_selections_by_buffer.is_empty() {
20866 return;
20867 }
20868
20869 // We defer the pane interaction because we ourselves are a workspace item
20870 // and activating a new item causes the pane to call a method on us reentrantly,
20871 // which panics if we're on the stack.
20872 window.defer(cx, move |window, cx| {
20873 workspace.update(cx, |workspace, cx| {
20874 let pane = if split {
20875 workspace.adjacent_pane(window, cx)
20876 } else {
20877 workspace.active_pane().clone()
20878 };
20879
20880 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20881 let editor = buffer
20882 .read(cx)
20883 .file()
20884 .is_none()
20885 .then(|| {
20886 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20887 // so `workspace.open_project_item` will never find them, always opening a new editor.
20888 // Instead, we try to activate the existing editor in the pane first.
20889 let (editor, pane_item_index) =
20890 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20891 let editor = item.downcast::<Editor>()?;
20892 let singleton_buffer =
20893 editor.read(cx).buffer().read(cx).as_singleton()?;
20894 if singleton_buffer == buffer {
20895 Some((editor, i))
20896 } else {
20897 None
20898 }
20899 })?;
20900 pane.update(cx, |pane, cx| {
20901 pane.activate_item(pane_item_index, true, true, window, cx)
20902 });
20903 Some(editor)
20904 })
20905 .flatten()
20906 .unwrap_or_else(|| {
20907 workspace.open_project_item::<Self>(
20908 pane.clone(),
20909 buffer,
20910 true,
20911 true,
20912 window,
20913 cx,
20914 )
20915 });
20916
20917 editor.update(cx, |editor, cx| {
20918 let autoscroll = match scroll_offset {
20919 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20920 None => Autoscroll::newest(),
20921 };
20922 let nav_history = editor.nav_history.take();
20923 editor.change_selections(
20924 SelectionEffects::scroll(autoscroll),
20925 window,
20926 cx,
20927 |s| {
20928 s.select_ranges(ranges);
20929 },
20930 );
20931 editor.nav_history = nav_history;
20932 });
20933 }
20934 })
20935 });
20936 }
20937
20938 // For now, don't allow opening excerpts in buffers that aren't backed by
20939 // regular project files.
20940 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20941 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20942 }
20943
20944 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20945 let snapshot = self.buffer.read(cx).read(cx);
20946 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20947 Some(
20948 ranges
20949 .iter()
20950 .map(move |range| {
20951 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20952 })
20953 .collect(),
20954 )
20955 }
20956
20957 fn selection_replacement_ranges(
20958 &self,
20959 range: Range<OffsetUtf16>,
20960 cx: &mut App,
20961 ) -> Vec<Range<OffsetUtf16>> {
20962 let selections = self.selections.all::<OffsetUtf16>(cx);
20963 let newest_selection = selections
20964 .iter()
20965 .max_by_key(|selection| selection.id)
20966 .unwrap();
20967 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20968 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20969 let snapshot = self.buffer.read(cx).read(cx);
20970 selections
20971 .into_iter()
20972 .map(|mut selection| {
20973 selection.start.0 =
20974 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20975 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20976 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20977 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20978 })
20979 .collect()
20980 }
20981
20982 fn report_editor_event(
20983 &self,
20984 reported_event: ReportEditorEvent,
20985 file_extension: Option<String>,
20986 cx: &App,
20987 ) {
20988 if cfg!(any(test, feature = "test-support")) {
20989 return;
20990 }
20991
20992 let Some(project) = &self.project else { return };
20993
20994 // If None, we are in a file without an extension
20995 let file = self
20996 .buffer
20997 .read(cx)
20998 .as_singleton()
20999 .and_then(|b| b.read(cx).file());
21000 let file_extension = file_extension.or(file
21001 .as_ref()
21002 .and_then(|file| Path::new(file.file_name(cx)).extension())
21003 .and_then(|e| e.to_str())
21004 .map(|a| a.to_string()));
21005
21006 let vim_mode = vim_enabled(cx);
21007
21008 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21009 let copilot_enabled = edit_predictions_provider
21010 == language::language_settings::EditPredictionProvider::Copilot;
21011 let copilot_enabled_for_language = self
21012 .buffer
21013 .read(cx)
21014 .language_settings(cx)
21015 .show_edit_predictions;
21016
21017 let project = project.read(cx);
21018 let event_type = reported_event.event_type();
21019
21020 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21021 telemetry::event!(
21022 event_type,
21023 type = if auto_saved {"autosave"} else {"manual"},
21024 file_extension,
21025 vim_mode,
21026 copilot_enabled,
21027 copilot_enabled_for_language,
21028 edit_predictions_provider,
21029 is_via_ssh = project.is_via_remote_server(),
21030 );
21031 } else {
21032 telemetry::event!(
21033 event_type,
21034 file_extension,
21035 vim_mode,
21036 copilot_enabled,
21037 copilot_enabled_for_language,
21038 edit_predictions_provider,
21039 is_via_ssh = project.is_via_remote_server(),
21040 );
21041 };
21042 }
21043
21044 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21045 /// with each line being an array of {text, highlight} objects.
21046 fn copy_highlight_json(
21047 &mut self,
21048 _: &CopyHighlightJson,
21049 window: &mut Window,
21050 cx: &mut Context<Self>,
21051 ) {
21052 #[derive(Serialize)]
21053 struct Chunk<'a> {
21054 text: String,
21055 highlight: Option<&'a str>,
21056 }
21057
21058 let snapshot = self.buffer.read(cx).snapshot(cx);
21059 let range = self
21060 .selected_text_range(false, window, cx)
21061 .and_then(|selection| {
21062 if selection.range.is_empty() {
21063 None
21064 } else {
21065 Some(selection.range)
21066 }
21067 })
21068 .unwrap_or_else(|| 0..snapshot.len());
21069
21070 let chunks = snapshot.chunks(range, true);
21071 let mut lines = Vec::new();
21072 let mut line: VecDeque<Chunk> = VecDeque::new();
21073
21074 let Some(style) = self.style.as_ref() else {
21075 return;
21076 };
21077
21078 for chunk in chunks {
21079 let highlight = chunk
21080 .syntax_highlight_id
21081 .and_then(|id| id.name(&style.syntax));
21082 let mut chunk_lines = chunk.text.split('\n').peekable();
21083 while let Some(text) = chunk_lines.next() {
21084 let mut merged_with_last_token = false;
21085 if let Some(last_token) = line.back_mut()
21086 && last_token.highlight == highlight
21087 {
21088 last_token.text.push_str(text);
21089 merged_with_last_token = true;
21090 }
21091
21092 if !merged_with_last_token {
21093 line.push_back(Chunk {
21094 text: text.into(),
21095 highlight,
21096 });
21097 }
21098
21099 if chunk_lines.peek().is_some() {
21100 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21101 line.pop_front();
21102 }
21103 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21104 line.pop_back();
21105 }
21106
21107 lines.push(mem::take(&mut line));
21108 }
21109 }
21110 }
21111
21112 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21113 return;
21114 };
21115 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21116 }
21117
21118 pub fn open_context_menu(
21119 &mut self,
21120 _: &OpenContextMenu,
21121 window: &mut Window,
21122 cx: &mut Context<Self>,
21123 ) {
21124 self.request_autoscroll(Autoscroll::newest(), cx);
21125 let position = self.selections.newest_display(cx).start;
21126 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21127 }
21128
21129 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21130 &self.inlay_hint_cache
21131 }
21132
21133 pub fn replay_insert_event(
21134 &mut self,
21135 text: &str,
21136 relative_utf16_range: Option<Range<isize>>,
21137 window: &mut Window,
21138 cx: &mut Context<Self>,
21139 ) {
21140 if !self.input_enabled {
21141 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21142 return;
21143 }
21144 if let Some(relative_utf16_range) = relative_utf16_range {
21145 let selections = self.selections.all::<OffsetUtf16>(cx);
21146 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21147 let new_ranges = selections.into_iter().map(|range| {
21148 let start = OffsetUtf16(
21149 range
21150 .head()
21151 .0
21152 .saturating_add_signed(relative_utf16_range.start),
21153 );
21154 let end = OffsetUtf16(
21155 range
21156 .head()
21157 .0
21158 .saturating_add_signed(relative_utf16_range.end),
21159 );
21160 start..end
21161 });
21162 s.select_ranges(new_ranges);
21163 });
21164 }
21165
21166 self.handle_input(text, window, cx);
21167 }
21168
21169 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21170 let Some(provider) = self.semantics_provider.as_ref() else {
21171 return false;
21172 };
21173
21174 let mut supports = false;
21175 self.buffer().update(cx, |this, cx| {
21176 this.for_each_buffer(|buffer| {
21177 supports |= provider.supports_inlay_hints(buffer, cx);
21178 });
21179 });
21180
21181 supports
21182 }
21183
21184 pub fn is_focused(&self, window: &Window) -> bool {
21185 self.focus_handle.is_focused(window)
21186 }
21187
21188 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21189 cx.emit(EditorEvent::Focused);
21190
21191 if let Some(descendant) = self
21192 .last_focused_descendant
21193 .take()
21194 .and_then(|descendant| descendant.upgrade())
21195 {
21196 window.focus(&descendant);
21197 } else {
21198 if let Some(blame) = self.blame.as_ref() {
21199 blame.update(cx, GitBlame::focus)
21200 }
21201
21202 self.blink_manager.update(cx, BlinkManager::enable);
21203 self.show_cursor_names(window, cx);
21204 self.buffer.update(cx, |buffer, cx| {
21205 buffer.finalize_last_transaction(cx);
21206 if self.leader_id.is_none() {
21207 buffer.set_active_selections(
21208 &self.selections.disjoint_anchors(),
21209 self.selections.line_mode,
21210 self.cursor_shape,
21211 cx,
21212 );
21213 }
21214 });
21215 }
21216 }
21217
21218 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21219 cx.emit(EditorEvent::FocusedIn)
21220 }
21221
21222 fn handle_focus_out(
21223 &mut self,
21224 event: FocusOutEvent,
21225 _window: &mut Window,
21226 cx: &mut Context<Self>,
21227 ) {
21228 if event.blurred != self.focus_handle {
21229 self.last_focused_descendant = Some(event.blurred);
21230 }
21231 self.selection_drag_state = SelectionDragState::None;
21232 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21233 }
21234
21235 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21236 self.blink_manager.update(cx, BlinkManager::disable);
21237 self.buffer
21238 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21239
21240 if let Some(blame) = self.blame.as_ref() {
21241 blame.update(cx, GitBlame::blur)
21242 }
21243 if !self.hover_state.focused(window, cx) {
21244 hide_hover(self, cx);
21245 }
21246 if !self
21247 .context_menu
21248 .borrow()
21249 .as_ref()
21250 .is_some_and(|context_menu| context_menu.focused(window, cx))
21251 {
21252 self.hide_context_menu(window, cx);
21253 }
21254 self.discard_edit_prediction(false, cx);
21255 cx.emit(EditorEvent::Blurred);
21256 cx.notify();
21257 }
21258
21259 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21260 let mut pending: String = window
21261 .pending_input_keystrokes()
21262 .into_iter()
21263 .flatten()
21264 .filter_map(|keystroke| {
21265 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21266 keystroke.key_char.clone()
21267 } else {
21268 None
21269 }
21270 })
21271 .collect();
21272
21273 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21274 pending = "".to_string();
21275 }
21276
21277 let existing_pending = self
21278 .text_highlights::<PendingInput>(cx)
21279 .map(|(_, ranges)| ranges.to_vec());
21280 if existing_pending.is_none() && pending.is_empty() {
21281 return;
21282 }
21283 let transaction =
21284 self.transact(window, cx, |this, window, cx| {
21285 let selections = this.selections.all::<usize>(cx);
21286 let edits = selections
21287 .iter()
21288 .map(|selection| (selection.end..selection.end, pending.clone()));
21289 this.edit(edits, cx);
21290 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21291 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21292 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21293 }));
21294 });
21295 if let Some(existing_ranges) = existing_pending {
21296 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21297 this.edit(edits, cx);
21298 }
21299 });
21300
21301 let snapshot = self.snapshot(window, cx);
21302 let ranges = self
21303 .selections
21304 .all::<usize>(cx)
21305 .into_iter()
21306 .map(|selection| {
21307 snapshot.buffer_snapshot.anchor_after(selection.end)
21308 ..snapshot
21309 .buffer_snapshot
21310 .anchor_before(selection.end + pending.len())
21311 })
21312 .collect();
21313
21314 if pending.is_empty() {
21315 self.clear_highlights::<PendingInput>(cx);
21316 } else {
21317 self.highlight_text::<PendingInput>(
21318 ranges,
21319 HighlightStyle {
21320 underline: Some(UnderlineStyle {
21321 thickness: px(1.),
21322 color: None,
21323 wavy: false,
21324 }),
21325 ..Default::default()
21326 },
21327 cx,
21328 );
21329 }
21330
21331 self.ime_transaction = self.ime_transaction.or(transaction);
21332 if let Some(transaction) = self.ime_transaction {
21333 self.buffer.update(cx, |buffer, cx| {
21334 buffer.group_until_transaction(transaction, cx);
21335 });
21336 }
21337
21338 if self.text_highlights::<PendingInput>(cx).is_none() {
21339 self.ime_transaction.take();
21340 }
21341 }
21342
21343 pub fn register_action_renderer(
21344 &mut self,
21345 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21346 ) -> Subscription {
21347 let id = self.next_editor_action_id.post_inc();
21348 self.editor_actions
21349 .borrow_mut()
21350 .insert(id, Box::new(listener));
21351
21352 let editor_actions = self.editor_actions.clone();
21353 Subscription::new(move || {
21354 editor_actions.borrow_mut().remove(&id);
21355 })
21356 }
21357
21358 pub fn register_action<A: Action>(
21359 &mut self,
21360 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21361 ) -> Subscription {
21362 let id = self.next_editor_action_id.post_inc();
21363 let listener = Arc::new(listener);
21364 self.editor_actions.borrow_mut().insert(
21365 id,
21366 Box::new(move |_, window, _| {
21367 let listener = listener.clone();
21368 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21369 let action = action.downcast_ref().unwrap();
21370 if phase == DispatchPhase::Bubble {
21371 listener(action, window, cx)
21372 }
21373 })
21374 }),
21375 );
21376
21377 let editor_actions = self.editor_actions.clone();
21378 Subscription::new(move || {
21379 editor_actions.borrow_mut().remove(&id);
21380 })
21381 }
21382
21383 pub fn file_header_size(&self) -> u32 {
21384 FILE_HEADER_HEIGHT
21385 }
21386
21387 pub fn restore(
21388 &mut self,
21389 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21390 window: &mut Window,
21391 cx: &mut Context<Self>,
21392 ) {
21393 let workspace = self.workspace();
21394 let project = self.project();
21395 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21396 let mut tasks = Vec::new();
21397 for (buffer_id, changes) in revert_changes {
21398 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21399 buffer.update(cx, |buffer, cx| {
21400 buffer.edit(
21401 changes
21402 .into_iter()
21403 .map(|(range, text)| (range, text.to_string())),
21404 None,
21405 cx,
21406 );
21407 });
21408
21409 if let Some(project) =
21410 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21411 {
21412 project.update(cx, |project, cx| {
21413 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21414 })
21415 }
21416 }
21417 }
21418 tasks
21419 });
21420 cx.spawn_in(window, async move |_, cx| {
21421 for (buffer, task) in save_tasks {
21422 let result = task.await;
21423 if result.is_err() {
21424 let Some(path) = buffer
21425 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21426 .ok()
21427 else {
21428 continue;
21429 };
21430 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21431 let Some(task) = cx
21432 .update_window_entity(workspace, |workspace, window, cx| {
21433 workspace
21434 .open_path_preview(path, None, false, false, false, window, cx)
21435 })
21436 .ok()
21437 else {
21438 continue;
21439 };
21440 task.await.log_err();
21441 }
21442 }
21443 }
21444 })
21445 .detach();
21446 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21447 selections.refresh()
21448 });
21449 }
21450
21451 pub fn to_pixel_point(
21452 &self,
21453 source: multi_buffer::Anchor,
21454 editor_snapshot: &EditorSnapshot,
21455 window: &mut Window,
21456 ) -> Option<gpui::Point<Pixels>> {
21457 let source_point = source.to_display_point(editor_snapshot);
21458 self.display_to_pixel_point(source_point, editor_snapshot, window)
21459 }
21460
21461 pub fn display_to_pixel_point(
21462 &self,
21463 source: DisplayPoint,
21464 editor_snapshot: &EditorSnapshot,
21465 window: &mut Window,
21466 ) -> Option<gpui::Point<Pixels>> {
21467 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21468 let text_layout_details = self.text_layout_details(window);
21469 let scroll_top = text_layout_details
21470 .scroll_anchor
21471 .scroll_position(editor_snapshot)
21472 .y;
21473
21474 if source.row().as_f32() < scroll_top.floor() {
21475 return None;
21476 }
21477 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21478 let source_y = line_height * (source.row().as_f32() - scroll_top);
21479 Some(gpui::Point::new(source_x, source_y))
21480 }
21481
21482 pub fn has_visible_completions_menu(&self) -> bool {
21483 !self.edit_prediction_preview_is_active()
21484 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21485 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21486 })
21487 }
21488
21489 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21490 if self.mode.is_minimap() {
21491 return;
21492 }
21493 self.addons
21494 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21495 }
21496
21497 pub fn unregister_addon<T: Addon>(&mut self) {
21498 self.addons.remove(&std::any::TypeId::of::<T>());
21499 }
21500
21501 pub fn addon<T: Addon>(&self) -> Option<&T> {
21502 let type_id = std::any::TypeId::of::<T>();
21503 self.addons
21504 .get(&type_id)
21505 .and_then(|item| item.to_any().downcast_ref::<T>())
21506 }
21507
21508 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21509 let type_id = std::any::TypeId::of::<T>();
21510 self.addons
21511 .get_mut(&type_id)
21512 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21513 }
21514
21515 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21516 let text_layout_details = self.text_layout_details(window);
21517 let style = &text_layout_details.editor_style;
21518 let font_id = window.text_system().resolve_font(&style.text.font());
21519 let font_size = style.text.font_size.to_pixels(window.rem_size());
21520 let line_height = style.text.line_height_in_pixels(window.rem_size());
21521 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21522 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21523
21524 CharacterDimensions {
21525 em_width,
21526 em_advance,
21527 line_height,
21528 }
21529 }
21530
21531 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21532 self.load_diff_task.clone()
21533 }
21534
21535 fn read_metadata_from_db(
21536 &mut self,
21537 item_id: u64,
21538 workspace_id: WorkspaceId,
21539 window: &mut Window,
21540 cx: &mut Context<Editor>,
21541 ) {
21542 if self.is_singleton(cx)
21543 && !self.mode.is_minimap()
21544 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21545 {
21546 let buffer_snapshot = OnceCell::new();
21547
21548 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21549 && !folds.is_empty()
21550 {
21551 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21552 self.fold_ranges(
21553 folds
21554 .into_iter()
21555 .map(|(start, end)| {
21556 snapshot.clip_offset(start, Bias::Left)
21557 ..snapshot.clip_offset(end, Bias::Right)
21558 })
21559 .collect(),
21560 false,
21561 window,
21562 cx,
21563 );
21564 }
21565
21566 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21567 && !selections.is_empty()
21568 {
21569 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21570 // skip adding the initial selection to selection history
21571 self.selection_history.mode = SelectionHistoryMode::Skipping;
21572 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21573 s.select_ranges(selections.into_iter().map(|(start, end)| {
21574 snapshot.clip_offset(start, Bias::Left)
21575 ..snapshot.clip_offset(end, Bias::Right)
21576 }));
21577 });
21578 self.selection_history.mode = SelectionHistoryMode::Normal;
21579 };
21580 }
21581
21582 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21583 }
21584
21585 fn update_lsp_data(
21586 &mut self,
21587 ignore_cache: bool,
21588 for_buffer: Option<BufferId>,
21589 window: &mut Window,
21590 cx: &mut Context<'_, Self>,
21591 ) {
21592 self.pull_diagnostics(for_buffer, window, cx);
21593 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21594 }
21595}
21596
21597fn vim_enabled(cx: &App) -> bool {
21598 cx.global::<SettingsStore>()
21599 .raw_user_settings()
21600 .get("vim_mode")
21601 == Some(&serde_json::Value::Bool(true))
21602}
21603
21604fn process_completion_for_edit(
21605 completion: &Completion,
21606 intent: CompletionIntent,
21607 buffer: &Entity<Buffer>,
21608 cursor_position: &text::Anchor,
21609 cx: &mut Context<Editor>,
21610) -> CompletionEdit {
21611 let buffer = buffer.read(cx);
21612 let buffer_snapshot = buffer.snapshot();
21613 let (snippet, new_text) = if completion.is_snippet() {
21614 // Workaround for typescript language server issues so that methods don't expand within
21615 // strings and functions with type expressions. The previous point is used because the query
21616 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21617 let mut snippet_source = completion.new_text.clone();
21618 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21619 previous_point.column = previous_point.column.saturating_sub(1);
21620 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21621 && scope.prefers_label_for_snippet_in_completion()
21622 && let Some(label) = completion.label()
21623 && matches!(
21624 completion.kind(),
21625 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21626 )
21627 {
21628 snippet_source = label;
21629 }
21630 match Snippet::parse(&snippet_source).log_err() {
21631 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21632 None => (None, completion.new_text.clone()),
21633 }
21634 } else {
21635 (None, completion.new_text.clone())
21636 };
21637
21638 let mut range_to_replace = {
21639 let replace_range = &completion.replace_range;
21640 if let CompletionSource::Lsp {
21641 insert_range: Some(insert_range),
21642 ..
21643 } = &completion.source
21644 {
21645 debug_assert_eq!(
21646 insert_range.start, replace_range.start,
21647 "insert_range and replace_range should start at the same position"
21648 );
21649 debug_assert!(
21650 insert_range
21651 .start
21652 .cmp(cursor_position, &buffer_snapshot)
21653 .is_le(),
21654 "insert_range should start before or at cursor position"
21655 );
21656 debug_assert!(
21657 replace_range
21658 .start
21659 .cmp(cursor_position, &buffer_snapshot)
21660 .is_le(),
21661 "replace_range should start before or at cursor position"
21662 );
21663
21664 let should_replace = match intent {
21665 CompletionIntent::CompleteWithInsert => false,
21666 CompletionIntent::CompleteWithReplace => true,
21667 CompletionIntent::Complete | CompletionIntent::Compose => {
21668 let insert_mode =
21669 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21670 .completions
21671 .lsp_insert_mode;
21672 match insert_mode {
21673 LspInsertMode::Insert => false,
21674 LspInsertMode::Replace => true,
21675 LspInsertMode::ReplaceSubsequence => {
21676 let mut text_to_replace = buffer.chars_for_range(
21677 buffer.anchor_before(replace_range.start)
21678 ..buffer.anchor_after(replace_range.end),
21679 );
21680 let mut current_needle = text_to_replace.next();
21681 for haystack_ch in completion.label.text.chars() {
21682 if let Some(needle_ch) = current_needle
21683 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21684 {
21685 current_needle = text_to_replace.next();
21686 }
21687 }
21688 current_needle.is_none()
21689 }
21690 LspInsertMode::ReplaceSuffix => {
21691 if replace_range
21692 .end
21693 .cmp(cursor_position, &buffer_snapshot)
21694 .is_gt()
21695 {
21696 let range_after_cursor = *cursor_position..replace_range.end;
21697 let text_after_cursor = buffer
21698 .text_for_range(
21699 buffer.anchor_before(range_after_cursor.start)
21700 ..buffer.anchor_after(range_after_cursor.end),
21701 )
21702 .collect::<String>()
21703 .to_ascii_lowercase();
21704 completion
21705 .label
21706 .text
21707 .to_ascii_lowercase()
21708 .ends_with(&text_after_cursor)
21709 } else {
21710 true
21711 }
21712 }
21713 }
21714 }
21715 };
21716
21717 if should_replace {
21718 replace_range.clone()
21719 } else {
21720 insert_range.clone()
21721 }
21722 } else {
21723 replace_range.clone()
21724 }
21725 };
21726
21727 if range_to_replace
21728 .end
21729 .cmp(cursor_position, &buffer_snapshot)
21730 .is_lt()
21731 {
21732 range_to_replace.end = *cursor_position;
21733 }
21734
21735 CompletionEdit {
21736 new_text,
21737 replace_range: range_to_replace.to_offset(buffer),
21738 snippet,
21739 }
21740}
21741
21742struct CompletionEdit {
21743 new_text: String,
21744 replace_range: Range<usize>,
21745 snippet: Option<Snippet>,
21746}
21747
21748fn insert_extra_newline_brackets(
21749 buffer: &MultiBufferSnapshot,
21750 range: Range<usize>,
21751 language: &language::LanguageScope,
21752) -> bool {
21753 let leading_whitespace_len = buffer
21754 .reversed_chars_at(range.start)
21755 .take_while(|c| c.is_whitespace() && *c != '\n')
21756 .map(|c| c.len_utf8())
21757 .sum::<usize>();
21758 let trailing_whitespace_len = buffer
21759 .chars_at(range.end)
21760 .take_while(|c| c.is_whitespace() && *c != '\n')
21761 .map(|c| c.len_utf8())
21762 .sum::<usize>();
21763 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21764
21765 language.brackets().any(|(pair, enabled)| {
21766 let pair_start = pair.start.trim_end();
21767 let pair_end = pair.end.trim_start();
21768
21769 enabled
21770 && pair.newline
21771 && buffer.contains_str_at(range.end, pair_end)
21772 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21773 })
21774}
21775
21776fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21777 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21778 [(buffer, range, _)] => (*buffer, range.clone()),
21779 _ => return false,
21780 };
21781 let pair = {
21782 let mut result: Option<BracketMatch> = None;
21783
21784 for pair in buffer
21785 .all_bracket_ranges(range.clone())
21786 .filter(move |pair| {
21787 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21788 })
21789 {
21790 let len = pair.close_range.end - pair.open_range.start;
21791
21792 if let Some(existing) = &result {
21793 let existing_len = existing.close_range.end - existing.open_range.start;
21794 if len > existing_len {
21795 continue;
21796 }
21797 }
21798
21799 result = Some(pair);
21800 }
21801
21802 result
21803 };
21804 let Some(pair) = pair else {
21805 return false;
21806 };
21807 pair.newline_only
21808 && buffer
21809 .chars_for_range(pair.open_range.end..range.start)
21810 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21811 .all(|c| c.is_whitespace() && c != '\n')
21812}
21813
21814fn update_uncommitted_diff_for_buffer(
21815 editor: Entity<Editor>,
21816 project: &Entity<Project>,
21817 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21818 buffer: Entity<MultiBuffer>,
21819 cx: &mut App,
21820) -> Task<()> {
21821 let mut tasks = Vec::new();
21822 project.update(cx, |project, cx| {
21823 for buffer in buffers {
21824 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21825 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21826 }
21827 }
21828 });
21829 cx.spawn(async move |cx| {
21830 let diffs = future::join_all(tasks).await;
21831 if editor
21832 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21833 .unwrap_or(false)
21834 {
21835 return;
21836 }
21837
21838 buffer
21839 .update(cx, |buffer, cx| {
21840 for diff in diffs.into_iter().flatten() {
21841 buffer.add_diff(diff, cx);
21842 }
21843 })
21844 .ok();
21845 })
21846}
21847
21848fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21849 let tab_size = tab_size.get() as usize;
21850 let mut width = offset;
21851
21852 for ch in text.chars() {
21853 width += if ch == '\t' {
21854 tab_size - (width % tab_size)
21855 } else {
21856 1
21857 };
21858 }
21859
21860 width - offset
21861}
21862
21863#[cfg(test)]
21864mod tests {
21865 use super::*;
21866
21867 #[test]
21868 fn test_string_size_with_expanded_tabs() {
21869 let nz = |val| NonZeroU32::new(val).unwrap();
21870 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21871 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21872 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21873 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21874 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21875 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21876 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21877 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21878 }
21879}
21880
21881/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21882struct WordBreakingTokenizer<'a> {
21883 input: &'a str,
21884}
21885
21886impl<'a> WordBreakingTokenizer<'a> {
21887 fn new(input: &'a str) -> Self {
21888 Self { input }
21889 }
21890}
21891
21892fn is_char_ideographic(ch: char) -> bool {
21893 use unicode_script::Script::*;
21894 use unicode_script::UnicodeScript;
21895 matches!(ch.script(), Han | Tangut | Yi)
21896}
21897
21898fn is_grapheme_ideographic(text: &str) -> bool {
21899 text.chars().any(is_char_ideographic)
21900}
21901
21902fn is_grapheme_whitespace(text: &str) -> bool {
21903 text.chars().any(|x| x.is_whitespace())
21904}
21905
21906fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21907 text.chars()
21908 .next()
21909 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21910}
21911
21912#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21913enum WordBreakToken<'a> {
21914 Word { token: &'a str, grapheme_len: usize },
21915 InlineWhitespace { token: &'a str, grapheme_len: usize },
21916 Newline,
21917}
21918
21919impl<'a> Iterator for WordBreakingTokenizer<'a> {
21920 /// Yields a span, the count of graphemes in the token, and whether it was
21921 /// whitespace. Note that it also breaks at word boundaries.
21922 type Item = WordBreakToken<'a>;
21923
21924 fn next(&mut self) -> Option<Self::Item> {
21925 use unicode_segmentation::UnicodeSegmentation;
21926 if self.input.is_empty() {
21927 return None;
21928 }
21929
21930 let mut iter = self.input.graphemes(true).peekable();
21931 let mut offset = 0;
21932 let mut grapheme_len = 0;
21933 if let Some(first_grapheme) = iter.next() {
21934 let is_newline = first_grapheme == "\n";
21935 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21936 offset += first_grapheme.len();
21937 grapheme_len += 1;
21938 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21939 if let Some(grapheme) = iter.peek().copied()
21940 && should_stay_with_preceding_ideograph(grapheme)
21941 {
21942 offset += grapheme.len();
21943 grapheme_len += 1;
21944 }
21945 } else {
21946 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21947 let mut next_word_bound = words.peek().copied();
21948 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21949 next_word_bound = words.next();
21950 }
21951 while let Some(grapheme) = iter.peek().copied() {
21952 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21953 break;
21954 };
21955 if is_grapheme_whitespace(grapheme) != is_whitespace
21956 || (grapheme == "\n") != is_newline
21957 {
21958 break;
21959 };
21960 offset += grapheme.len();
21961 grapheme_len += 1;
21962 iter.next();
21963 }
21964 }
21965 let token = &self.input[..offset];
21966 self.input = &self.input[offset..];
21967 if token == "\n" {
21968 Some(WordBreakToken::Newline)
21969 } else if is_whitespace {
21970 Some(WordBreakToken::InlineWhitespace {
21971 token,
21972 grapheme_len,
21973 })
21974 } else {
21975 Some(WordBreakToken::Word {
21976 token,
21977 grapheme_len,
21978 })
21979 }
21980 } else {
21981 None
21982 }
21983 }
21984}
21985
21986#[test]
21987fn test_word_breaking_tokenizer() {
21988 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21989 ("", &[]),
21990 (" ", &[whitespace(" ", 2)]),
21991 ("Ʒ", &[word("Ʒ", 1)]),
21992 ("Ǽ", &[word("Ǽ", 1)]),
21993 ("⋑", &[word("⋑", 1)]),
21994 ("⋑⋑", &[word("⋑⋑", 2)]),
21995 (
21996 "原理,进而",
21997 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21998 ),
21999 (
22000 "hello world",
22001 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22002 ),
22003 (
22004 "hello, world",
22005 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22006 ),
22007 (
22008 " hello world",
22009 &[
22010 whitespace(" ", 2),
22011 word("hello", 5),
22012 whitespace(" ", 1),
22013 word("world", 5),
22014 ],
22015 ),
22016 (
22017 "这是什么 \n 钢笔",
22018 &[
22019 word("这", 1),
22020 word("是", 1),
22021 word("什", 1),
22022 word("么", 1),
22023 whitespace(" ", 1),
22024 newline(),
22025 whitespace(" ", 1),
22026 word("钢", 1),
22027 word("笔", 1),
22028 ],
22029 ),
22030 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22031 ];
22032
22033 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22034 WordBreakToken::Word {
22035 token,
22036 grapheme_len,
22037 }
22038 }
22039
22040 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22041 WordBreakToken::InlineWhitespace {
22042 token,
22043 grapheme_len,
22044 }
22045 }
22046
22047 fn newline() -> WordBreakToken<'static> {
22048 WordBreakToken::Newline
22049 }
22050
22051 for (input, result) in tests {
22052 assert_eq!(
22053 WordBreakingTokenizer::new(input)
22054 .collect::<Vec<_>>()
22055 .as_slice(),
22056 *result,
22057 );
22058 }
22059}
22060
22061fn wrap_with_prefix(
22062 first_line_prefix: String,
22063 subsequent_lines_prefix: String,
22064 unwrapped_text: String,
22065 wrap_column: usize,
22066 tab_size: NonZeroU32,
22067 preserve_existing_whitespace: bool,
22068) -> String {
22069 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22070 let subsequent_lines_prefix_len =
22071 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22072 let mut wrapped_text = String::new();
22073 let mut current_line = first_line_prefix;
22074 let mut is_first_line = true;
22075
22076 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22077 let mut current_line_len = first_line_prefix_len;
22078 let mut in_whitespace = false;
22079 for token in tokenizer {
22080 let have_preceding_whitespace = in_whitespace;
22081 match token {
22082 WordBreakToken::Word {
22083 token,
22084 grapheme_len,
22085 } => {
22086 in_whitespace = false;
22087 let current_prefix_len = if is_first_line {
22088 first_line_prefix_len
22089 } else {
22090 subsequent_lines_prefix_len
22091 };
22092 if current_line_len + grapheme_len > wrap_column
22093 && current_line_len != current_prefix_len
22094 {
22095 wrapped_text.push_str(current_line.trim_end());
22096 wrapped_text.push('\n');
22097 is_first_line = false;
22098 current_line = subsequent_lines_prefix.clone();
22099 current_line_len = subsequent_lines_prefix_len;
22100 }
22101 current_line.push_str(token);
22102 current_line_len += grapheme_len;
22103 }
22104 WordBreakToken::InlineWhitespace {
22105 mut token,
22106 mut grapheme_len,
22107 } => {
22108 in_whitespace = true;
22109 if have_preceding_whitespace && !preserve_existing_whitespace {
22110 continue;
22111 }
22112 if !preserve_existing_whitespace {
22113 token = " ";
22114 grapheme_len = 1;
22115 }
22116 let current_prefix_len = if is_first_line {
22117 first_line_prefix_len
22118 } else {
22119 subsequent_lines_prefix_len
22120 };
22121 if current_line_len + grapheme_len > wrap_column {
22122 wrapped_text.push_str(current_line.trim_end());
22123 wrapped_text.push('\n');
22124 is_first_line = false;
22125 current_line = subsequent_lines_prefix.clone();
22126 current_line_len = subsequent_lines_prefix_len;
22127 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22128 current_line.push_str(token);
22129 current_line_len += grapheme_len;
22130 }
22131 }
22132 WordBreakToken::Newline => {
22133 in_whitespace = true;
22134 let current_prefix_len = if is_first_line {
22135 first_line_prefix_len
22136 } else {
22137 subsequent_lines_prefix_len
22138 };
22139 if preserve_existing_whitespace {
22140 wrapped_text.push_str(current_line.trim_end());
22141 wrapped_text.push('\n');
22142 is_first_line = false;
22143 current_line = subsequent_lines_prefix.clone();
22144 current_line_len = subsequent_lines_prefix_len;
22145 } else if have_preceding_whitespace {
22146 continue;
22147 } else if current_line_len + 1 > wrap_column
22148 && current_line_len != current_prefix_len
22149 {
22150 wrapped_text.push_str(current_line.trim_end());
22151 wrapped_text.push('\n');
22152 is_first_line = false;
22153 current_line = subsequent_lines_prefix.clone();
22154 current_line_len = subsequent_lines_prefix_len;
22155 } else if current_line_len != current_prefix_len {
22156 current_line.push(' ');
22157 current_line_len += 1;
22158 }
22159 }
22160 }
22161 }
22162
22163 if !current_line.is_empty() {
22164 wrapped_text.push_str(¤t_line);
22165 }
22166 wrapped_text
22167}
22168
22169#[test]
22170fn test_wrap_with_prefix() {
22171 assert_eq!(
22172 wrap_with_prefix(
22173 "# ".to_string(),
22174 "# ".to_string(),
22175 "abcdefg".to_string(),
22176 4,
22177 NonZeroU32::new(4).unwrap(),
22178 false,
22179 ),
22180 "# abcdefg"
22181 );
22182 assert_eq!(
22183 wrap_with_prefix(
22184 "".to_string(),
22185 "".to_string(),
22186 "\thello world".to_string(),
22187 8,
22188 NonZeroU32::new(4).unwrap(),
22189 false,
22190 ),
22191 "hello\nworld"
22192 );
22193 assert_eq!(
22194 wrap_with_prefix(
22195 "// ".to_string(),
22196 "// ".to_string(),
22197 "xx \nyy zz aa bb cc".to_string(),
22198 12,
22199 NonZeroU32::new(4).unwrap(),
22200 false,
22201 ),
22202 "// xx yy zz\n// aa bb cc"
22203 );
22204 assert_eq!(
22205 wrap_with_prefix(
22206 String::new(),
22207 String::new(),
22208 "这是什么 \n 钢笔".to_string(),
22209 3,
22210 NonZeroU32::new(4).unwrap(),
22211 false,
22212 ),
22213 "这是什\n么 钢\n笔"
22214 );
22215}
22216
22217pub trait CollaborationHub {
22218 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22219 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22220 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22221}
22222
22223impl CollaborationHub for Entity<Project> {
22224 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22225 self.read(cx).collaborators()
22226 }
22227
22228 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22229 self.read(cx).user_store().read(cx).participant_indices()
22230 }
22231
22232 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22233 let this = self.read(cx);
22234 let user_ids = this.collaborators().values().map(|c| c.user_id);
22235 this.user_store().read(cx).participant_names(user_ids, cx)
22236 }
22237}
22238
22239pub trait SemanticsProvider {
22240 fn hover(
22241 &self,
22242 buffer: &Entity<Buffer>,
22243 position: text::Anchor,
22244 cx: &mut App,
22245 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22246
22247 fn inline_values(
22248 &self,
22249 buffer_handle: Entity<Buffer>,
22250 range: Range<text::Anchor>,
22251 cx: &mut App,
22252 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22253
22254 fn inlay_hints(
22255 &self,
22256 buffer_handle: Entity<Buffer>,
22257 range: Range<text::Anchor>,
22258 cx: &mut App,
22259 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22260
22261 fn resolve_inlay_hint(
22262 &self,
22263 hint: InlayHint,
22264 buffer_handle: Entity<Buffer>,
22265 server_id: LanguageServerId,
22266 cx: &mut App,
22267 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22268
22269 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22270
22271 fn document_highlights(
22272 &self,
22273 buffer: &Entity<Buffer>,
22274 position: text::Anchor,
22275 cx: &mut App,
22276 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22277
22278 fn definitions(
22279 &self,
22280 buffer: &Entity<Buffer>,
22281 position: text::Anchor,
22282 kind: GotoDefinitionKind,
22283 cx: &mut App,
22284 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22285
22286 fn range_for_rename(
22287 &self,
22288 buffer: &Entity<Buffer>,
22289 position: text::Anchor,
22290 cx: &mut App,
22291 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22292
22293 fn perform_rename(
22294 &self,
22295 buffer: &Entity<Buffer>,
22296 position: text::Anchor,
22297 new_name: String,
22298 cx: &mut App,
22299 ) -> Option<Task<Result<ProjectTransaction>>>;
22300}
22301
22302pub trait CompletionProvider {
22303 fn completions(
22304 &self,
22305 excerpt_id: ExcerptId,
22306 buffer: &Entity<Buffer>,
22307 buffer_position: text::Anchor,
22308 trigger: CompletionContext,
22309 window: &mut Window,
22310 cx: &mut Context<Editor>,
22311 ) -> Task<Result<Vec<CompletionResponse>>>;
22312
22313 fn resolve_completions(
22314 &self,
22315 _buffer: Entity<Buffer>,
22316 _completion_indices: Vec<usize>,
22317 _completions: Rc<RefCell<Box<[Completion]>>>,
22318 _cx: &mut Context<Editor>,
22319 ) -> Task<Result<bool>> {
22320 Task::ready(Ok(false))
22321 }
22322
22323 fn apply_additional_edits_for_completion(
22324 &self,
22325 _buffer: Entity<Buffer>,
22326 _completions: Rc<RefCell<Box<[Completion]>>>,
22327 _completion_index: usize,
22328 _push_to_history: bool,
22329 _cx: &mut Context<Editor>,
22330 ) -> Task<Result<Option<language::Transaction>>> {
22331 Task::ready(Ok(None))
22332 }
22333
22334 fn is_completion_trigger(
22335 &self,
22336 buffer: &Entity<Buffer>,
22337 position: language::Anchor,
22338 text: &str,
22339 trigger_in_words: bool,
22340 menu_is_open: bool,
22341 cx: &mut Context<Editor>,
22342 ) -> bool;
22343
22344 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22345
22346 fn sort_completions(&self) -> bool {
22347 true
22348 }
22349
22350 fn filter_completions(&self) -> bool {
22351 true
22352 }
22353}
22354
22355pub trait CodeActionProvider {
22356 fn id(&self) -> Arc<str>;
22357
22358 fn code_actions(
22359 &self,
22360 buffer: &Entity<Buffer>,
22361 range: Range<text::Anchor>,
22362 window: &mut Window,
22363 cx: &mut App,
22364 ) -> Task<Result<Vec<CodeAction>>>;
22365
22366 fn apply_code_action(
22367 &self,
22368 buffer_handle: Entity<Buffer>,
22369 action: CodeAction,
22370 excerpt_id: ExcerptId,
22371 push_to_history: bool,
22372 window: &mut Window,
22373 cx: &mut App,
22374 ) -> Task<Result<ProjectTransaction>>;
22375}
22376
22377impl CodeActionProvider for Entity<Project> {
22378 fn id(&self) -> Arc<str> {
22379 "project".into()
22380 }
22381
22382 fn code_actions(
22383 &self,
22384 buffer: &Entity<Buffer>,
22385 range: Range<text::Anchor>,
22386 _window: &mut Window,
22387 cx: &mut App,
22388 ) -> Task<Result<Vec<CodeAction>>> {
22389 self.update(cx, |project, cx| {
22390 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22391 let code_actions = project.code_actions(buffer, range, None, cx);
22392 cx.background_spawn(async move {
22393 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22394 Ok(code_lens_actions
22395 .context("code lens fetch")?
22396 .into_iter()
22397 .flatten()
22398 .chain(
22399 code_actions
22400 .context("code action fetch")?
22401 .into_iter()
22402 .flatten(),
22403 )
22404 .collect())
22405 })
22406 })
22407 }
22408
22409 fn apply_code_action(
22410 &self,
22411 buffer_handle: Entity<Buffer>,
22412 action: CodeAction,
22413 _excerpt_id: ExcerptId,
22414 push_to_history: bool,
22415 _window: &mut Window,
22416 cx: &mut App,
22417 ) -> Task<Result<ProjectTransaction>> {
22418 self.update(cx, |project, cx| {
22419 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22420 })
22421 }
22422}
22423
22424fn snippet_completions(
22425 project: &Project,
22426 buffer: &Entity<Buffer>,
22427 buffer_position: text::Anchor,
22428 cx: &mut App,
22429) -> Task<Result<CompletionResponse>> {
22430 let languages = buffer.read(cx).languages_at(buffer_position);
22431 let snippet_store = project.snippets().read(cx);
22432
22433 let scopes: Vec<_> = languages
22434 .iter()
22435 .filter_map(|language| {
22436 let language_name = language.lsp_id();
22437 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22438
22439 if snippets.is_empty() {
22440 None
22441 } else {
22442 Some((language.default_scope(), snippets))
22443 }
22444 })
22445 .collect();
22446
22447 if scopes.is_empty() {
22448 return Task::ready(Ok(CompletionResponse {
22449 completions: vec![],
22450 display_options: CompletionDisplayOptions::default(),
22451 is_incomplete: false,
22452 }));
22453 }
22454
22455 let snapshot = buffer.read(cx).text_snapshot();
22456 let chars: String = snapshot
22457 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22458 .collect();
22459 let executor = cx.background_executor().clone();
22460
22461 cx.background_spawn(async move {
22462 let mut is_incomplete = false;
22463 let mut completions: Vec<Completion> = Vec::new();
22464 for (scope, snippets) in scopes.into_iter() {
22465 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22466 let mut last_word = chars
22467 .chars()
22468 .take_while(|c| classifier.is_word(*c))
22469 .collect::<String>();
22470 last_word = last_word.chars().rev().collect();
22471
22472 if last_word.is_empty() {
22473 return Ok(CompletionResponse {
22474 completions: vec![],
22475 display_options: CompletionDisplayOptions::default(),
22476 is_incomplete: true,
22477 });
22478 }
22479
22480 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22481 let to_lsp = |point: &text::Anchor| {
22482 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22483 point_to_lsp(end)
22484 };
22485 let lsp_end = to_lsp(&buffer_position);
22486
22487 let candidates = snippets
22488 .iter()
22489 .enumerate()
22490 .flat_map(|(ix, snippet)| {
22491 snippet
22492 .prefix
22493 .iter()
22494 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22495 })
22496 .collect::<Vec<StringMatchCandidate>>();
22497
22498 const MAX_RESULTS: usize = 100;
22499 let mut matches = fuzzy::match_strings(
22500 &candidates,
22501 &last_word,
22502 last_word.chars().any(|c| c.is_uppercase()),
22503 true,
22504 MAX_RESULTS,
22505 &Default::default(),
22506 executor.clone(),
22507 )
22508 .await;
22509
22510 if matches.len() >= MAX_RESULTS {
22511 is_incomplete = true;
22512 }
22513
22514 // Remove all candidates where the query's start does not match the start of any word in the candidate
22515 if let Some(query_start) = last_word.chars().next() {
22516 matches.retain(|string_match| {
22517 split_words(&string_match.string).any(|word| {
22518 // Check that the first codepoint of the word as lowercase matches the first
22519 // codepoint of the query as lowercase
22520 word.chars()
22521 .flat_map(|codepoint| codepoint.to_lowercase())
22522 .zip(query_start.to_lowercase())
22523 .all(|(word_cp, query_cp)| word_cp == query_cp)
22524 })
22525 });
22526 }
22527
22528 let matched_strings = matches
22529 .into_iter()
22530 .map(|m| m.string)
22531 .collect::<HashSet<_>>();
22532
22533 completions.extend(snippets.iter().filter_map(|snippet| {
22534 let matching_prefix = snippet
22535 .prefix
22536 .iter()
22537 .find(|prefix| matched_strings.contains(*prefix))?;
22538 let start = as_offset - last_word.len();
22539 let start = snapshot.anchor_before(start);
22540 let range = start..buffer_position;
22541 let lsp_start = to_lsp(&start);
22542 let lsp_range = lsp::Range {
22543 start: lsp_start,
22544 end: lsp_end,
22545 };
22546 Some(Completion {
22547 replace_range: range,
22548 new_text: snippet.body.clone(),
22549 source: CompletionSource::Lsp {
22550 insert_range: None,
22551 server_id: LanguageServerId(usize::MAX),
22552 resolved: true,
22553 lsp_completion: Box::new(lsp::CompletionItem {
22554 label: snippet.prefix.first().unwrap().clone(),
22555 kind: Some(CompletionItemKind::SNIPPET),
22556 label_details: snippet.description.as_ref().map(|description| {
22557 lsp::CompletionItemLabelDetails {
22558 detail: Some(description.clone()),
22559 description: None,
22560 }
22561 }),
22562 insert_text_format: Some(InsertTextFormat::SNIPPET),
22563 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22564 lsp::InsertReplaceEdit {
22565 new_text: snippet.body.clone(),
22566 insert: lsp_range,
22567 replace: lsp_range,
22568 },
22569 )),
22570 filter_text: Some(snippet.body.clone()),
22571 sort_text: Some(char::MAX.to_string()),
22572 ..lsp::CompletionItem::default()
22573 }),
22574 lsp_defaults: None,
22575 },
22576 label: CodeLabel {
22577 text: matching_prefix.clone(),
22578 runs: Vec::new(),
22579 filter_range: 0..matching_prefix.len(),
22580 },
22581 icon_path: None,
22582 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22583 single_line: snippet.name.clone().into(),
22584 plain_text: snippet
22585 .description
22586 .clone()
22587 .map(|description| description.into()),
22588 }),
22589 insert_text_mode: None,
22590 confirm: None,
22591 })
22592 }))
22593 }
22594
22595 Ok(CompletionResponse {
22596 completions,
22597 display_options: CompletionDisplayOptions::default(),
22598 is_incomplete,
22599 })
22600 })
22601}
22602
22603impl CompletionProvider for Entity<Project> {
22604 fn completions(
22605 &self,
22606 _excerpt_id: ExcerptId,
22607 buffer: &Entity<Buffer>,
22608 buffer_position: text::Anchor,
22609 options: CompletionContext,
22610 _window: &mut Window,
22611 cx: &mut Context<Editor>,
22612 ) -> Task<Result<Vec<CompletionResponse>>> {
22613 self.update(cx, |project, cx| {
22614 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22615 let project_completions = project.completions(buffer, buffer_position, options, cx);
22616 cx.background_spawn(async move {
22617 let mut responses = project_completions.await?;
22618 let snippets = snippets.await?;
22619 if !snippets.completions.is_empty() {
22620 responses.push(snippets);
22621 }
22622 Ok(responses)
22623 })
22624 })
22625 }
22626
22627 fn resolve_completions(
22628 &self,
22629 buffer: Entity<Buffer>,
22630 completion_indices: Vec<usize>,
22631 completions: Rc<RefCell<Box<[Completion]>>>,
22632 cx: &mut Context<Editor>,
22633 ) -> Task<Result<bool>> {
22634 self.update(cx, |project, cx| {
22635 project.lsp_store().update(cx, |lsp_store, cx| {
22636 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22637 })
22638 })
22639 }
22640
22641 fn apply_additional_edits_for_completion(
22642 &self,
22643 buffer: Entity<Buffer>,
22644 completions: Rc<RefCell<Box<[Completion]>>>,
22645 completion_index: usize,
22646 push_to_history: bool,
22647 cx: &mut Context<Editor>,
22648 ) -> Task<Result<Option<language::Transaction>>> {
22649 self.update(cx, |project, cx| {
22650 project.lsp_store().update(cx, |lsp_store, cx| {
22651 lsp_store.apply_additional_edits_for_completion(
22652 buffer,
22653 completions,
22654 completion_index,
22655 push_to_history,
22656 cx,
22657 )
22658 })
22659 })
22660 }
22661
22662 fn is_completion_trigger(
22663 &self,
22664 buffer: &Entity<Buffer>,
22665 position: language::Anchor,
22666 text: &str,
22667 trigger_in_words: bool,
22668 menu_is_open: bool,
22669 cx: &mut Context<Editor>,
22670 ) -> bool {
22671 let mut chars = text.chars();
22672 let char = if let Some(char) = chars.next() {
22673 char
22674 } else {
22675 return false;
22676 };
22677 if chars.next().is_some() {
22678 return false;
22679 }
22680
22681 let buffer = buffer.read(cx);
22682 let snapshot = buffer.snapshot();
22683 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22684 return false;
22685 }
22686 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22687 if trigger_in_words && classifier.is_word(char) {
22688 return true;
22689 }
22690
22691 buffer.completion_triggers().contains(text)
22692 }
22693}
22694
22695impl SemanticsProvider for Entity<Project> {
22696 fn hover(
22697 &self,
22698 buffer: &Entity<Buffer>,
22699 position: text::Anchor,
22700 cx: &mut App,
22701 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22702 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22703 }
22704
22705 fn document_highlights(
22706 &self,
22707 buffer: &Entity<Buffer>,
22708 position: text::Anchor,
22709 cx: &mut App,
22710 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22711 Some(self.update(cx, |project, cx| {
22712 project.document_highlights(buffer, position, cx)
22713 }))
22714 }
22715
22716 fn definitions(
22717 &self,
22718 buffer: &Entity<Buffer>,
22719 position: text::Anchor,
22720 kind: GotoDefinitionKind,
22721 cx: &mut App,
22722 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22723 Some(self.update(cx, |project, cx| match kind {
22724 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22725 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22726 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22727 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22728 }))
22729 }
22730
22731 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22732 self.update(cx, |project, cx| {
22733 if project
22734 .active_debug_session(cx)
22735 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22736 {
22737 return true;
22738 }
22739
22740 buffer.update(cx, |buffer, cx| {
22741 project.any_language_server_supports_inlay_hints(buffer, cx)
22742 })
22743 })
22744 }
22745
22746 fn inline_values(
22747 &self,
22748 buffer_handle: Entity<Buffer>,
22749 range: Range<text::Anchor>,
22750 cx: &mut App,
22751 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22752 self.update(cx, |project, cx| {
22753 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22754
22755 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22756 })
22757 }
22758
22759 fn inlay_hints(
22760 &self,
22761 buffer_handle: Entity<Buffer>,
22762 range: Range<text::Anchor>,
22763 cx: &mut App,
22764 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22765 Some(self.update(cx, |project, cx| {
22766 project.inlay_hints(buffer_handle, range, cx)
22767 }))
22768 }
22769
22770 fn resolve_inlay_hint(
22771 &self,
22772 hint: InlayHint,
22773 buffer_handle: Entity<Buffer>,
22774 server_id: LanguageServerId,
22775 cx: &mut App,
22776 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22777 Some(self.update(cx, |project, cx| {
22778 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22779 }))
22780 }
22781
22782 fn range_for_rename(
22783 &self,
22784 buffer: &Entity<Buffer>,
22785 position: text::Anchor,
22786 cx: &mut App,
22787 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22788 Some(self.update(cx, |project, cx| {
22789 let buffer = buffer.clone();
22790 let task = project.prepare_rename(buffer.clone(), position, cx);
22791 cx.spawn(async move |_, cx| {
22792 Ok(match task.await? {
22793 PrepareRenameResponse::Success(range) => Some(range),
22794 PrepareRenameResponse::InvalidPosition => None,
22795 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22796 // Fallback on using TreeSitter info to determine identifier range
22797 buffer.read_with(cx, |buffer, _| {
22798 let snapshot = buffer.snapshot();
22799 let (range, kind) = snapshot.surrounding_word(position, false);
22800 if kind != Some(CharKind::Word) {
22801 return None;
22802 }
22803 Some(
22804 snapshot.anchor_before(range.start)
22805 ..snapshot.anchor_after(range.end),
22806 )
22807 })?
22808 }
22809 })
22810 })
22811 }))
22812 }
22813
22814 fn perform_rename(
22815 &self,
22816 buffer: &Entity<Buffer>,
22817 position: text::Anchor,
22818 new_name: String,
22819 cx: &mut App,
22820 ) -> Option<Task<Result<ProjectTransaction>>> {
22821 Some(self.update(cx, |project, cx| {
22822 project.perform_rename(buffer.clone(), position, new_name, cx)
22823 }))
22824 }
22825}
22826
22827fn inlay_hint_settings(
22828 location: Anchor,
22829 snapshot: &MultiBufferSnapshot,
22830 cx: &mut Context<Editor>,
22831) -> InlayHintSettings {
22832 let file = snapshot.file_at(location);
22833 let language = snapshot.language_at(location).map(|l| l.name());
22834 language_settings(language, file, cx).inlay_hints
22835}
22836
22837fn consume_contiguous_rows(
22838 contiguous_row_selections: &mut Vec<Selection<Point>>,
22839 selection: &Selection<Point>,
22840 display_map: &DisplaySnapshot,
22841 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22842) -> (MultiBufferRow, MultiBufferRow) {
22843 contiguous_row_selections.push(selection.clone());
22844 let start_row = starting_row(selection, display_map);
22845 let mut end_row = ending_row(selection, display_map);
22846
22847 while let Some(next_selection) = selections.peek() {
22848 if next_selection.start.row <= end_row.0 {
22849 end_row = ending_row(next_selection, display_map);
22850 contiguous_row_selections.push(selections.next().unwrap().clone());
22851 } else {
22852 break;
22853 }
22854 }
22855 (start_row, end_row)
22856}
22857
22858fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22859 if selection.start.column > 0 {
22860 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22861 } else {
22862 MultiBufferRow(selection.start.row)
22863 }
22864}
22865
22866fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22867 if next_selection.end.column > 0 || next_selection.is_empty() {
22868 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22869 } else {
22870 MultiBufferRow(next_selection.end.row)
22871 }
22872}
22873
22874impl EditorSnapshot {
22875 pub fn remote_selections_in_range<'a>(
22876 &'a self,
22877 range: &'a Range<Anchor>,
22878 collaboration_hub: &dyn CollaborationHub,
22879 cx: &'a App,
22880 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22881 let participant_names = collaboration_hub.user_names(cx);
22882 let participant_indices = collaboration_hub.user_participant_indices(cx);
22883 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22884 let collaborators_by_replica_id = collaborators_by_peer_id
22885 .values()
22886 .map(|collaborator| (collaborator.replica_id, collaborator))
22887 .collect::<HashMap<_, _>>();
22888 self.buffer_snapshot
22889 .selections_in_range(range, false)
22890 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22891 if replica_id == AGENT_REPLICA_ID {
22892 Some(RemoteSelection {
22893 replica_id,
22894 selection,
22895 cursor_shape,
22896 line_mode,
22897 collaborator_id: CollaboratorId::Agent,
22898 user_name: Some("Agent".into()),
22899 color: cx.theme().players().agent(),
22900 })
22901 } else {
22902 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22903 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22904 let user_name = participant_names.get(&collaborator.user_id).cloned();
22905 Some(RemoteSelection {
22906 replica_id,
22907 selection,
22908 cursor_shape,
22909 line_mode,
22910 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22911 user_name,
22912 color: if let Some(index) = participant_index {
22913 cx.theme().players().color_for_participant(index.0)
22914 } else {
22915 cx.theme().players().absent()
22916 },
22917 })
22918 }
22919 })
22920 }
22921
22922 pub fn hunks_for_ranges(
22923 &self,
22924 ranges: impl IntoIterator<Item = Range<Point>>,
22925 ) -> Vec<MultiBufferDiffHunk> {
22926 let mut hunks = Vec::new();
22927 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22928 HashMap::default();
22929 for query_range in ranges {
22930 let query_rows =
22931 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22932 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22933 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22934 ) {
22935 // Include deleted hunks that are adjacent to the query range, because
22936 // otherwise they would be missed.
22937 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22938 if hunk.status().is_deleted() {
22939 intersects_range |= hunk.row_range.start == query_rows.end;
22940 intersects_range |= hunk.row_range.end == query_rows.start;
22941 }
22942 if intersects_range {
22943 if !processed_buffer_rows
22944 .entry(hunk.buffer_id)
22945 .or_default()
22946 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22947 {
22948 continue;
22949 }
22950 hunks.push(hunk);
22951 }
22952 }
22953 }
22954
22955 hunks
22956 }
22957
22958 fn display_diff_hunks_for_rows<'a>(
22959 &'a self,
22960 display_rows: Range<DisplayRow>,
22961 folded_buffers: &'a HashSet<BufferId>,
22962 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22963 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22964 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22965
22966 self.buffer_snapshot
22967 .diff_hunks_in_range(buffer_start..buffer_end)
22968 .filter_map(|hunk| {
22969 if folded_buffers.contains(&hunk.buffer_id) {
22970 return None;
22971 }
22972
22973 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22974 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22975
22976 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22977 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22978
22979 let display_hunk = if hunk_display_start.column() != 0 {
22980 DisplayDiffHunk::Folded {
22981 display_row: hunk_display_start.row(),
22982 }
22983 } else {
22984 let mut end_row = hunk_display_end.row();
22985 if hunk_display_end.column() > 0 {
22986 end_row.0 += 1;
22987 }
22988 let is_created_file = hunk.is_created_file();
22989 DisplayDiffHunk::Unfolded {
22990 status: hunk.status(),
22991 diff_base_byte_range: hunk.diff_base_byte_range,
22992 display_row_range: hunk_display_start.row()..end_row,
22993 multi_buffer_range: Anchor::range_in_buffer(
22994 hunk.excerpt_id,
22995 hunk.buffer_id,
22996 hunk.buffer_range,
22997 ),
22998 is_created_file,
22999 }
23000 };
23001
23002 Some(display_hunk)
23003 })
23004 }
23005
23006 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23007 self.display_snapshot.buffer_snapshot.language_at(position)
23008 }
23009
23010 pub fn is_focused(&self) -> bool {
23011 self.is_focused
23012 }
23013
23014 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
23015 self.placeholder_text.as_ref()
23016 }
23017
23018 pub fn scroll_position(&self) -> gpui::Point<f32> {
23019 self.scroll_anchor.scroll_position(&self.display_snapshot)
23020 }
23021
23022 fn gutter_dimensions(
23023 &self,
23024 font_id: FontId,
23025 font_size: Pixels,
23026 max_line_number_width: Pixels,
23027 cx: &App,
23028 ) -> Option<GutterDimensions> {
23029 if !self.show_gutter {
23030 return None;
23031 }
23032
23033 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23034 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23035
23036 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23037 matches!(
23038 ProjectSettings::get_global(cx).git.git_gutter,
23039 Some(GitGutterSetting::TrackedFiles)
23040 )
23041 });
23042 let gutter_settings = EditorSettings::get_global(cx).gutter;
23043 let show_line_numbers = self
23044 .show_line_numbers
23045 .unwrap_or(gutter_settings.line_numbers);
23046 let line_gutter_width = if show_line_numbers {
23047 // Avoid flicker-like gutter resizes when the line number gains another digit by
23048 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23049 let min_width_for_number_on_gutter =
23050 ch_advance * gutter_settings.min_line_number_digits as f32;
23051 max_line_number_width.max(min_width_for_number_on_gutter)
23052 } else {
23053 0.0.into()
23054 };
23055
23056 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23057 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23058
23059 let git_blame_entries_width =
23060 self.git_blame_gutter_max_author_length
23061 .map(|max_author_length| {
23062 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23063 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23064
23065 /// The number of characters to dedicate to gaps and margins.
23066 const SPACING_WIDTH: usize = 4;
23067
23068 let max_char_count = max_author_length.min(renderer.max_author_length())
23069 + ::git::SHORT_SHA_LENGTH
23070 + MAX_RELATIVE_TIMESTAMP.len()
23071 + SPACING_WIDTH;
23072
23073 ch_advance * max_char_count
23074 });
23075
23076 let is_singleton = self.buffer_snapshot.is_singleton();
23077
23078 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23079 left_padding += if !is_singleton {
23080 ch_width * 4.0
23081 } else if show_runnables || show_breakpoints {
23082 ch_width * 3.0
23083 } else if show_git_gutter && show_line_numbers {
23084 ch_width * 2.0
23085 } else if show_git_gutter || show_line_numbers {
23086 ch_width
23087 } else {
23088 px(0.)
23089 };
23090
23091 let shows_folds = is_singleton && gutter_settings.folds;
23092
23093 let right_padding = if shows_folds && show_line_numbers {
23094 ch_width * 4.0
23095 } else if shows_folds || (!is_singleton && show_line_numbers) {
23096 ch_width * 3.0
23097 } else if show_line_numbers {
23098 ch_width
23099 } else {
23100 px(0.)
23101 };
23102
23103 Some(GutterDimensions {
23104 left_padding,
23105 right_padding,
23106 width: line_gutter_width + left_padding + right_padding,
23107 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23108 git_blame_entries_width,
23109 })
23110 }
23111
23112 pub fn render_crease_toggle(
23113 &self,
23114 buffer_row: MultiBufferRow,
23115 row_contains_cursor: bool,
23116 editor: Entity<Editor>,
23117 window: &mut Window,
23118 cx: &mut App,
23119 ) -> Option<AnyElement> {
23120 let folded = self.is_line_folded(buffer_row);
23121 let mut is_foldable = false;
23122
23123 if let Some(crease) = self
23124 .crease_snapshot
23125 .query_row(buffer_row, &self.buffer_snapshot)
23126 {
23127 is_foldable = true;
23128 match crease {
23129 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23130 if let Some(render_toggle) = render_toggle {
23131 let toggle_callback =
23132 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23133 if folded {
23134 editor.update(cx, |editor, cx| {
23135 editor.fold_at(buffer_row, window, cx)
23136 });
23137 } else {
23138 editor.update(cx, |editor, cx| {
23139 editor.unfold_at(buffer_row, window, cx)
23140 });
23141 }
23142 });
23143 return Some((render_toggle)(
23144 buffer_row,
23145 folded,
23146 toggle_callback,
23147 window,
23148 cx,
23149 ));
23150 }
23151 }
23152 }
23153 }
23154
23155 is_foldable |= self.starts_indent(buffer_row);
23156
23157 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23158 Some(
23159 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23160 .toggle_state(folded)
23161 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23162 if folded {
23163 this.unfold_at(buffer_row, window, cx);
23164 } else {
23165 this.fold_at(buffer_row, window, cx);
23166 }
23167 }))
23168 .into_any_element(),
23169 )
23170 } else {
23171 None
23172 }
23173 }
23174
23175 pub fn render_crease_trailer(
23176 &self,
23177 buffer_row: MultiBufferRow,
23178 window: &mut Window,
23179 cx: &mut App,
23180 ) -> Option<AnyElement> {
23181 let folded = self.is_line_folded(buffer_row);
23182 if let Crease::Inline { render_trailer, .. } = self
23183 .crease_snapshot
23184 .query_row(buffer_row, &self.buffer_snapshot)?
23185 {
23186 let render_trailer = render_trailer.as_ref()?;
23187 Some(render_trailer(buffer_row, folded, window, cx))
23188 } else {
23189 None
23190 }
23191 }
23192}
23193
23194impl Deref for EditorSnapshot {
23195 type Target = DisplaySnapshot;
23196
23197 fn deref(&self) -> &Self::Target {
23198 &self.display_snapshot
23199 }
23200}
23201
23202#[derive(Clone, Debug, PartialEq, Eq)]
23203pub enum EditorEvent {
23204 InputIgnored {
23205 text: Arc<str>,
23206 },
23207 InputHandled {
23208 utf16_range_to_replace: Option<Range<isize>>,
23209 text: Arc<str>,
23210 },
23211 ExcerptsAdded {
23212 buffer: Entity<Buffer>,
23213 predecessor: ExcerptId,
23214 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23215 },
23216 ExcerptsRemoved {
23217 ids: Vec<ExcerptId>,
23218 removed_buffer_ids: Vec<BufferId>,
23219 },
23220 BufferFoldToggled {
23221 ids: Vec<ExcerptId>,
23222 folded: bool,
23223 },
23224 ExcerptsEdited {
23225 ids: Vec<ExcerptId>,
23226 },
23227 ExcerptsExpanded {
23228 ids: Vec<ExcerptId>,
23229 },
23230 BufferEdited,
23231 Edited {
23232 transaction_id: clock::Lamport,
23233 },
23234 Reparsed(BufferId),
23235 Focused,
23236 FocusedIn,
23237 Blurred,
23238 DirtyChanged,
23239 Saved,
23240 TitleChanged,
23241 SelectionsChanged {
23242 local: bool,
23243 },
23244 ScrollPositionChanged {
23245 local: bool,
23246 autoscroll: bool,
23247 },
23248 TransactionUndone {
23249 transaction_id: clock::Lamport,
23250 },
23251 TransactionBegun {
23252 transaction_id: clock::Lamport,
23253 },
23254 CursorShapeChanged,
23255 BreadcrumbsChanged,
23256 PushedToNavHistory {
23257 anchor: Anchor,
23258 is_deactivate: bool,
23259 },
23260}
23261
23262impl EventEmitter<EditorEvent> for Editor {}
23263
23264impl Focusable for Editor {
23265 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23266 self.focus_handle.clone()
23267 }
23268}
23269
23270impl Render for Editor {
23271 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23272 let settings = ThemeSettings::get_global(cx);
23273
23274 let mut text_style = match self.mode {
23275 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23276 color: cx.theme().colors().editor_foreground,
23277 font_family: settings.ui_font.family.clone(),
23278 font_features: settings.ui_font.features.clone(),
23279 font_fallbacks: settings.ui_font.fallbacks.clone(),
23280 font_size: rems(0.875).into(),
23281 font_weight: settings.ui_font.weight,
23282 line_height: relative(settings.buffer_line_height.value()),
23283 ..Default::default()
23284 },
23285 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23286 color: cx.theme().colors().editor_foreground,
23287 font_family: settings.buffer_font.family.clone(),
23288 font_features: settings.buffer_font.features.clone(),
23289 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23290 font_size: settings.buffer_font_size(cx).into(),
23291 font_weight: settings.buffer_font.weight,
23292 line_height: relative(settings.buffer_line_height.value()),
23293 ..Default::default()
23294 },
23295 };
23296 if let Some(text_style_refinement) = &self.text_style_refinement {
23297 text_style.refine(text_style_refinement)
23298 }
23299
23300 let background = match self.mode {
23301 EditorMode::SingleLine => cx.theme().system().transparent,
23302 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23303 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23304 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23305 };
23306
23307 EditorElement::new(
23308 &cx.entity(),
23309 EditorStyle {
23310 background,
23311 border: cx.theme().colors().border,
23312 local_player: cx.theme().players().local(),
23313 text: text_style,
23314 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23315 syntax: cx.theme().syntax().clone(),
23316 status: cx.theme().status().clone(),
23317 inlay_hints_style: make_inlay_hints_style(cx),
23318 edit_prediction_styles: make_suggestion_styles(cx),
23319 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23320 show_underlines: self.diagnostics_enabled(),
23321 },
23322 )
23323 }
23324}
23325
23326impl EntityInputHandler for Editor {
23327 fn text_for_range(
23328 &mut self,
23329 range_utf16: Range<usize>,
23330 adjusted_range: &mut Option<Range<usize>>,
23331 _: &mut Window,
23332 cx: &mut Context<Self>,
23333 ) -> Option<String> {
23334 let snapshot = self.buffer.read(cx).read(cx);
23335 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23336 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23337 if (start.0..end.0) != range_utf16 {
23338 adjusted_range.replace(start.0..end.0);
23339 }
23340 Some(snapshot.text_for_range(start..end).collect())
23341 }
23342
23343 fn selected_text_range(
23344 &mut self,
23345 ignore_disabled_input: bool,
23346 _: &mut Window,
23347 cx: &mut Context<Self>,
23348 ) -> Option<UTF16Selection> {
23349 // Prevent the IME menu from appearing when holding down an alphabetic key
23350 // while input is disabled.
23351 if !ignore_disabled_input && !self.input_enabled {
23352 return None;
23353 }
23354
23355 let selection = self.selections.newest::<OffsetUtf16>(cx);
23356 let range = selection.range();
23357
23358 Some(UTF16Selection {
23359 range: range.start.0..range.end.0,
23360 reversed: selection.reversed,
23361 })
23362 }
23363
23364 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23365 let snapshot = self.buffer.read(cx).read(cx);
23366 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23367 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23368 }
23369
23370 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23371 self.clear_highlights::<InputComposition>(cx);
23372 self.ime_transaction.take();
23373 }
23374
23375 fn replace_text_in_range(
23376 &mut self,
23377 range_utf16: Option<Range<usize>>,
23378 text: &str,
23379 window: &mut Window,
23380 cx: &mut Context<Self>,
23381 ) {
23382 if !self.input_enabled {
23383 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23384 return;
23385 }
23386
23387 self.transact(window, cx, |this, window, cx| {
23388 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23389 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23390 Some(this.selection_replacement_ranges(range_utf16, cx))
23391 } else {
23392 this.marked_text_ranges(cx)
23393 };
23394
23395 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23396 let newest_selection_id = this.selections.newest_anchor().id;
23397 this.selections
23398 .all::<OffsetUtf16>(cx)
23399 .iter()
23400 .zip(ranges_to_replace.iter())
23401 .find_map(|(selection, range)| {
23402 if selection.id == newest_selection_id {
23403 Some(
23404 (range.start.0 as isize - selection.head().0 as isize)
23405 ..(range.end.0 as isize - selection.head().0 as isize),
23406 )
23407 } else {
23408 None
23409 }
23410 })
23411 });
23412
23413 cx.emit(EditorEvent::InputHandled {
23414 utf16_range_to_replace: range_to_replace,
23415 text: text.into(),
23416 });
23417
23418 if let Some(new_selected_ranges) = new_selected_ranges {
23419 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23420 selections.select_ranges(new_selected_ranges)
23421 });
23422 this.backspace(&Default::default(), window, cx);
23423 }
23424
23425 this.handle_input(text, window, cx);
23426 });
23427
23428 if let Some(transaction) = self.ime_transaction {
23429 self.buffer.update(cx, |buffer, cx| {
23430 buffer.group_until_transaction(transaction, cx);
23431 });
23432 }
23433
23434 self.unmark_text(window, cx);
23435 }
23436
23437 fn replace_and_mark_text_in_range(
23438 &mut self,
23439 range_utf16: Option<Range<usize>>,
23440 text: &str,
23441 new_selected_range_utf16: Option<Range<usize>>,
23442 window: &mut Window,
23443 cx: &mut Context<Self>,
23444 ) {
23445 if !self.input_enabled {
23446 return;
23447 }
23448
23449 let transaction = self.transact(window, cx, |this, window, cx| {
23450 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23451 let snapshot = this.buffer.read(cx).read(cx);
23452 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23453 for marked_range in &mut marked_ranges {
23454 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23455 marked_range.start.0 += relative_range_utf16.start;
23456 marked_range.start =
23457 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23458 marked_range.end =
23459 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23460 }
23461 }
23462 Some(marked_ranges)
23463 } else if let Some(range_utf16) = range_utf16 {
23464 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23465 Some(this.selection_replacement_ranges(range_utf16, cx))
23466 } else {
23467 None
23468 };
23469
23470 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23471 let newest_selection_id = this.selections.newest_anchor().id;
23472 this.selections
23473 .all::<OffsetUtf16>(cx)
23474 .iter()
23475 .zip(ranges_to_replace.iter())
23476 .find_map(|(selection, range)| {
23477 if selection.id == newest_selection_id {
23478 Some(
23479 (range.start.0 as isize - selection.head().0 as isize)
23480 ..(range.end.0 as isize - selection.head().0 as isize),
23481 )
23482 } else {
23483 None
23484 }
23485 })
23486 });
23487
23488 cx.emit(EditorEvent::InputHandled {
23489 utf16_range_to_replace: range_to_replace,
23490 text: text.into(),
23491 });
23492
23493 if let Some(ranges) = ranges_to_replace {
23494 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23495 s.select_ranges(ranges)
23496 });
23497 }
23498
23499 let marked_ranges = {
23500 let snapshot = this.buffer.read(cx).read(cx);
23501 this.selections
23502 .disjoint_anchors()
23503 .iter()
23504 .map(|selection| {
23505 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23506 })
23507 .collect::<Vec<_>>()
23508 };
23509
23510 if text.is_empty() {
23511 this.unmark_text(window, cx);
23512 } else {
23513 this.highlight_text::<InputComposition>(
23514 marked_ranges.clone(),
23515 HighlightStyle {
23516 underline: Some(UnderlineStyle {
23517 thickness: px(1.),
23518 color: None,
23519 wavy: false,
23520 }),
23521 ..Default::default()
23522 },
23523 cx,
23524 );
23525 }
23526
23527 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23528 let use_autoclose = this.use_autoclose;
23529 let use_auto_surround = this.use_auto_surround;
23530 this.set_use_autoclose(false);
23531 this.set_use_auto_surround(false);
23532 this.handle_input(text, window, cx);
23533 this.set_use_autoclose(use_autoclose);
23534 this.set_use_auto_surround(use_auto_surround);
23535
23536 if let Some(new_selected_range) = new_selected_range_utf16 {
23537 let snapshot = this.buffer.read(cx).read(cx);
23538 let new_selected_ranges = marked_ranges
23539 .into_iter()
23540 .map(|marked_range| {
23541 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23542 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23543 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23544 snapshot.clip_offset_utf16(new_start, Bias::Left)
23545 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23546 })
23547 .collect::<Vec<_>>();
23548
23549 drop(snapshot);
23550 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23551 selections.select_ranges(new_selected_ranges)
23552 });
23553 }
23554 });
23555
23556 self.ime_transaction = self.ime_transaction.or(transaction);
23557 if let Some(transaction) = self.ime_transaction {
23558 self.buffer.update(cx, |buffer, cx| {
23559 buffer.group_until_transaction(transaction, cx);
23560 });
23561 }
23562
23563 if self.text_highlights::<InputComposition>(cx).is_none() {
23564 self.ime_transaction.take();
23565 }
23566 }
23567
23568 fn bounds_for_range(
23569 &mut self,
23570 range_utf16: Range<usize>,
23571 element_bounds: gpui::Bounds<Pixels>,
23572 window: &mut Window,
23573 cx: &mut Context<Self>,
23574 ) -> Option<gpui::Bounds<Pixels>> {
23575 let text_layout_details = self.text_layout_details(window);
23576 let CharacterDimensions {
23577 em_width,
23578 em_advance,
23579 line_height,
23580 } = self.character_dimensions(window);
23581
23582 let snapshot = self.snapshot(window, cx);
23583 let scroll_position = snapshot.scroll_position();
23584 let scroll_left = scroll_position.x * em_advance;
23585
23586 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23587 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23588 + self.gutter_dimensions.full_width();
23589 let y = line_height * (start.row().as_f32() - scroll_position.y);
23590
23591 Some(Bounds {
23592 origin: element_bounds.origin + point(x, y),
23593 size: size(em_width, line_height),
23594 })
23595 }
23596
23597 fn character_index_for_point(
23598 &mut self,
23599 point: gpui::Point<Pixels>,
23600 _window: &mut Window,
23601 _cx: &mut Context<Self>,
23602 ) -> Option<usize> {
23603 let position_map = self.last_position_map.as_ref()?;
23604 if !position_map.text_hitbox.contains(&point) {
23605 return None;
23606 }
23607 let display_point = position_map.point_for_position(point).previous_valid;
23608 let anchor = position_map
23609 .snapshot
23610 .display_point_to_anchor(display_point, Bias::Left);
23611 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23612 Some(utf16_offset.0)
23613 }
23614}
23615
23616trait SelectionExt {
23617 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23618 fn spanned_rows(
23619 &self,
23620 include_end_if_at_line_start: bool,
23621 map: &DisplaySnapshot,
23622 ) -> Range<MultiBufferRow>;
23623}
23624
23625impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23626 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23627 let start = self
23628 .start
23629 .to_point(&map.buffer_snapshot)
23630 .to_display_point(map);
23631 let end = self
23632 .end
23633 .to_point(&map.buffer_snapshot)
23634 .to_display_point(map);
23635 if self.reversed {
23636 end..start
23637 } else {
23638 start..end
23639 }
23640 }
23641
23642 fn spanned_rows(
23643 &self,
23644 include_end_if_at_line_start: bool,
23645 map: &DisplaySnapshot,
23646 ) -> Range<MultiBufferRow> {
23647 let start = self.start.to_point(&map.buffer_snapshot);
23648 let mut end = self.end.to_point(&map.buffer_snapshot);
23649 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23650 end.row -= 1;
23651 }
23652
23653 let buffer_start = map.prev_line_boundary(start).0;
23654 let buffer_end = map.next_line_boundary(end).0;
23655 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23656 }
23657}
23658
23659impl<T: InvalidationRegion> InvalidationStack<T> {
23660 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23661 where
23662 S: Clone + ToOffset,
23663 {
23664 while let Some(region) = self.last() {
23665 let all_selections_inside_invalidation_ranges =
23666 if selections.len() == region.ranges().len() {
23667 selections
23668 .iter()
23669 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23670 .all(|(selection, invalidation_range)| {
23671 let head = selection.head().to_offset(buffer);
23672 invalidation_range.start <= head && invalidation_range.end >= head
23673 })
23674 } else {
23675 false
23676 };
23677
23678 if all_selections_inside_invalidation_ranges {
23679 break;
23680 } else {
23681 self.pop();
23682 }
23683 }
23684 }
23685}
23686
23687impl<T> Default for InvalidationStack<T> {
23688 fn default() -> Self {
23689 Self(Default::default())
23690 }
23691}
23692
23693impl<T> Deref for InvalidationStack<T> {
23694 type Target = Vec<T>;
23695
23696 fn deref(&self) -> &Self::Target {
23697 &self.0
23698 }
23699}
23700
23701impl<T> DerefMut for InvalidationStack<T> {
23702 fn deref_mut(&mut self) -> &mut Self::Target {
23703 &mut self.0
23704 }
23705}
23706
23707impl InvalidationRegion for SnippetState {
23708 fn ranges(&self) -> &[Range<Anchor>] {
23709 &self.ranges[self.active_index]
23710 }
23711}
23712
23713fn edit_prediction_edit_text(
23714 current_snapshot: &BufferSnapshot,
23715 edits: &[(Range<Anchor>, String)],
23716 edit_preview: &EditPreview,
23717 include_deletions: bool,
23718 cx: &App,
23719) -> HighlightedText {
23720 let edits = edits
23721 .iter()
23722 .map(|(anchor, text)| {
23723 (
23724 anchor.start.text_anchor..anchor.end.text_anchor,
23725 text.clone(),
23726 )
23727 })
23728 .collect::<Vec<_>>();
23729
23730 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23731}
23732
23733fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23734 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23735 // Just show the raw edit text with basic styling
23736 let mut text = String::new();
23737 let mut highlights = Vec::new();
23738
23739 let insertion_highlight_style = HighlightStyle {
23740 color: Some(cx.theme().colors().text),
23741 ..Default::default()
23742 };
23743
23744 for (_, edit_text) in edits {
23745 let start_offset = text.len();
23746 text.push_str(edit_text);
23747 let end_offset = text.len();
23748
23749 if start_offset < end_offset {
23750 highlights.push((start_offset..end_offset, insertion_highlight_style));
23751 }
23752 }
23753
23754 HighlightedText {
23755 text: text.into(),
23756 highlights,
23757 }
23758}
23759
23760pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23761 match severity {
23762 lsp::DiagnosticSeverity::ERROR => colors.error,
23763 lsp::DiagnosticSeverity::WARNING => colors.warning,
23764 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23765 lsp::DiagnosticSeverity::HINT => colors.info,
23766 _ => colors.ignored,
23767 }
23768}
23769
23770pub fn styled_runs_for_code_label<'a>(
23771 label: &'a CodeLabel,
23772 syntax_theme: &'a theme::SyntaxTheme,
23773) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23774 let fade_out = HighlightStyle {
23775 fade_out: Some(0.35),
23776 ..Default::default()
23777 };
23778
23779 let mut prev_end = label.filter_range.end;
23780 label
23781 .runs
23782 .iter()
23783 .enumerate()
23784 .flat_map(move |(ix, (range, highlight_id))| {
23785 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23786 style
23787 } else {
23788 return Default::default();
23789 };
23790 let mut muted_style = style;
23791 muted_style.highlight(fade_out);
23792
23793 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23794 if range.start >= label.filter_range.end {
23795 if range.start > prev_end {
23796 runs.push((prev_end..range.start, fade_out));
23797 }
23798 runs.push((range.clone(), muted_style));
23799 } else if range.end <= label.filter_range.end {
23800 runs.push((range.clone(), style));
23801 } else {
23802 runs.push((range.start..label.filter_range.end, style));
23803 runs.push((label.filter_range.end..range.end, muted_style));
23804 }
23805 prev_end = cmp::max(prev_end, range.end);
23806
23807 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23808 runs.push((prev_end..label.text.len(), fade_out));
23809 }
23810
23811 runs
23812 })
23813}
23814
23815pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23816 let mut prev_index = 0;
23817 let mut prev_codepoint: Option<char> = None;
23818 text.char_indices()
23819 .chain([(text.len(), '\0')])
23820 .filter_map(move |(index, codepoint)| {
23821 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23822 let is_boundary = index == text.len()
23823 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23824 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23825 if is_boundary {
23826 let chunk = &text[prev_index..index];
23827 prev_index = index;
23828 Some(chunk)
23829 } else {
23830 None
23831 }
23832 })
23833}
23834
23835pub trait RangeToAnchorExt: Sized {
23836 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23837
23838 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23839 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23840 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23841 }
23842}
23843
23844impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23845 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23846 let start_offset = self.start.to_offset(snapshot);
23847 let end_offset = self.end.to_offset(snapshot);
23848 if start_offset == end_offset {
23849 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23850 } else {
23851 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23852 }
23853 }
23854}
23855
23856pub trait RowExt {
23857 fn as_f32(&self) -> f32;
23858
23859 fn next_row(&self) -> Self;
23860
23861 fn previous_row(&self) -> Self;
23862
23863 fn minus(&self, other: Self) -> u32;
23864}
23865
23866impl RowExt for DisplayRow {
23867 fn as_f32(&self) -> f32 {
23868 self.0 as f32
23869 }
23870
23871 fn next_row(&self) -> Self {
23872 Self(self.0 + 1)
23873 }
23874
23875 fn previous_row(&self) -> Self {
23876 Self(self.0.saturating_sub(1))
23877 }
23878
23879 fn minus(&self, other: Self) -> u32 {
23880 self.0 - other.0
23881 }
23882}
23883
23884impl RowExt for MultiBufferRow {
23885 fn as_f32(&self) -> f32 {
23886 self.0 as f32
23887 }
23888
23889 fn next_row(&self) -> Self {
23890 Self(self.0 + 1)
23891 }
23892
23893 fn previous_row(&self) -> Self {
23894 Self(self.0.saturating_sub(1))
23895 }
23896
23897 fn minus(&self, other: Self) -> u32 {
23898 self.0 - other.0
23899 }
23900}
23901
23902trait RowRangeExt {
23903 type Row;
23904
23905 fn len(&self) -> usize;
23906
23907 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23908}
23909
23910impl RowRangeExt for Range<MultiBufferRow> {
23911 type Row = MultiBufferRow;
23912
23913 fn len(&self) -> usize {
23914 (self.end.0 - self.start.0) as usize
23915 }
23916
23917 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23918 (self.start.0..self.end.0).map(MultiBufferRow)
23919 }
23920}
23921
23922impl RowRangeExt for Range<DisplayRow> {
23923 type Row = DisplayRow;
23924
23925 fn len(&self) -> usize {
23926 (self.end.0 - self.start.0) as usize
23927 }
23928
23929 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23930 (self.start.0..self.end.0).map(DisplayRow)
23931 }
23932}
23933
23934/// If select range has more than one line, we
23935/// just point the cursor to range.start.
23936fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23937 if range.start.row == range.end.row {
23938 range
23939 } else {
23940 range.start..range.start
23941 }
23942}
23943pub struct KillRing(ClipboardItem);
23944impl Global for KillRing {}
23945
23946const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23947
23948enum BreakpointPromptEditAction {
23949 Log,
23950 Condition,
23951 HitCondition,
23952}
23953
23954struct BreakpointPromptEditor {
23955 pub(crate) prompt: Entity<Editor>,
23956 editor: WeakEntity<Editor>,
23957 breakpoint_anchor: Anchor,
23958 breakpoint: Breakpoint,
23959 edit_action: BreakpointPromptEditAction,
23960 block_ids: HashSet<CustomBlockId>,
23961 editor_margins: Arc<Mutex<EditorMargins>>,
23962 _subscriptions: Vec<Subscription>,
23963}
23964
23965impl BreakpointPromptEditor {
23966 const MAX_LINES: u8 = 4;
23967
23968 fn new(
23969 editor: WeakEntity<Editor>,
23970 breakpoint_anchor: Anchor,
23971 breakpoint: Breakpoint,
23972 edit_action: BreakpointPromptEditAction,
23973 window: &mut Window,
23974 cx: &mut Context<Self>,
23975 ) -> Self {
23976 let base_text = match edit_action {
23977 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23978 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23979 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23980 }
23981 .map(|msg| msg.to_string())
23982 .unwrap_or_default();
23983
23984 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23985 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23986
23987 let prompt = cx.new(|cx| {
23988 let mut prompt = Editor::new(
23989 EditorMode::AutoHeight {
23990 min_lines: 1,
23991 max_lines: Some(Self::MAX_LINES as usize),
23992 },
23993 buffer,
23994 None,
23995 window,
23996 cx,
23997 );
23998 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23999 prompt.set_show_cursor_when_unfocused(false, cx);
24000 prompt.set_placeholder_text(
24001 match edit_action {
24002 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24003 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24004 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24005 },
24006 cx,
24007 );
24008
24009 prompt
24010 });
24011
24012 Self {
24013 prompt,
24014 editor,
24015 breakpoint_anchor,
24016 breakpoint,
24017 edit_action,
24018 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24019 block_ids: Default::default(),
24020 _subscriptions: vec![],
24021 }
24022 }
24023
24024 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24025 self.block_ids.extend(block_ids)
24026 }
24027
24028 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24029 if let Some(editor) = self.editor.upgrade() {
24030 let message = self
24031 .prompt
24032 .read(cx)
24033 .buffer
24034 .read(cx)
24035 .as_singleton()
24036 .expect("A multi buffer in breakpoint prompt isn't possible")
24037 .read(cx)
24038 .as_rope()
24039 .to_string();
24040
24041 editor.update(cx, |editor, cx| {
24042 editor.edit_breakpoint_at_anchor(
24043 self.breakpoint_anchor,
24044 self.breakpoint.clone(),
24045 match self.edit_action {
24046 BreakpointPromptEditAction::Log => {
24047 BreakpointEditAction::EditLogMessage(message.into())
24048 }
24049 BreakpointPromptEditAction::Condition => {
24050 BreakpointEditAction::EditCondition(message.into())
24051 }
24052 BreakpointPromptEditAction::HitCondition => {
24053 BreakpointEditAction::EditHitCondition(message.into())
24054 }
24055 },
24056 cx,
24057 );
24058
24059 editor.remove_blocks(self.block_ids.clone(), None, cx);
24060 cx.focus_self(window);
24061 });
24062 }
24063 }
24064
24065 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24066 self.editor
24067 .update(cx, |editor, cx| {
24068 editor.remove_blocks(self.block_ids.clone(), None, cx);
24069 window.focus(&editor.focus_handle);
24070 })
24071 .log_err();
24072 }
24073
24074 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24075 let settings = ThemeSettings::get_global(cx);
24076 let text_style = TextStyle {
24077 color: if self.prompt.read(cx).read_only(cx) {
24078 cx.theme().colors().text_disabled
24079 } else {
24080 cx.theme().colors().text
24081 },
24082 font_family: settings.buffer_font.family.clone(),
24083 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24084 font_size: settings.buffer_font_size(cx).into(),
24085 font_weight: settings.buffer_font.weight,
24086 line_height: relative(settings.buffer_line_height.value()),
24087 ..Default::default()
24088 };
24089 EditorElement::new(
24090 &self.prompt,
24091 EditorStyle {
24092 background: cx.theme().colors().editor_background,
24093 local_player: cx.theme().players().local(),
24094 text: text_style,
24095 ..Default::default()
24096 },
24097 )
24098 }
24099}
24100
24101impl Render for BreakpointPromptEditor {
24102 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24103 let editor_margins = *self.editor_margins.lock();
24104 let gutter_dimensions = editor_margins.gutter;
24105 h_flex()
24106 .key_context("Editor")
24107 .bg(cx.theme().colors().editor_background)
24108 .border_y_1()
24109 .border_color(cx.theme().status().info_border)
24110 .size_full()
24111 .py(window.line_height() / 2.5)
24112 .on_action(cx.listener(Self::confirm))
24113 .on_action(cx.listener(Self::cancel))
24114 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24115 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24116 }
24117}
24118
24119impl Focusable for BreakpointPromptEditor {
24120 fn focus_handle(&self, cx: &App) -> FocusHandle {
24121 self.prompt.focus_handle(cx)
24122 }
24123}
24124
24125fn all_edits_insertions_or_deletions(
24126 edits: &Vec<(Range<Anchor>, String)>,
24127 snapshot: &MultiBufferSnapshot,
24128) -> bool {
24129 let mut all_insertions = true;
24130 let mut all_deletions = true;
24131
24132 for (range, new_text) in edits.iter() {
24133 let range_is_empty = range.to_offset(snapshot).is_empty();
24134 let text_is_empty = new_text.is_empty();
24135
24136 if range_is_empty != text_is_empty {
24137 if range_is_empty {
24138 all_deletions = false;
24139 } else {
24140 all_insertions = false;
24141 }
24142 } else {
24143 return false;
24144 }
24145
24146 if !all_insertions && !all_deletions {
24147 return false;
24148 }
24149 }
24150 all_insertions || all_deletions
24151}
24152
24153struct MissingEditPredictionKeybindingTooltip;
24154
24155impl Render for MissingEditPredictionKeybindingTooltip {
24156 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24157 ui::tooltip_container(window, cx, |container, _, cx| {
24158 container
24159 .flex_shrink_0()
24160 .max_w_80()
24161 .min_h(rems_from_px(124.))
24162 .justify_between()
24163 .child(
24164 v_flex()
24165 .flex_1()
24166 .text_ui_sm(cx)
24167 .child(Label::new("Conflict with Accept Keybinding"))
24168 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24169 )
24170 .child(
24171 h_flex()
24172 .pb_1()
24173 .gap_1()
24174 .items_end()
24175 .w_full()
24176 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24177 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24178 }))
24179 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24180 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24181 })),
24182 )
24183 })
24184 }
24185}
24186
24187#[derive(Debug, Clone, Copy, PartialEq)]
24188pub struct LineHighlight {
24189 pub background: Background,
24190 pub border: Option<gpui::Hsla>,
24191 pub include_gutter: bool,
24192 pub type_id: Option<TypeId>,
24193}
24194
24195struct LineManipulationResult {
24196 pub new_text: String,
24197 pub line_count_before: usize,
24198 pub line_count_after: usize,
24199}
24200
24201fn render_diff_hunk_controls(
24202 row: u32,
24203 status: &DiffHunkStatus,
24204 hunk_range: Range<Anchor>,
24205 is_created_file: bool,
24206 line_height: Pixels,
24207 editor: &Entity<Editor>,
24208 _window: &mut Window,
24209 cx: &mut App,
24210) -> AnyElement {
24211 h_flex()
24212 .h(line_height)
24213 .mr_1()
24214 .gap_1()
24215 .px_0p5()
24216 .pb_1()
24217 .border_x_1()
24218 .border_b_1()
24219 .border_color(cx.theme().colors().border_variant)
24220 .rounded_b_lg()
24221 .bg(cx.theme().colors().editor_background)
24222 .gap_1()
24223 .block_mouse_except_scroll()
24224 .shadow_md()
24225 .child(if status.has_secondary_hunk() {
24226 Button::new(("stage", row as u64), "Stage")
24227 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24228 .tooltip({
24229 let focus_handle = editor.focus_handle(cx);
24230 move |window, cx| {
24231 Tooltip::for_action_in(
24232 "Stage Hunk",
24233 &::git::ToggleStaged,
24234 &focus_handle,
24235 window,
24236 cx,
24237 )
24238 }
24239 })
24240 .on_click({
24241 let editor = editor.clone();
24242 move |_event, _window, cx| {
24243 editor.update(cx, |editor, cx| {
24244 editor.stage_or_unstage_diff_hunks(
24245 true,
24246 vec![hunk_range.start..hunk_range.start],
24247 cx,
24248 );
24249 });
24250 }
24251 })
24252 } else {
24253 Button::new(("unstage", row as u64), "Unstage")
24254 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24255 .tooltip({
24256 let focus_handle = editor.focus_handle(cx);
24257 move |window, cx| {
24258 Tooltip::for_action_in(
24259 "Unstage Hunk",
24260 &::git::ToggleStaged,
24261 &focus_handle,
24262 window,
24263 cx,
24264 )
24265 }
24266 })
24267 .on_click({
24268 let editor = editor.clone();
24269 move |_event, _window, cx| {
24270 editor.update(cx, |editor, cx| {
24271 editor.stage_or_unstage_diff_hunks(
24272 false,
24273 vec![hunk_range.start..hunk_range.start],
24274 cx,
24275 );
24276 });
24277 }
24278 })
24279 })
24280 .child(
24281 Button::new(("restore", row as u64), "Restore")
24282 .tooltip({
24283 let focus_handle = editor.focus_handle(cx);
24284 move |window, cx| {
24285 Tooltip::for_action_in(
24286 "Restore Hunk",
24287 &::git::Restore,
24288 &focus_handle,
24289 window,
24290 cx,
24291 )
24292 }
24293 })
24294 .on_click({
24295 let editor = editor.clone();
24296 move |_event, window, cx| {
24297 editor.update(cx, |editor, cx| {
24298 let snapshot = editor.snapshot(window, cx);
24299 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24300 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24301 });
24302 }
24303 })
24304 .disabled(is_created_file),
24305 )
24306 .when(
24307 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24308 |el| {
24309 el.child(
24310 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24311 .shape(IconButtonShape::Square)
24312 .icon_size(IconSize::Small)
24313 // .disabled(!has_multiple_hunks)
24314 .tooltip({
24315 let focus_handle = editor.focus_handle(cx);
24316 move |window, cx| {
24317 Tooltip::for_action_in(
24318 "Next Hunk",
24319 &GoToHunk,
24320 &focus_handle,
24321 window,
24322 cx,
24323 )
24324 }
24325 })
24326 .on_click({
24327 let editor = editor.clone();
24328 move |_event, window, cx| {
24329 editor.update(cx, |editor, cx| {
24330 let snapshot = editor.snapshot(window, cx);
24331 let position =
24332 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24333 editor.go_to_hunk_before_or_after_position(
24334 &snapshot,
24335 position,
24336 Direction::Next,
24337 window,
24338 cx,
24339 );
24340 editor.expand_selected_diff_hunks(cx);
24341 });
24342 }
24343 }),
24344 )
24345 .child(
24346 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24347 .shape(IconButtonShape::Square)
24348 .icon_size(IconSize::Small)
24349 // .disabled(!has_multiple_hunks)
24350 .tooltip({
24351 let focus_handle = editor.focus_handle(cx);
24352 move |window, cx| {
24353 Tooltip::for_action_in(
24354 "Previous Hunk",
24355 &GoToPreviousHunk,
24356 &focus_handle,
24357 window,
24358 cx,
24359 )
24360 }
24361 })
24362 .on_click({
24363 let editor = editor.clone();
24364 move |_event, window, cx| {
24365 editor.update(cx, |editor, cx| {
24366 let snapshot = editor.snapshot(window, cx);
24367 let point =
24368 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24369 editor.go_to_hunk_before_or_after_position(
24370 &snapshot,
24371 point,
24372 Direction::Prev,
24373 window,
24374 cx,
24375 );
24376 editor.expand_selected_diff_hunks(cx);
24377 });
24378 }
24379 }),
24380 )
24381 },
24382 )
24383 .into_any_element()
24384}
24385
24386pub fn multibuffer_context_lines(cx: &App) -> u32 {
24387 EditorSettings::try_get(cx)
24388 .map(|settings| settings.excerpt_context_lines)
24389 .unwrap_or(2)
24390 .clamp(1, 32)
24391}