1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap, ShowScrollbar,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CodeLabel, CursorShape, DiagnosticEntry,
125 DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize,
126 Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal, TextObject,
127 TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{
164 DiagnosticSeverity, GitGutterSetting, GoToDiagnosticSeverityFilter, ProjectSettings,
165 },
166};
167use rand::seq::SliceRandom;
168use rpc::{ErrorCode, ErrorExt, proto::PeerId};
169use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
170use selections_collection::{
171 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
172};
173use serde::{Deserialize, Serialize};
174use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
175use smallvec::{SmallVec, smallvec};
176use snippet::Snippet;
177use std::{
178 any::TypeId,
179 borrow::Cow,
180 cell::OnceCell,
181 cell::RefCell,
182 cmp::{self, Ordering, Reverse},
183 iter::Peekable,
184 mem,
185 num::NonZeroU32,
186 ops::Not,
187 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
188 path::{Path, PathBuf},
189 rc::Rc,
190 sync::Arc,
191 time::{Duration, Instant},
192};
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
223const MAX_LINE_LEN: usize = 1024;
224const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
225const MAX_SELECTION_HISTORY_LEN: usize = 1024;
226pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
227#[doc(hidden)]
228pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
229pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
230
231pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268struct InlineValueCache {
269 enabled: bool,
270 inlays: Vec<InlayId>,
271 refresh_task: Task<Option<()>>,
272}
273
274impl InlineValueCache {
275 fn new(enabled: bool) -> Self {
276 Self {
277 enabled,
278 inlays: Vec::new(),
279 refresh_task: Task::ready(None),
280 }
281 }
282}
283
284#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
285pub enum InlayId {
286 EditPrediction(usize),
287 DebuggerValue(usize),
288 // LSP
289 Hint(usize),
290 Color(usize),
291}
292
293impl InlayId {
294 fn id(&self) -> usize {
295 match self {
296 Self::EditPrediction(id) => *id,
297 Self::DebuggerValue(id) => *id,
298 Self::Hint(id) => *id,
299 Self::Color(id) => *id,
300 }
301 }
302}
303
304pub enum ActiveDebugLine {}
305pub enum DebugStackFrameLine {}
306enum DocumentHighlightRead {}
307enum DocumentHighlightWrite {}
308enum InputComposition {}
309pub enum PendingInput {}
310enum SelectedTextHighlight {}
311
312pub enum ConflictsOuter {}
313pub enum ConflictsOurs {}
314pub enum ConflictsTheirs {}
315pub enum ConflictsOursMarker {}
316pub enum ConflictsTheirsMarker {}
317
318#[derive(Debug, Copy, Clone, PartialEq, Eq)]
319pub enum Navigated {
320 Yes,
321 No,
322}
323
324impl Navigated {
325 pub fn from_bool(yes: bool) -> Navigated {
326 if yes { Navigated::Yes } else { Navigated::No }
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331enum DisplayDiffHunk {
332 Folded {
333 display_row: DisplayRow,
334 },
335 Unfolded {
336 is_created_file: bool,
337 diff_base_byte_range: Range<usize>,
338 display_row_range: Range<DisplayRow>,
339 multi_buffer_range: Range<Anchor>,
340 status: DiffHunkStatus,
341 },
342}
343
344pub enum HideMouseCursorOrigin {
345 TypingAction,
346 MovementAction,
347}
348
349pub fn init_settings(cx: &mut App) {
350 EditorSettings::register(cx);
351}
352
353pub fn init(cx: &mut App) {
354 init_settings(cx);
355
356 cx.set_global(GlobalBlameRenderer(Arc::new(())));
357
358 workspace::register_project_item::<Editor>(cx);
359 workspace::FollowableViewRegistry::register::<Editor>(cx);
360 workspace::register_serializable_item::<Editor>(cx);
361
362 cx.observe_new(
363 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
364 workspace.register_action(Editor::new_file);
365 workspace.register_action(Editor::new_file_vertical);
366 workspace.register_action(Editor::new_file_horizontal);
367 workspace.register_action(Editor::cancel_language_server_work);
368 workspace.register_action(Editor::toggle_focus);
369 },
370 )
371 .detach();
372
373 cx.on_action(move |_: &workspace::NewFile, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387 cx.on_action(move |_: &workspace::NewWindow, cx| {
388 let app_state = workspace::AppState::global(cx);
389 if let Some(app_state) = app_state.upgrade() {
390 workspace::open_new(
391 Default::default(),
392 app_state,
393 cx,
394 |workspace, window, cx| {
395 cx.activate(true);
396 Editor::new_file(workspace, &Default::default(), window, cx)
397 },
398 )
399 .detach();
400 }
401 });
402}
403
404pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
405 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
406}
407
408pub trait DiagnosticRenderer {
409 fn render_group(
410 &self,
411 diagnostic_group: Vec<DiagnosticEntry<Point>>,
412 buffer_id: BufferId,
413 snapshot: EditorSnapshot,
414 editor: WeakEntity<Editor>,
415 cx: &mut App,
416 ) -> Vec<BlockProperties<Anchor>>;
417
418 fn render_hover(
419 &self,
420 diagnostic_group: Vec<DiagnosticEntry<Point>>,
421 range: Range<Point>,
422 buffer_id: BufferId,
423 cx: &mut App,
424 ) -> Option<Entity<markdown::Markdown>>;
425
426 fn open_link(
427 &self,
428 editor: &mut Editor,
429 link: SharedString,
430 window: &mut Window,
431 cx: &mut Context<Editor>,
432 );
433}
434
435pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
436
437impl GlobalDiagnosticRenderer {
438 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
439 cx.try_global::<Self>().map(|g| g.0.clone())
440 }
441}
442
443impl gpui::Global for GlobalDiagnosticRenderer {}
444pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
445 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
446}
447
448pub struct SearchWithinRange;
449
450trait InvalidationRegion {
451 fn ranges(&self) -> &[Range<Anchor>];
452}
453
454#[derive(Clone, Debug, PartialEq)]
455pub enum SelectPhase {
456 Begin {
457 position: DisplayPoint,
458 add: bool,
459 click_count: usize,
460 },
461 BeginColumnar {
462 position: DisplayPoint,
463 reset: bool,
464 mode: ColumnarMode,
465 goal_column: u32,
466 },
467 Extend {
468 position: DisplayPoint,
469 click_count: usize,
470 },
471 Update {
472 position: DisplayPoint,
473 goal_column: u32,
474 scroll_delta: gpui::Point<f32>,
475 },
476 End,
477}
478
479#[derive(Clone, Debug, PartialEq)]
480pub enum ColumnarMode {
481 FromMouse,
482 FromSelection,
483}
484
485#[derive(Clone, Debug)]
486pub enum SelectMode {
487 Character,
488 Word(Range<Anchor>),
489 Line(Range<Anchor>),
490 All,
491}
492
493#[derive(Clone, PartialEq, Eq, Debug)]
494pub enum EditorMode {
495 SingleLine,
496 AutoHeight {
497 min_lines: usize,
498 max_lines: Option<usize>,
499 },
500 Full {
501 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
502 scale_ui_elements_with_buffer_font_size: bool,
503 /// When set to `true`, the editor will render a background for the active line.
504 show_active_line_background: bool,
505 /// When set to `true`, the editor's height will be determined by its content.
506 sized_by_content: bool,
507 },
508 Minimap {
509 parent: WeakEntity<Editor>,
510 },
511}
512
513impl EditorMode {
514 pub fn full() -> Self {
515 Self::Full {
516 scale_ui_elements_with_buffer_font_size: true,
517 show_active_line_background: true,
518 sized_by_content: false,
519 }
520 }
521
522 #[inline]
523 pub fn is_full(&self) -> bool {
524 matches!(self, Self::Full { .. })
525 }
526
527 #[inline]
528 pub fn is_single_line(&self) -> bool {
529 matches!(self, Self::SingleLine { .. })
530 }
531
532 #[inline]
533 fn is_minimap(&self) -> bool {
534 matches!(self, Self::Minimap { .. })
535 }
536}
537
538#[derive(Copy, Clone, Debug)]
539pub enum SoftWrap {
540 /// Prefer not to wrap at all.
541 ///
542 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
543 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
544 GitDiff,
545 /// Prefer a single line generally, unless an overly long line is encountered.
546 None,
547 /// Soft wrap lines that exceed the editor width.
548 EditorWidth,
549 /// Soft wrap lines at the preferred line length.
550 Column(u32),
551 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
552 Bounded(u32),
553}
554
555#[derive(Clone)]
556pub struct EditorStyle {
557 pub background: Hsla,
558 pub border: Hsla,
559 pub local_player: PlayerColor,
560 pub text: TextStyle,
561 pub scrollbar_width: Pixels,
562 pub syntax: Arc<SyntaxTheme>,
563 pub status: StatusColors,
564 pub inlay_hints_style: HighlightStyle,
565 pub edit_prediction_styles: EditPredictionStyles,
566 pub unnecessary_code_fade: f32,
567 pub show_underlines: bool,
568}
569
570impl Default for EditorStyle {
571 fn default() -> Self {
572 Self {
573 background: Hsla::default(),
574 border: Hsla::default(),
575 local_player: PlayerColor::default(),
576 text: TextStyle::default(),
577 scrollbar_width: Pixels::default(),
578 syntax: Default::default(),
579 // HACK: Status colors don't have a real default.
580 // We should look into removing the status colors from the editor
581 // style and retrieve them directly from the theme.
582 status: StatusColors::dark(),
583 inlay_hints_style: HighlightStyle::default(),
584 edit_prediction_styles: EditPredictionStyles {
585 insertion: HighlightStyle::default(),
586 whitespace: HighlightStyle::default(),
587 },
588 unnecessary_code_fade: Default::default(),
589 show_underlines: true,
590 }
591 }
592}
593
594pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
595 let show_background = language_settings::language_settings(None, None, cx)
596 .inlay_hints
597 .show_background;
598
599 HighlightStyle {
600 color: Some(cx.theme().status().hint),
601 background_color: show_background.then(|| cx.theme().status().hint_background),
602 ..HighlightStyle::default()
603 }
604}
605
606pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
607 EditPredictionStyles {
608 insertion: HighlightStyle {
609 color: Some(cx.theme().status().predictive),
610 ..HighlightStyle::default()
611 },
612 whitespace: HighlightStyle {
613 background_color: Some(cx.theme().status().created_background),
614 ..HighlightStyle::default()
615 },
616 }
617}
618
619type CompletionId = usize;
620
621pub(crate) enum EditDisplayMode {
622 TabAccept,
623 DiffPopover,
624 Inline,
625}
626
627enum EditPrediction {
628 Edit {
629 edits: Vec<(Range<Anchor>, String)>,
630 edit_preview: Option<EditPreview>,
631 display_mode: EditDisplayMode,
632 snapshot: BufferSnapshot,
633 },
634 Move {
635 target: Anchor,
636 snapshot: BufferSnapshot,
637 },
638}
639
640struct EditPredictionState {
641 inlay_ids: Vec<InlayId>,
642 completion: EditPrediction,
643 completion_id: Option<SharedString>,
644 invalidation_range: Range<Anchor>,
645}
646
647enum EditPredictionSettings {
648 Disabled,
649 Enabled {
650 show_in_menu: bool,
651 preview_requires_modifier: bool,
652 },
653}
654
655enum EditPredictionHighlight {}
656
657#[derive(Debug, Clone)]
658struct InlineDiagnostic {
659 message: SharedString,
660 group_id: usize,
661 is_primary: bool,
662 start: Point,
663 severity: lsp::DiagnosticSeverity,
664}
665
666pub enum MenuEditPredictionsPolicy {
667 Never,
668 ByProvider,
669}
670
671pub enum EditPredictionPreview {
672 /// Modifier is not pressed
673 Inactive { released_too_fast: bool },
674 /// Modifier pressed
675 Active {
676 since: Instant,
677 previous_scroll_position: Option<ScrollAnchor>,
678 },
679}
680
681impl EditPredictionPreview {
682 pub fn released_too_fast(&self) -> bool {
683 match self {
684 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
685 EditPredictionPreview::Active { .. } => false,
686 }
687 }
688
689 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
690 if let EditPredictionPreview::Active {
691 previous_scroll_position,
692 ..
693 } = self
694 {
695 *previous_scroll_position = scroll_position;
696 }
697 }
698}
699
700pub struct ContextMenuOptions {
701 pub min_entries_visible: usize,
702 pub max_entries_visible: usize,
703 pub placement: Option<ContextMenuPlacement>,
704}
705
706#[derive(Debug, Clone, PartialEq, Eq)]
707pub enum ContextMenuPlacement {
708 Above,
709 Below,
710}
711
712#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
713struct EditorActionId(usize);
714
715impl EditorActionId {
716 pub fn post_inc(&mut self) -> Self {
717 let answer = self.0;
718
719 *self = Self(answer + 1);
720
721 Self(answer)
722 }
723}
724
725// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
726// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
727
728type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
729type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
730
731#[derive(Default)]
732struct ScrollbarMarkerState {
733 scrollbar_size: Size<Pixels>,
734 dirty: bool,
735 markers: Arc<[PaintQuad]>,
736 pending_refresh: Option<Task<Result<()>>>,
737}
738
739impl ScrollbarMarkerState {
740 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
741 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
742 }
743}
744
745#[derive(Clone, Copy, PartialEq, Eq)]
746pub enum MinimapVisibility {
747 Disabled,
748 Enabled {
749 /// The configuration currently present in the users settings.
750 setting_configuration: bool,
751 /// Whether to override the currently set visibility from the users setting.
752 toggle_override: bool,
753 },
754}
755
756impl MinimapVisibility {
757 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
758 if mode.is_full() {
759 Self::Enabled {
760 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
761 toggle_override: false,
762 }
763 } else {
764 Self::Disabled
765 }
766 }
767
768 fn hidden(&self) -> Self {
769 match *self {
770 Self::Enabled {
771 setting_configuration,
772 ..
773 } => Self::Enabled {
774 setting_configuration,
775 toggle_override: setting_configuration,
776 },
777 Self::Disabled => Self::Disabled,
778 }
779 }
780
781 fn disabled(&self) -> bool {
782 matches!(*self, Self::Disabled)
783 }
784
785 fn settings_visibility(&self) -> bool {
786 match *self {
787 Self::Enabled {
788 setting_configuration,
789 ..
790 } => setting_configuration,
791 _ => false,
792 }
793 }
794
795 fn visible(&self) -> bool {
796 match *self {
797 Self::Enabled {
798 setting_configuration,
799 toggle_override,
800 } => setting_configuration ^ toggle_override,
801 _ => false,
802 }
803 }
804
805 fn toggle_visibility(&self) -> Self {
806 match *self {
807 Self::Enabled {
808 toggle_override,
809 setting_configuration,
810 } => Self::Enabled {
811 setting_configuration,
812 toggle_override: !toggle_override,
813 },
814 Self::Disabled => Self::Disabled,
815 }
816 }
817}
818
819#[derive(Clone, Debug)]
820struct RunnableTasks {
821 templates: Vec<(TaskSourceKind, TaskTemplate)>,
822 offset: multi_buffer::Anchor,
823 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
824 column: u32,
825 // Values of all named captures, including those starting with '_'
826 extra_variables: HashMap<String, String>,
827 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
828 context_range: Range<BufferOffset>,
829}
830
831impl RunnableTasks {
832 fn resolve<'a>(
833 &'a self,
834 cx: &'a task::TaskContext,
835 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
836 self.templates.iter().filter_map(|(kind, template)| {
837 template
838 .resolve_task(&kind.to_id_base(), cx)
839 .map(|task| (kind.clone(), task))
840 })
841 }
842}
843
844#[derive(Clone)]
845pub struct ResolvedTasks {
846 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
847 position: Anchor,
848}
849
850#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
851struct BufferOffset(usize);
852
853// Addons allow storing per-editor state in other crates (e.g. Vim)
854pub trait Addon: 'static {
855 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
856
857 fn render_buffer_header_controls(
858 &self,
859 _: &ExcerptInfo,
860 _: &Window,
861 _: &App,
862 ) -> Option<AnyElement> {
863 None
864 }
865
866 fn to_any(&self) -> &dyn std::any::Any;
867
868 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
869 None
870 }
871}
872
873struct ChangeLocation {
874 current: Option<Vec<Anchor>>,
875 original: Vec<Anchor>,
876}
877impl ChangeLocation {
878 fn locations(&self) -> &[Anchor] {
879 self.current.as_ref().unwrap_or(&self.original)
880 }
881}
882
883/// A set of caret positions, registered when the editor was edited.
884pub struct ChangeList {
885 changes: Vec<ChangeLocation>,
886 /// Currently "selected" change.
887 position: Option<usize>,
888}
889
890impl ChangeList {
891 pub fn new() -> Self {
892 Self {
893 changes: Vec::new(),
894 position: None,
895 }
896 }
897
898 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
899 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
900 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
901 if self.changes.is_empty() {
902 return None;
903 }
904
905 let prev = self.position.unwrap_or(self.changes.len());
906 let next = if direction == Direction::Prev {
907 prev.saturating_sub(count)
908 } else {
909 (prev + count).min(self.changes.len() - 1)
910 };
911 self.position = Some(next);
912 self.changes.get(next).map(|change| change.locations())
913 }
914
915 /// Adds a new change to the list, resetting the change list position.
916 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
917 self.position.take();
918 if let Some(last) = self.changes.last_mut()
919 && group
920 {
921 last.current = Some(new_positions)
922 } else {
923 self.changes.push(ChangeLocation {
924 original: new_positions,
925 current: None,
926 });
927 }
928 }
929
930 pub fn last(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.locations())
932 }
933
934 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
935 self.changes.last().map(|change| change.original.as_slice())
936 }
937
938 pub fn invert_last_group(&mut self) {
939 if let Some(last) = self.changes.last_mut()
940 && let Some(current) = last.current.as_mut()
941 {
942 mem::swap(&mut last.original, current);
943 }
944 }
945}
946
947#[derive(Clone)]
948struct InlineBlamePopoverState {
949 scroll_handle: ScrollHandle,
950 commit_message: Option<ParsedCommitMessage>,
951 markdown: Entity<Markdown>,
952}
953
954struct InlineBlamePopover {
955 position: gpui::Point<Pixels>,
956 hide_task: Option<Task<()>>,
957 popover_bounds: Option<Bounds<Pixels>>,
958 popover_state: InlineBlamePopoverState,
959 keyboard_grace: bool,
960}
961
962enum SelectionDragState {
963 /// State when no drag related activity is detected.
964 None,
965 /// State when the mouse is down on a selection that is about to be dragged.
966 ReadyToDrag {
967 selection: Selection<Anchor>,
968 click_position: gpui::Point<Pixels>,
969 mouse_down_time: Instant,
970 },
971 /// State when the mouse is dragging the selection in the editor.
972 Dragging {
973 selection: Selection<Anchor>,
974 drop_cursor: Selection<Anchor>,
975 hide_drop_cursor: bool,
976 },
977}
978
979enum ColumnarSelectionState {
980 FromMouse {
981 selection_tail: Anchor,
982 display_point: Option<DisplayPoint>,
983 },
984 FromSelection {
985 selection_tail: Anchor,
986 },
987}
988
989/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
990/// a breakpoint on them.
991#[derive(Clone, Copy, Debug, PartialEq, Eq)]
992struct PhantomBreakpointIndicator {
993 display_row: DisplayRow,
994 /// There's a small debounce between hovering over the line and showing the indicator.
995 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
996 is_active: bool,
997 collides_with_existing_breakpoint: bool,
998}
999
1000/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1001///
1002/// See the [module level documentation](self) for more information.
1003pub struct Editor {
1004 focus_handle: FocusHandle,
1005 last_focused_descendant: Option<WeakFocusHandle>,
1006 /// The text buffer being edited
1007 buffer: Entity<MultiBuffer>,
1008 /// Map of how text in the buffer should be displayed.
1009 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1010 pub display_map: Entity<DisplayMap>,
1011 pub selections: SelectionsCollection,
1012 pub scroll_manager: ScrollManager,
1013 /// When inline assist editors are linked, they all render cursors because
1014 /// typing enters text into each of them, even the ones that aren't focused.
1015 pub(crate) show_cursor_when_unfocused: bool,
1016 columnar_selection_state: Option<ColumnarSelectionState>,
1017 add_selections_state: Option<AddSelectionsState>,
1018 select_next_state: Option<SelectNextState>,
1019 select_prev_state: Option<SelectNextState>,
1020 selection_history: SelectionHistory,
1021 defer_selection_effects: bool,
1022 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1023 autoclose_regions: Vec<AutocloseRegion>,
1024 snippet_stack: InvalidationStack<SnippetState>,
1025 select_syntax_node_history: SelectSyntaxNodeHistory,
1026 ime_transaction: Option<TransactionId>,
1027 pub diagnostics_max_severity: DiagnosticSeverity,
1028 active_diagnostics: ActiveDiagnostic,
1029 show_inline_diagnostics: bool,
1030 inline_diagnostics_update: Task<()>,
1031 inline_diagnostics_enabled: bool,
1032 diagnostics_enabled: bool,
1033 word_completions_enabled: bool,
1034 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1035 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1036 hard_wrap: Option<usize>,
1037 project: Option<Entity<Project>>,
1038 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1039 completion_provider: Option<Rc<dyn CompletionProvider>>,
1040 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1041 blink_manager: Entity<BlinkManager>,
1042 show_cursor_names: bool,
1043 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1044 pub show_local_selections: bool,
1045 mode: EditorMode,
1046 show_breadcrumbs: bool,
1047 show_gutter: bool,
1048 show_scrollbars: ScrollbarAxes,
1049 minimap_visibility: MinimapVisibility,
1050 offset_content: bool,
1051 disable_expand_excerpt_buttons: bool,
1052 show_line_numbers: Option<bool>,
1053 use_relative_line_numbers: Option<bool>,
1054 show_git_diff_gutter: Option<bool>,
1055 show_code_actions: Option<bool>,
1056 show_runnables: Option<bool>,
1057 show_breakpoints: Option<bool>,
1058 show_wrap_guides: Option<bool>,
1059 show_indent_guides: Option<bool>,
1060 placeholder_text: Option<Arc<str>>,
1061 highlight_order: usize,
1062 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1063 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1064 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1065 scrollbar_marker_state: ScrollbarMarkerState,
1066 active_indent_guides_state: ActiveIndentGuidesState,
1067 nav_history: Option<ItemNavHistory>,
1068 context_menu: RefCell<Option<CodeContextMenu>>,
1069 context_menu_options: Option<ContextMenuOptions>,
1070 mouse_context_menu: Option<MouseContextMenu>,
1071 completion_tasks: Vec<(CompletionId, Task<()>)>,
1072 inline_blame_popover: Option<InlineBlamePopover>,
1073 inline_blame_popover_show_task: Option<Task<()>>,
1074 signature_help_state: SignatureHelpState,
1075 auto_signature_help: Option<bool>,
1076 find_all_references_task_sources: Vec<Anchor>,
1077 next_completion_id: CompletionId,
1078 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1079 code_actions_task: Option<Task<Result<()>>>,
1080 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1082 document_highlights_task: Option<Task<()>>,
1083 linked_editing_range_task: Option<Task<Option<()>>>,
1084 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1085 pending_rename: Option<RenameState>,
1086 searchable: bool,
1087 cursor_shape: CursorShape,
1088 current_line_highlight: Option<CurrentLineHighlight>,
1089 collapse_matches: bool,
1090 autoindent_mode: Option<AutoindentMode>,
1091 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1092 input_enabled: bool,
1093 use_modal_editing: bool,
1094 read_only: bool,
1095 leader_id: Option<CollaboratorId>,
1096 remote_id: Option<ViewId>,
1097 pub hover_state: HoverState,
1098 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1099 gutter_hovered: bool,
1100 hovered_link_state: Option<HoveredLinkState>,
1101 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1102 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1103 active_edit_prediction: Option<EditPredictionState>,
1104 /// Used to prevent flickering as the user types while the menu is open
1105 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1106 edit_prediction_settings: EditPredictionSettings,
1107 edit_predictions_hidden_for_vim_mode: bool,
1108 show_edit_predictions_override: Option<bool>,
1109 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1110 edit_prediction_preview: EditPredictionPreview,
1111 edit_prediction_indent_conflict: bool,
1112 edit_prediction_requires_modifier_in_indent_conflict: bool,
1113 inlay_hint_cache: InlayHintCache,
1114 next_inlay_id: usize,
1115 _subscriptions: Vec<Subscription>,
1116 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1117 gutter_dimensions: GutterDimensions,
1118 style: Option<EditorStyle>,
1119 text_style_refinement: Option<TextStyleRefinement>,
1120 next_editor_action_id: EditorActionId,
1121 editor_actions: Rc<
1122 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1123 >,
1124 use_autoclose: bool,
1125 use_auto_surround: bool,
1126 auto_replace_emoji_shortcode: bool,
1127 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1128 show_git_blame_gutter: bool,
1129 show_git_blame_inline: bool,
1130 show_git_blame_inline_delay_task: Option<Task<()>>,
1131 git_blame_inline_enabled: bool,
1132 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1133 serialize_dirty_buffers: bool,
1134 show_selection_menu: Option<bool>,
1135 blame: Option<Entity<GitBlame>>,
1136 blame_subscription: Option<Subscription>,
1137 custom_context_menu: Option<
1138 Box<
1139 dyn 'static
1140 + Fn(
1141 &mut Self,
1142 DisplayPoint,
1143 &mut Window,
1144 &mut Context<Self>,
1145 ) -> Option<Entity<ui::ContextMenu>>,
1146 >,
1147 >,
1148 last_bounds: Option<Bounds<Pixels>>,
1149 last_position_map: Option<Rc<PositionMap>>,
1150 expect_bounds_change: Option<Bounds<Pixels>>,
1151 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1152 tasks_update_task: Option<Task<()>>,
1153 breakpoint_store: Option<Entity<BreakpointStore>>,
1154 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1155 hovered_diff_hunk_row: Option<DisplayRow>,
1156 pull_diagnostics_task: Task<()>,
1157 in_project_search: bool,
1158 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1159 breadcrumb_header: Option<String>,
1160 focused_block: Option<FocusedBlock>,
1161 next_scroll_position: NextScrollCursorCenterTopBottom,
1162 addons: HashMap<TypeId, Box<dyn Addon>>,
1163 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1164 load_diff_task: Option<Shared<Task<()>>>,
1165 /// Whether we are temporarily displaying a diff other than git's
1166 temporary_diff_override: bool,
1167 selection_mark_mode: bool,
1168 toggle_fold_multiple_buffers: Task<()>,
1169 _scroll_cursor_center_top_bottom_task: Task<()>,
1170 serialize_selections: Task<()>,
1171 serialize_folds: Task<()>,
1172 mouse_cursor_hidden: bool,
1173 minimap: Option<Entity<Self>>,
1174 hide_mouse_mode: HideMouseMode,
1175 pub change_list: ChangeList,
1176 inline_value_cache: InlineValueCache,
1177 selection_drag_state: SelectionDragState,
1178 next_color_inlay_id: usize,
1179 colors: Option<LspColorData>,
1180 folding_newlines: Task<()>,
1181}
1182
1183#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1184enum NextScrollCursorCenterTopBottom {
1185 #[default]
1186 Center,
1187 Top,
1188 Bottom,
1189}
1190
1191impl NextScrollCursorCenterTopBottom {
1192 fn next(&self) -> Self {
1193 match self {
1194 Self::Center => Self::Top,
1195 Self::Top => Self::Bottom,
1196 Self::Bottom => Self::Center,
1197 }
1198 }
1199}
1200
1201#[derive(Clone)]
1202pub struct EditorSnapshot {
1203 pub mode: EditorMode,
1204 show_gutter: bool,
1205 show_line_numbers: Option<bool>,
1206 show_git_diff_gutter: Option<bool>,
1207 show_code_actions: Option<bool>,
1208 show_runnables: Option<bool>,
1209 show_breakpoints: Option<bool>,
1210 git_blame_gutter_max_author_length: Option<usize>,
1211 pub display_snapshot: DisplaySnapshot,
1212 pub placeholder_text: Option<Arc<str>>,
1213 is_focused: bool,
1214 scroll_anchor: ScrollAnchor,
1215 ongoing_scroll: OngoingScroll,
1216 current_line_highlight: CurrentLineHighlight,
1217 gutter_hovered: bool,
1218}
1219
1220#[derive(Default, Debug, Clone, Copy)]
1221pub struct GutterDimensions {
1222 pub left_padding: Pixels,
1223 pub right_padding: Pixels,
1224 pub width: Pixels,
1225 pub margin: Pixels,
1226 pub git_blame_entries_width: Option<Pixels>,
1227}
1228
1229impl GutterDimensions {
1230 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1231 Self {
1232 margin: Self::default_gutter_margin(font_id, font_size, cx),
1233 ..Default::default()
1234 }
1235 }
1236
1237 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1238 -cx.text_system().descent(font_id, font_size)
1239 }
1240 /// The full width of the space taken up by the gutter.
1241 pub fn full_width(&self) -> Pixels {
1242 self.margin + self.width
1243 }
1244
1245 /// The width of the space reserved for the fold indicators,
1246 /// use alongside 'justify_end' and `gutter_width` to
1247 /// right align content with the line numbers
1248 pub fn fold_area_width(&self) -> Pixels {
1249 self.margin + self.right_padding
1250 }
1251}
1252
1253struct CharacterDimensions {
1254 em_width: Pixels,
1255 em_advance: Pixels,
1256 line_height: Pixels,
1257}
1258
1259#[derive(Debug)]
1260pub struct RemoteSelection {
1261 pub replica_id: ReplicaId,
1262 pub selection: Selection<Anchor>,
1263 pub cursor_shape: CursorShape,
1264 pub collaborator_id: CollaboratorId,
1265 pub line_mode: bool,
1266 pub user_name: Option<SharedString>,
1267 pub color: PlayerColor,
1268}
1269
1270#[derive(Clone, Debug)]
1271struct SelectionHistoryEntry {
1272 selections: Arc<[Selection<Anchor>]>,
1273 select_next_state: Option<SelectNextState>,
1274 select_prev_state: Option<SelectNextState>,
1275 add_selections_state: Option<AddSelectionsState>,
1276}
1277
1278#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1279enum SelectionHistoryMode {
1280 Normal,
1281 Undoing,
1282 Redoing,
1283 Skipping,
1284}
1285
1286#[derive(Clone, PartialEq, Eq, Hash)]
1287struct HoveredCursor {
1288 replica_id: u16,
1289 selection_id: usize,
1290}
1291
1292impl Default for SelectionHistoryMode {
1293 fn default() -> Self {
1294 Self::Normal
1295 }
1296}
1297
1298#[derive(Debug)]
1299/// SelectionEffects controls the side-effects of updating the selection.
1300///
1301/// The default behaviour does "what you mostly want":
1302/// - it pushes to the nav history if the cursor moved by >10 lines
1303/// - it re-triggers completion requests
1304/// - it scrolls to fit
1305///
1306/// You might want to modify these behaviours. For example when doing a "jump"
1307/// like go to definition, we always want to add to nav history; but when scrolling
1308/// in vim mode we never do.
1309///
1310/// Similarly, you might want to disable scrolling if you don't want the viewport to
1311/// move.
1312#[derive(Clone)]
1313pub struct SelectionEffects {
1314 nav_history: Option<bool>,
1315 completions: bool,
1316 scroll: Option<Autoscroll>,
1317}
1318
1319impl Default for SelectionEffects {
1320 fn default() -> Self {
1321 Self {
1322 nav_history: None,
1323 completions: true,
1324 scroll: Some(Autoscroll::fit()),
1325 }
1326 }
1327}
1328impl SelectionEffects {
1329 pub fn scroll(scroll: Autoscroll) -> Self {
1330 Self {
1331 scroll: Some(scroll),
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn no_scroll() -> Self {
1337 Self {
1338 scroll: None,
1339 ..Default::default()
1340 }
1341 }
1342
1343 pub fn completions(self, completions: bool) -> Self {
1344 Self {
1345 completions,
1346 ..self
1347 }
1348 }
1349
1350 pub fn nav_history(self, nav_history: bool) -> Self {
1351 Self {
1352 nav_history: Some(nav_history),
1353 ..self
1354 }
1355 }
1356}
1357
1358struct DeferredSelectionEffectsState {
1359 changed: bool,
1360 effects: SelectionEffects,
1361 old_cursor_position: Anchor,
1362 history_entry: SelectionHistoryEntry,
1363}
1364
1365#[derive(Default)]
1366struct SelectionHistory {
1367 #[allow(clippy::type_complexity)]
1368 selections_by_transaction:
1369 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1370 mode: SelectionHistoryMode,
1371 undo_stack: VecDeque<SelectionHistoryEntry>,
1372 redo_stack: VecDeque<SelectionHistoryEntry>,
1373}
1374
1375impl SelectionHistory {
1376 #[track_caller]
1377 fn insert_transaction(
1378 &mut self,
1379 transaction_id: TransactionId,
1380 selections: Arc<[Selection<Anchor>]>,
1381 ) {
1382 if selections.is_empty() {
1383 log::error!(
1384 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1385 std::panic::Location::caller()
1386 );
1387 return;
1388 }
1389 self.selections_by_transaction
1390 .insert(transaction_id, (selections, None));
1391 }
1392
1393 #[allow(clippy::type_complexity)]
1394 fn transaction(
1395 &self,
1396 transaction_id: TransactionId,
1397 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1398 self.selections_by_transaction.get(&transaction_id)
1399 }
1400
1401 #[allow(clippy::type_complexity)]
1402 fn transaction_mut(
1403 &mut self,
1404 transaction_id: TransactionId,
1405 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1406 self.selections_by_transaction.get_mut(&transaction_id)
1407 }
1408
1409 fn push(&mut self, entry: SelectionHistoryEntry) {
1410 if !entry.selections.is_empty() {
1411 match self.mode {
1412 SelectionHistoryMode::Normal => {
1413 self.push_undo(entry);
1414 self.redo_stack.clear();
1415 }
1416 SelectionHistoryMode::Undoing => self.push_redo(entry),
1417 SelectionHistoryMode::Redoing => self.push_undo(entry),
1418 SelectionHistoryMode::Skipping => {}
1419 }
1420 }
1421 }
1422
1423 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1424 if self
1425 .undo_stack
1426 .back()
1427 .is_none_or(|e| e.selections != entry.selections)
1428 {
1429 self.undo_stack.push_back(entry);
1430 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1431 self.undo_stack.pop_front();
1432 }
1433 }
1434 }
1435
1436 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1437 if self
1438 .redo_stack
1439 .back()
1440 .is_none_or(|e| e.selections != entry.selections)
1441 {
1442 self.redo_stack.push_back(entry);
1443 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1444 self.redo_stack.pop_front();
1445 }
1446 }
1447 }
1448}
1449
1450#[derive(Clone, Copy)]
1451pub struct RowHighlightOptions {
1452 pub autoscroll: bool,
1453 pub include_gutter: bool,
1454}
1455
1456impl Default for RowHighlightOptions {
1457 fn default() -> Self {
1458 Self {
1459 autoscroll: Default::default(),
1460 include_gutter: true,
1461 }
1462 }
1463}
1464
1465struct RowHighlight {
1466 index: usize,
1467 range: Range<Anchor>,
1468 color: Hsla,
1469 options: RowHighlightOptions,
1470 type_id: TypeId,
1471}
1472
1473#[derive(Clone, Debug)]
1474struct AddSelectionsState {
1475 groups: Vec<AddSelectionsGroup>,
1476}
1477
1478#[derive(Clone, Debug)]
1479struct AddSelectionsGroup {
1480 above: bool,
1481 stack: Vec<usize>,
1482}
1483
1484#[derive(Clone)]
1485struct SelectNextState {
1486 query: AhoCorasick,
1487 wordwise: bool,
1488 done: bool,
1489}
1490
1491impl std::fmt::Debug for SelectNextState {
1492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1493 f.debug_struct(std::any::type_name::<Self>())
1494 .field("wordwise", &self.wordwise)
1495 .field("done", &self.done)
1496 .finish()
1497 }
1498}
1499
1500#[derive(Debug)]
1501struct AutocloseRegion {
1502 selection_id: usize,
1503 range: Range<Anchor>,
1504 pair: BracketPair,
1505}
1506
1507#[derive(Debug)]
1508struct SnippetState {
1509 ranges: Vec<Vec<Range<Anchor>>>,
1510 active_index: usize,
1511 choices: Vec<Option<Vec<String>>>,
1512}
1513
1514#[doc(hidden)]
1515pub struct RenameState {
1516 pub range: Range<Anchor>,
1517 pub old_name: Arc<str>,
1518 pub editor: Entity<Editor>,
1519 block_id: CustomBlockId,
1520}
1521
1522struct InvalidationStack<T>(Vec<T>);
1523
1524struct RegisteredEditPredictionProvider {
1525 provider: Arc<dyn EditPredictionProviderHandle>,
1526 _subscription: Subscription,
1527}
1528
1529#[derive(Debug, PartialEq, Eq)]
1530pub struct ActiveDiagnosticGroup {
1531 pub active_range: Range<Anchor>,
1532 pub active_message: String,
1533 pub group_id: usize,
1534 pub blocks: HashSet<CustomBlockId>,
1535}
1536
1537#[derive(Debug, PartialEq, Eq)]
1538
1539pub(crate) enum ActiveDiagnostic {
1540 None,
1541 All,
1542 Group(ActiveDiagnosticGroup),
1543}
1544
1545#[derive(Serialize, Deserialize, Clone, Debug)]
1546pub struct ClipboardSelection {
1547 /// The number of bytes in this selection.
1548 pub len: usize,
1549 /// Whether this was a full-line selection.
1550 pub is_entire_line: bool,
1551 /// The indentation of the first line when this content was originally copied.
1552 pub first_line_indent: u32,
1553}
1554
1555// selections, scroll behavior, was newest selection reversed
1556type SelectSyntaxNodeHistoryState = (
1557 Box<[Selection<usize>]>,
1558 SelectSyntaxNodeScrollBehavior,
1559 bool,
1560);
1561
1562#[derive(Default)]
1563struct SelectSyntaxNodeHistory {
1564 stack: Vec<SelectSyntaxNodeHistoryState>,
1565 // disable temporarily to allow changing selections without losing the stack
1566 pub disable_clearing: bool,
1567}
1568
1569impl SelectSyntaxNodeHistory {
1570 pub fn try_clear(&mut self) {
1571 if !self.disable_clearing {
1572 self.stack.clear();
1573 }
1574 }
1575
1576 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1577 self.stack.push(selection);
1578 }
1579
1580 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1581 self.stack.pop()
1582 }
1583}
1584
1585enum SelectSyntaxNodeScrollBehavior {
1586 CursorTop,
1587 FitSelection,
1588 CursorBottom,
1589}
1590
1591#[derive(Debug)]
1592pub(crate) struct NavigationData {
1593 cursor_anchor: Anchor,
1594 cursor_position: Point,
1595 scroll_anchor: ScrollAnchor,
1596 scroll_top_row: u32,
1597}
1598
1599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1600pub enum GotoDefinitionKind {
1601 Symbol,
1602 Declaration,
1603 Type,
1604 Implementation,
1605}
1606
1607#[derive(Debug, Clone)]
1608enum InlayHintRefreshReason {
1609 ModifiersChanged(bool),
1610 Toggle(bool),
1611 SettingsChange(InlayHintSettings),
1612 NewLinesShown,
1613 BufferEdited(HashSet<Arc<Language>>),
1614 RefreshRequested,
1615 ExcerptsRemoved(Vec<ExcerptId>),
1616}
1617
1618impl InlayHintRefreshReason {
1619 fn description(&self) -> &'static str {
1620 match self {
1621 Self::ModifiersChanged(_) => "modifiers changed",
1622 Self::Toggle(_) => "toggle",
1623 Self::SettingsChange(_) => "settings change",
1624 Self::NewLinesShown => "new lines shown",
1625 Self::BufferEdited(_) => "buffer edited",
1626 Self::RefreshRequested => "refresh requested",
1627 Self::ExcerptsRemoved(_) => "excerpts removed",
1628 }
1629 }
1630}
1631
1632pub enum FormatTarget {
1633 Buffers(HashSet<Entity<Buffer>>),
1634 Ranges(Vec<Range<MultiBufferPoint>>),
1635}
1636
1637pub(crate) struct FocusedBlock {
1638 id: BlockId,
1639 focus_handle: WeakFocusHandle,
1640}
1641
1642#[derive(Clone)]
1643enum JumpData {
1644 MultiBufferRow {
1645 row: MultiBufferRow,
1646 line_offset_from_top: u32,
1647 },
1648 MultiBufferPoint {
1649 excerpt_id: ExcerptId,
1650 position: Point,
1651 anchor: text::Anchor,
1652 line_offset_from_top: u32,
1653 },
1654}
1655
1656pub enum MultibufferSelectionMode {
1657 First,
1658 All,
1659}
1660
1661#[derive(Clone, Copy, Debug, Default)]
1662pub struct RewrapOptions {
1663 pub override_language_settings: bool,
1664 pub preserve_existing_whitespace: bool,
1665}
1666
1667impl Editor {
1668 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1669 let buffer = cx.new(|cx| Buffer::local("", cx));
1670 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1671 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1672 }
1673
1674 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(EditorMode::full(), buffer, None, window, cx)
1678 }
1679
1680 pub fn auto_height(
1681 min_lines: usize,
1682 max_lines: usize,
1683 window: &mut Window,
1684 cx: &mut Context<Self>,
1685 ) -> Self {
1686 let buffer = cx.new(|cx| Buffer::local("", cx));
1687 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1688 Self::new(
1689 EditorMode::AutoHeight {
1690 min_lines,
1691 max_lines: Some(max_lines),
1692 },
1693 buffer,
1694 None,
1695 window,
1696 cx,
1697 )
1698 }
1699
1700 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1701 /// The editor grows as tall as needed to fit its content.
1702 pub fn auto_height_unbounded(
1703 min_lines: usize,
1704 window: &mut Window,
1705 cx: &mut Context<Self>,
1706 ) -> Self {
1707 let buffer = cx.new(|cx| Buffer::local("", cx));
1708 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1709 Self::new(
1710 EditorMode::AutoHeight {
1711 min_lines,
1712 max_lines: None,
1713 },
1714 buffer,
1715 None,
1716 window,
1717 cx,
1718 )
1719 }
1720
1721 pub fn for_buffer(
1722 buffer: Entity<Buffer>,
1723 project: Option<Entity<Project>>,
1724 window: &mut Window,
1725 cx: &mut Context<Self>,
1726 ) -> Self {
1727 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1728 Self::new(EditorMode::full(), buffer, project, window, cx)
1729 }
1730
1731 pub fn for_multibuffer(
1732 buffer: Entity<MultiBuffer>,
1733 project: Option<Entity<Project>>,
1734 window: &mut Window,
1735 cx: &mut Context<Self>,
1736 ) -> Self {
1737 Self::new(EditorMode::full(), buffer, project, window, cx)
1738 }
1739
1740 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1741 let mut clone = Self::new(
1742 self.mode.clone(),
1743 self.buffer.clone(),
1744 self.project.clone(),
1745 window,
1746 cx,
1747 );
1748 self.display_map.update(cx, |display_map, cx| {
1749 let snapshot = display_map.snapshot(cx);
1750 clone.display_map.update(cx, |display_map, cx| {
1751 display_map.set_state(&snapshot, cx);
1752 });
1753 });
1754 clone.folds_did_change(cx);
1755 clone.selections.clone_state(&self.selections);
1756 clone.scroll_manager.clone_state(&self.scroll_manager);
1757 clone.searchable = self.searchable;
1758 clone.read_only = self.read_only;
1759 clone
1760 }
1761
1762 pub fn new(
1763 mode: EditorMode,
1764 buffer: Entity<MultiBuffer>,
1765 project: Option<Entity<Project>>,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 Editor::new_internal(mode, buffer, project, None, window, cx)
1770 }
1771
1772 fn new_internal(
1773 mode: EditorMode,
1774 buffer: Entity<MultiBuffer>,
1775 project: Option<Entity<Project>>,
1776 display_map: Option<Entity<DisplayMap>>,
1777 window: &mut Window,
1778 cx: &mut Context<Self>,
1779 ) -> Self {
1780 debug_assert!(
1781 display_map.is_none() || mode.is_minimap(),
1782 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1783 );
1784
1785 let full_mode = mode.is_full();
1786 let is_minimap = mode.is_minimap();
1787 let diagnostics_max_severity = if full_mode {
1788 EditorSettings::get_global(cx)
1789 .diagnostics_max_severity
1790 .unwrap_or(DiagnosticSeverity::Hint)
1791 } else {
1792 DiagnosticSeverity::Off
1793 };
1794 let style = window.text_style();
1795 let font_size = style.font_size.to_pixels(window.rem_size());
1796 let editor = cx.entity().downgrade();
1797 let fold_placeholder = FoldPlaceholder {
1798 constrain_width: false,
1799 render: Arc::new(move |fold_id, fold_range, cx| {
1800 let editor = editor.clone();
1801 div()
1802 .id(fold_id)
1803 .bg(cx.theme().colors().ghost_element_background)
1804 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1805 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1806 .rounded_xs()
1807 .size_full()
1808 .cursor_pointer()
1809 .child("⋯")
1810 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1811 .on_click(move |_, _window, cx| {
1812 editor
1813 .update(cx, |editor, cx| {
1814 editor.unfold_ranges(
1815 &[fold_range.start..fold_range.end],
1816 true,
1817 false,
1818 cx,
1819 );
1820 cx.stop_propagation();
1821 })
1822 .ok();
1823 })
1824 .into_any()
1825 }),
1826 merge_adjacent: true,
1827 ..FoldPlaceholder::default()
1828 };
1829 let display_map = display_map.unwrap_or_else(|| {
1830 cx.new(|cx| {
1831 DisplayMap::new(
1832 buffer.clone(),
1833 style.font(),
1834 font_size,
1835 None,
1836 FILE_HEADER_HEIGHT,
1837 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1838 fold_placeholder,
1839 diagnostics_max_severity,
1840 cx,
1841 )
1842 })
1843 });
1844
1845 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1846
1847 let blink_manager = cx.new(|cx| {
1848 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1849 if is_minimap {
1850 blink_manager.disable(cx);
1851 }
1852 blink_manager
1853 });
1854
1855 let soft_wrap_mode_override =
1856 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1857
1858 let mut project_subscriptions = Vec::new();
1859 if full_mode && let Some(project) = project.as_ref() {
1860 project_subscriptions.push(cx.subscribe_in(
1861 project,
1862 window,
1863 |editor, _, event, window, cx| match event {
1864 project::Event::RefreshCodeLens => {
1865 // we always query lens with actions, without storing them, always refreshing them
1866 }
1867 project::Event::RefreshInlayHints => {
1868 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1869 }
1870 project::Event::LanguageServerAdded(..)
1871 | project::Event::LanguageServerRemoved(..) => {
1872 if editor.tasks_update_task.is_none() {
1873 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1874 }
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1897 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1898 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1899 }
1900 }
1901
1902 project::Event::EntryRenamed(transaction) => {
1903 let Some(workspace) = editor.workspace() else {
1904 return;
1905 };
1906 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1907 else {
1908 return;
1909 };
1910 if active_editor.entity_id() == cx.entity_id() {
1911 let edited_buffers_already_open = {
1912 let other_editors: Vec<Entity<Editor>> = workspace
1913 .read(cx)
1914 .panes()
1915 .iter()
1916 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1917 .filter(|editor| editor.entity_id() != cx.entity_id())
1918 .collect();
1919
1920 transaction.0.keys().all(|buffer| {
1921 other_editors.iter().any(|editor| {
1922 let multi_buffer = editor.read(cx).buffer();
1923 multi_buffer.read(cx).is_singleton()
1924 && multi_buffer.read(cx).as_singleton().map_or(
1925 false,
1926 |singleton| {
1927 singleton.entity_id() == buffer.entity_id()
1928 },
1929 )
1930 })
1931 })
1932 };
1933
1934 if !edited_buffers_already_open {
1935 let workspace = workspace.downgrade();
1936 let transaction = transaction.clone();
1937 cx.defer_in(window, move |_, window, cx| {
1938 cx.spawn_in(window, async move |editor, cx| {
1939 Self::open_project_transaction(
1940 &editor,
1941 workspace,
1942 transaction,
1943 "Rename".to_string(),
1944 cx,
1945 )
1946 .await
1947 .ok()
1948 })
1949 .detach();
1950 });
1951 }
1952 }
1953 }
1954
1955 _ => {}
1956 },
1957 ));
1958 if let Some(task_inventory) = project
1959 .read(cx)
1960 .task_store()
1961 .read(cx)
1962 .task_inventory()
1963 .cloned()
1964 {
1965 project_subscriptions.push(cx.observe_in(
1966 &task_inventory,
1967 window,
1968 |editor, _, window, cx| {
1969 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1970 },
1971 ));
1972 };
1973
1974 project_subscriptions.push(cx.subscribe_in(
1975 &project.read(cx).breakpoint_store(),
1976 window,
1977 |editor, _, event, window, cx| match event {
1978 BreakpointStoreEvent::ClearDebugLines => {
1979 editor.clear_row_highlights::<ActiveDebugLine>();
1980 editor.refresh_inline_values(cx);
1981 }
1982 BreakpointStoreEvent::SetDebugLine => {
1983 if editor.go_to_active_debug_line(window, cx) {
1984 cx.stop_propagation();
1985 }
1986
1987 editor.refresh_inline_values(cx);
1988 }
1989 _ => {}
1990 },
1991 ));
1992 let git_store = project.read(cx).git_store().clone();
1993 let project = project.clone();
1994 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1995 if let GitStoreEvent::RepositoryUpdated(
1996 _,
1997 RepositoryEvent::Updated {
1998 new_instance: true, ..
1999 },
2000 _,
2001 ) = event
2002 {
2003 this.load_diff_task = Some(
2004 update_uncommitted_diff_for_buffer(
2005 cx.entity(),
2006 &project,
2007 this.buffer.read(cx).all_buffers(),
2008 this.buffer.clone(),
2009 cx,
2010 )
2011 .shared(),
2012 );
2013 }
2014 }));
2015 }
2016
2017 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2018
2019 let inlay_hint_settings =
2020 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2021 let focus_handle = cx.focus_handle();
2022 if !is_minimap {
2023 cx.on_focus(&focus_handle, window, Self::handle_focus)
2024 .detach();
2025 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2026 .detach();
2027 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2028 .detach();
2029 cx.on_blur(&focus_handle, window, Self::handle_blur)
2030 .detach();
2031 cx.observe_pending_input(window, Self::observe_pending_input)
2032 .detach();
2033 }
2034
2035 let show_indent_guides =
2036 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2037 Some(false)
2038 } else {
2039 None
2040 };
2041
2042 let breakpoint_store = match (&mode, project.as_ref()) {
2043 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2044 _ => None,
2045 };
2046
2047 let mut code_action_providers = Vec::new();
2048 let mut load_uncommitted_diff = None;
2049 if let Some(project) = project.clone() {
2050 load_uncommitted_diff = Some(
2051 update_uncommitted_diff_for_buffer(
2052 cx.entity(),
2053 &project,
2054 buffer.read(cx).all_buffers(),
2055 buffer.clone(),
2056 cx,
2057 )
2058 .shared(),
2059 );
2060 code_action_providers.push(Rc::new(project) as Rc<_>);
2061 }
2062
2063 let mut editor = Self {
2064 focus_handle,
2065 show_cursor_when_unfocused: false,
2066 last_focused_descendant: None,
2067 buffer: buffer.clone(),
2068 display_map: display_map.clone(),
2069 selections,
2070 scroll_manager: ScrollManager::new(cx),
2071 columnar_selection_state: None,
2072 add_selections_state: None,
2073 select_next_state: None,
2074 select_prev_state: None,
2075 selection_history: SelectionHistory::default(),
2076 defer_selection_effects: false,
2077 deferred_selection_effects_state: None,
2078 autoclose_regions: Vec::new(),
2079 snippet_stack: InvalidationStack::default(),
2080 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2081 ime_transaction: None,
2082 active_diagnostics: ActiveDiagnostic::None,
2083 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2084 inline_diagnostics_update: Task::ready(()),
2085 inline_diagnostics: Vec::new(),
2086 soft_wrap_mode_override,
2087 diagnostics_max_severity,
2088 hard_wrap: None,
2089 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2090 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2091 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2092 project,
2093 blink_manager: blink_manager.clone(),
2094 show_local_selections: true,
2095 show_scrollbars: ScrollbarAxes {
2096 horizontal: full_mode,
2097 vertical: full_mode,
2098 },
2099 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2100 offset_content: !matches!(mode, EditorMode::SingleLine),
2101 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2102 show_gutter: full_mode,
2103 show_line_numbers: (!full_mode).then_some(false),
2104 use_relative_line_numbers: None,
2105 disable_expand_excerpt_buttons: !full_mode,
2106 show_git_diff_gutter: None,
2107 show_code_actions: None,
2108 show_runnables: None,
2109 show_breakpoints: None,
2110 show_wrap_guides: None,
2111 show_indent_guides,
2112 placeholder_text: None,
2113 highlight_order: 0,
2114 highlighted_rows: HashMap::default(),
2115 background_highlights: HashMap::default(),
2116 gutter_highlights: HashMap::default(),
2117 scrollbar_marker_state: ScrollbarMarkerState::default(),
2118 active_indent_guides_state: ActiveIndentGuidesState::default(),
2119 nav_history: None,
2120 context_menu: RefCell::new(None),
2121 context_menu_options: None,
2122 mouse_context_menu: None,
2123 completion_tasks: Vec::new(),
2124 inline_blame_popover: None,
2125 inline_blame_popover_show_task: None,
2126 signature_help_state: SignatureHelpState::default(),
2127 auto_signature_help: None,
2128 find_all_references_task_sources: Vec::new(),
2129 next_completion_id: 0,
2130 next_inlay_id: 0,
2131 code_action_providers,
2132 available_code_actions: None,
2133 code_actions_task: None,
2134 quick_selection_highlight_task: None,
2135 debounced_selection_highlight_task: None,
2136 document_highlights_task: None,
2137 linked_editing_range_task: None,
2138 pending_rename: None,
2139 searchable: !is_minimap,
2140 cursor_shape: EditorSettings::get_global(cx)
2141 .cursor_shape
2142 .unwrap_or_default(),
2143 current_line_highlight: None,
2144 autoindent_mode: Some(AutoindentMode::EachLine),
2145 collapse_matches: false,
2146 workspace: None,
2147 input_enabled: !is_minimap,
2148 use_modal_editing: full_mode,
2149 read_only: is_minimap,
2150 use_autoclose: true,
2151 use_auto_surround: true,
2152 auto_replace_emoji_shortcode: false,
2153 jsx_tag_auto_close_enabled_in_any_buffer: false,
2154 leader_id: None,
2155 remote_id: None,
2156 hover_state: HoverState::default(),
2157 pending_mouse_down: None,
2158 hovered_link_state: None,
2159 edit_prediction_provider: None,
2160 active_edit_prediction: None,
2161 stale_edit_prediction_in_menu: None,
2162 edit_prediction_preview: EditPredictionPreview::Inactive {
2163 released_too_fast: false,
2164 },
2165 inline_diagnostics_enabled: full_mode,
2166 diagnostics_enabled: full_mode,
2167 word_completions_enabled: full_mode,
2168 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2169 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2170 gutter_hovered: false,
2171 pixel_position_of_newest_cursor: None,
2172 last_bounds: None,
2173 last_position_map: None,
2174 expect_bounds_change: None,
2175 gutter_dimensions: GutterDimensions::default(),
2176 style: None,
2177 show_cursor_names: false,
2178 hovered_cursors: HashMap::default(),
2179 next_editor_action_id: EditorActionId::default(),
2180 editor_actions: Rc::default(),
2181 edit_predictions_hidden_for_vim_mode: false,
2182 show_edit_predictions_override: None,
2183 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2184 edit_prediction_settings: EditPredictionSettings::Disabled,
2185 edit_prediction_indent_conflict: false,
2186 edit_prediction_requires_modifier_in_indent_conflict: true,
2187 custom_context_menu: None,
2188 show_git_blame_gutter: false,
2189 show_git_blame_inline: false,
2190 show_selection_menu: None,
2191 show_git_blame_inline_delay_task: None,
2192 git_blame_inline_enabled: full_mode
2193 && ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2194 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2195 serialize_dirty_buffers: !is_minimap
2196 && ProjectSettings::get_global(cx)
2197 .session
2198 .restore_unsaved_buffers,
2199 blame: None,
2200 blame_subscription: None,
2201 tasks: BTreeMap::default(),
2202
2203 breakpoint_store,
2204 gutter_breakpoint_indicator: (None, None),
2205 hovered_diff_hunk_row: None,
2206 _subscriptions: (!is_minimap)
2207 .then(|| {
2208 vec![
2209 cx.observe(&buffer, Self::on_buffer_changed),
2210 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2211 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2212 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2213 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2214 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2215 cx.observe_window_activation(window, |editor, window, cx| {
2216 let active = window.is_window_active();
2217 editor.blink_manager.update(cx, |blink_manager, cx| {
2218 if active {
2219 blink_manager.enable(cx);
2220 } else {
2221 blink_manager.disable(cx);
2222 }
2223 });
2224 if active {
2225 editor.show_mouse_cursor(cx);
2226 }
2227 }),
2228 ]
2229 })
2230 .unwrap_or_default(),
2231 tasks_update_task: None,
2232 pull_diagnostics_task: Task::ready(()),
2233 colors: None,
2234 next_color_inlay_id: 0,
2235 linked_edit_ranges: Default::default(),
2236 in_project_search: false,
2237 previous_search_ranges: None,
2238 breadcrumb_header: None,
2239 focused_block: None,
2240 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2241 addons: HashMap::default(),
2242 registered_buffers: HashMap::default(),
2243 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2244 selection_mark_mode: false,
2245 toggle_fold_multiple_buffers: Task::ready(()),
2246 serialize_selections: Task::ready(()),
2247 serialize_folds: Task::ready(()),
2248 text_style_refinement: None,
2249 load_diff_task: load_uncommitted_diff,
2250 temporary_diff_override: false,
2251 mouse_cursor_hidden: false,
2252 minimap: None,
2253 hide_mouse_mode: EditorSettings::get_global(cx)
2254 .hide_mouse
2255 .unwrap_or_default(),
2256 change_list: ChangeList::new(),
2257 mode,
2258 selection_drag_state: SelectionDragState::None,
2259 folding_newlines: Task::ready(()),
2260 };
2261
2262 if is_minimap {
2263 return editor;
2264 }
2265
2266 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2267 editor
2268 ._subscriptions
2269 .push(cx.observe(breakpoints, |_, _, cx| {
2270 cx.notify();
2271 }));
2272 }
2273 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2274 editor._subscriptions.extend(project_subscriptions);
2275
2276 editor._subscriptions.push(cx.subscribe_in(
2277 &cx.entity(),
2278 window,
2279 |editor, _, e: &EditorEvent, window, cx| match e {
2280 EditorEvent::ScrollPositionChanged { local, .. } => {
2281 if *local {
2282 let new_anchor = editor.scroll_manager.anchor();
2283 let snapshot = editor.snapshot(window, cx);
2284 editor.update_restoration_data(cx, move |data| {
2285 data.scroll_position = (
2286 new_anchor.top_row(&snapshot.buffer_snapshot),
2287 new_anchor.offset,
2288 );
2289 });
2290 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2291 editor.inline_blame_popover.take();
2292 }
2293 }
2294 EditorEvent::Edited { .. } => {
2295 if !vim_enabled(cx) {
2296 let (map, selections) = editor.selections.all_adjusted_display(cx);
2297 let pop_state = editor
2298 .change_list
2299 .last()
2300 .map(|previous| {
2301 previous.len() == selections.len()
2302 && previous.iter().enumerate().all(|(ix, p)| {
2303 p.to_display_point(&map).row()
2304 == selections[ix].head().row()
2305 })
2306 })
2307 .unwrap_or(false);
2308 let new_positions = selections
2309 .into_iter()
2310 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2311 .collect();
2312 editor
2313 .change_list
2314 .push_to_change_list(pop_state, new_positions);
2315 }
2316 }
2317 _ => (),
2318 },
2319 ));
2320
2321 if let Some(dap_store) = editor
2322 .project
2323 .as_ref()
2324 .map(|project| project.read(cx).dap_store())
2325 {
2326 let weak_editor = cx.weak_entity();
2327
2328 editor
2329 ._subscriptions
2330 .push(
2331 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2332 let session_entity = cx.entity();
2333 weak_editor
2334 .update(cx, |editor, cx| {
2335 editor._subscriptions.push(
2336 cx.subscribe(&session_entity, Self::on_debug_session_event),
2337 );
2338 })
2339 .ok();
2340 }),
2341 );
2342
2343 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2344 editor
2345 ._subscriptions
2346 .push(cx.subscribe(&session, Self::on_debug_session_event));
2347 }
2348 }
2349
2350 // skip adding the initial selection to selection history
2351 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2352 editor.end_selection(window, cx);
2353 editor.selection_history.mode = SelectionHistoryMode::Normal;
2354
2355 editor.scroll_manager.show_scrollbars(window, cx);
2356 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2357
2358 if full_mode {
2359 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2360 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2361
2362 if editor.git_blame_inline_enabled {
2363 editor.start_git_blame_inline(false, window, cx);
2364 }
2365
2366 editor.go_to_active_debug_line(window, cx);
2367
2368 if let Some(buffer) = buffer.read(cx).as_singleton()
2369 && let Some(project) = editor.project()
2370 {
2371 let handle = project.update(cx, |project, cx| {
2372 project.register_buffer_with_language_servers(&buffer, cx)
2373 });
2374 editor
2375 .registered_buffers
2376 .insert(buffer.read(cx).remote_id(), handle);
2377 }
2378
2379 editor.minimap =
2380 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2381 editor.colors = Some(LspColorData::new(cx));
2382 editor.update_lsp_data(false, None, window, cx);
2383 }
2384
2385 if editor.mode.is_full() {
2386 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2387 }
2388
2389 editor
2390 }
2391
2392 pub fn deploy_mouse_context_menu(
2393 &mut self,
2394 position: gpui::Point<Pixels>,
2395 context_menu: Entity<ContextMenu>,
2396 window: &mut Window,
2397 cx: &mut Context<Self>,
2398 ) {
2399 self.mouse_context_menu = Some(MouseContextMenu::new(
2400 self,
2401 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2402 context_menu,
2403 window,
2404 cx,
2405 ));
2406 }
2407
2408 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2409 self.mouse_context_menu
2410 .as_ref()
2411 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2412 }
2413
2414 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2415 if self
2416 .selections
2417 .pending
2418 .as_ref()
2419 .is_some_and(|pending_selection| {
2420 let snapshot = self.buffer().read(cx).snapshot(cx);
2421 pending_selection
2422 .selection
2423 .range()
2424 .includes(range, &snapshot)
2425 })
2426 {
2427 return true;
2428 }
2429
2430 self.selections
2431 .disjoint_in_range::<usize>(range.clone(), cx)
2432 .into_iter()
2433 .any(|selection| {
2434 // This is needed to cover a corner case, if we just check for an existing
2435 // selection in the fold range, having a cursor at the start of the fold
2436 // marks it as selected. Non-empty selections don't cause this.
2437 let length = selection.end - selection.start;
2438 length > 0
2439 })
2440 }
2441
2442 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2443 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2444 }
2445
2446 fn key_context_internal(
2447 &self,
2448 has_active_edit_prediction: bool,
2449 window: &Window,
2450 cx: &App,
2451 ) -> KeyContext {
2452 let mut key_context = KeyContext::new_with_defaults();
2453 key_context.add("Editor");
2454 let mode = match self.mode {
2455 EditorMode::SingleLine => "single_line",
2456 EditorMode::AutoHeight { .. } => "auto_height",
2457 EditorMode::Minimap { .. } => "minimap",
2458 EditorMode::Full { .. } => "full",
2459 };
2460
2461 if EditorSettings::jupyter_enabled(cx) {
2462 key_context.add("jupyter");
2463 }
2464
2465 key_context.set("mode", mode);
2466 if self.pending_rename.is_some() {
2467 key_context.add("renaming");
2468 }
2469
2470 match self.context_menu.borrow().as_ref() {
2471 Some(CodeContextMenu::Completions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_completions");
2475 }
2476 }
2477 Some(CodeContextMenu::CodeActions(menu)) => {
2478 if menu.visible() {
2479 key_context.add("menu");
2480 key_context.add("showing_code_actions")
2481 }
2482 }
2483 None => {}
2484 }
2485
2486 if self.signature_help_state.has_multiple_signatures() {
2487 key_context.add("showing_signature_help");
2488 }
2489
2490 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2491 if !self.focus_handle(cx).contains_focused(window, cx)
2492 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2493 {
2494 for addon in self.addons.values() {
2495 addon.extend_key_context(&mut key_context, cx)
2496 }
2497 }
2498
2499 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2500 if let Some(extension) = singleton_buffer
2501 .read(cx)
2502 .file()
2503 .and_then(|file| file.path().extension()?.to_str())
2504 {
2505 key_context.set("extension", extension.to_string());
2506 }
2507 } else {
2508 key_context.add("multibuffer");
2509 }
2510
2511 if has_active_edit_prediction {
2512 if self.edit_prediction_in_conflict() {
2513 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2514 } else {
2515 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2516 key_context.add("copilot_suggestion");
2517 }
2518 }
2519
2520 if self.selection_mark_mode {
2521 key_context.add("selection_mode");
2522 }
2523
2524 key_context
2525 }
2526
2527 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2528 if self.mouse_cursor_hidden {
2529 self.mouse_cursor_hidden = false;
2530 cx.notify();
2531 }
2532 }
2533
2534 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2535 let hide_mouse_cursor = match origin {
2536 HideMouseCursorOrigin::TypingAction => {
2537 matches!(
2538 self.hide_mouse_mode,
2539 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2540 )
2541 }
2542 HideMouseCursorOrigin::MovementAction => {
2543 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2544 }
2545 };
2546 if self.mouse_cursor_hidden != hide_mouse_cursor {
2547 self.mouse_cursor_hidden = hide_mouse_cursor;
2548 cx.notify();
2549 }
2550 }
2551
2552 pub fn edit_prediction_in_conflict(&self) -> bool {
2553 if !self.show_edit_predictions_in_menu() {
2554 return false;
2555 }
2556
2557 let showing_completions = self
2558 .context_menu
2559 .borrow()
2560 .as_ref()
2561 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2562
2563 showing_completions
2564 || self.edit_prediction_requires_modifier()
2565 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2566 // bindings to insert tab characters.
2567 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2568 }
2569
2570 pub fn accept_edit_prediction_keybind(
2571 &self,
2572 accept_partial: bool,
2573 window: &Window,
2574 cx: &App,
2575 ) -> AcceptEditPredictionBinding {
2576 let key_context = self.key_context_internal(true, window, cx);
2577 let in_conflict = self.edit_prediction_in_conflict();
2578
2579 let bindings = if accept_partial {
2580 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2581 } else {
2582 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2583 };
2584
2585 // TODO: if the binding contains multiple keystrokes, display all of them, not
2586 // just the first one.
2587 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2588 !in_conflict
2589 || binding
2590 .keystrokes()
2591 .first()
2592 .is_some_and(|keystroke| keystroke.modifiers().modified())
2593 }))
2594 }
2595
2596 pub fn new_file(
2597 workspace: &mut Workspace,
2598 _: &workspace::NewFile,
2599 window: &mut Window,
2600 cx: &mut Context<Workspace>,
2601 ) {
2602 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2603 "Failed to create buffer",
2604 window,
2605 cx,
2606 |e, _, _| match e.error_code() {
2607 ErrorCode::RemoteUpgradeRequired => Some(format!(
2608 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2609 e.error_tag("required").unwrap_or("the latest version")
2610 )),
2611 _ => None,
2612 },
2613 );
2614 }
2615
2616 pub fn new_in_workspace(
2617 workspace: &mut Workspace,
2618 window: &mut Window,
2619 cx: &mut Context<Workspace>,
2620 ) -> Task<Result<Entity<Editor>>> {
2621 let project = workspace.project().clone();
2622 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2623
2624 cx.spawn_in(window, async move |workspace, cx| {
2625 let buffer = create.await?;
2626 workspace.update_in(cx, |workspace, window, cx| {
2627 let editor =
2628 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2629 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2630 editor
2631 })
2632 })
2633 }
2634
2635 fn new_file_vertical(
2636 workspace: &mut Workspace,
2637 _: &workspace::NewFileSplitVertical,
2638 window: &mut Window,
2639 cx: &mut Context<Workspace>,
2640 ) {
2641 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2642 }
2643
2644 fn new_file_horizontal(
2645 workspace: &mut Workspace,
2646 _: &workspace::NewFileSplitHorizontal,
2647 window: &mut Window,
2648 cx: &mut Context<Workspace>,
2649 ) {
2650 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2651 }
2652
2653 fn new_file_in_direction(
2654 workspace: &mut Workspace,
2655 direction: SplitDirection,
2656 window: &mut Window,
2657 cx: &mut Context<Workspace>,
2658 ) {
2659 let project = workspace.project().clone();
2660 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2661
2662 cx.spawn_in(window, async move |workspace, cx| {
2663 let buffer = create.await?;
2664 workspace.update_in(cx, move |workspace, window, cx| {
2665 workspace.split_item(
2666 direction,
2667 Box::new(
2668 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2669 ),
2670 window,
2671 cx,
2672 )
2673 })?;
2674 anyhow::Ok(())
2675 })
2676 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2677 match e.error_code() {
2678 ErrorCode::RemoteUpgradeRequired => Some(format!(
2679 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2680 e.error_tag("required").unwrap_or("the latest version")
2681 )),
2682 _ => None,
2683 }
2684 });
2685 }
2686
2687 pub fn leader_id(&self) -> Option<CollaboratorId> {
2688 self.leader_id
2689 }
2690
2691 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2692 &self.buffer
2693 }
2694
2695 pub fn project(&self) -> Option<&Entity<Project>> {
2696 self.project.as_ref()
2697 }
2698
2699 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2700 self.workspace.as_ref()?.0.upgrade()
2701 }
2702
2703 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2704 self.buffer().read(cx).title(cx)
2705 }
2706
2707 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2708 let git_blame_gutter_max_author_length = self
2709 .render_git_blame_gutter(cx)
2710 .then(|| {
2711 if let Some(blame) = self.blame.as_ref() {
2712 let max_author_length =
2713 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2714 Some(max_author_length)
2715 } else {
2716 None
2717 }
2718 })
2719 .flatten();
2720
2721 EditorSnapshot {
2722 mode: self.mode.clone(),
2723 show_gutter: self.show_gutter,
2724 show_line_numbers: self.show_line_numbers,
2725 show_git_diff_gutter: self.show_git_diff_gutter,
2726 show_code_actions: self.show_code_actions,
2727 show_runnables: self.show_runnables,
2728 show_breakpoints: self.show_breakpoints,
2729 git_blame_gutter_max_author_length,
2730 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2731 scroll_anchor: self.scroll_manager.anchor(),
2732 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2733 placeholder_text: self.placeholder_text.clone(),
2734 is_focused: self.focus_handle.is_focused(window),
2735 current_line_highlight: self
2736 .current_line_highlight
2737 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2738 gutter_hovered: self.gutter_hovered,
2739 }
2740 }
2741
2742 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2743 self.buffer.read(cx).language_at(point, cx)
2744 }
2745
2746 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2747 self.buffer.read(cx).read(cx).file_at(point).cloned()
2748 }
2749
2750 pub fn active_excerpt(
2751 &self,
2752 cx: &App,
2753 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2754 self.buffer
2755 .read(cx)
2756 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2757 }
2758
2759 pub fn mode(&self) -> &EditorMode {
2760 &self.mode
2761 }
2762
2763 pub fn set_mode(&mut self, mode: EditorMode) {
2764 self.mode = mode;
2765 }
2766
2767 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2768 self.collaboration_hub.as_deref()
2769 }
2770
2771 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2772 self.collaboration_hub = Some(hub);
2773 }
2774
2775 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2776 self.in_project_search = in_project_search;
2777 }
2778
2779 pub fn set_custom_context_menu(
2780 &mut self,
2781 f: impl 'static
2782 + Fn(
2783 &mut Self,
2784 DisplayPoint,
2785 &mut Window,
2786 &mut Context<Self>,
2787 ) -> Option<Entity<ui::ContextMenu>>,
2788 ) {
2789 self.custom_context_menu = Some(Box::new(f))
2790 }
2791
2792 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2793 self.completion_provider = provider;
2794 }
2795
2796 #[cfg(any(test, feature = "test-support"))]
2797 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2798 self.completion_provider.clone()
2799 }
2800
2801 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2802 self.semantics_provider.clone()
2803 }
2804
2805 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2806 self.semantics_provider = provider;
2807 }
2808
2809 pub fn set_edit_prediction_provider<T>(
2810 &mut self,
2811 provider: Option<Entity<T>>,
2812 window: &mut Window,
2813 cx: &mut Context<Self>,
2814 ) where
2815 T: EditPredictionProvider,
2816 {
2817 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2818 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2819 if this.focus_handle.is_focused(window) {
2820 this.update_visible_edit_prediction(window, cx);
2821 }
2822 }),
2823 provider: Arc::new(provider),
2824 });
2825 self.update_edit_prediction_settings(cx);
2826 self.refresh_edit_prediction(false, false, window, cx);
2827 }
2828
2829 pub fn placeholder_text(&self) -> Option<&str> {
2830 self.placeholder_text.as_deref()
2831 }
2832
2833 pub fn set_placeholder_text(
2834 &mut self,
2835 placeholder_text: impl Into<Arc<str>>,
2836 cx: &mut Context<Self>,
2837 ) {
2838 let placeholder_text = Some(placeholder_text.into());
2839 if self.placeholder_text != placeholder_text {
2840 self.placeholder_text = placeholder_text;
2841 cx.notify();
2842 }
2843 }
2844
2845 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2846 self.cursor_shape = cursor_shape;
2847
2848 // Disrupt blink for immediate user feedback that the cursor shape has changed
2849 self.blink_manager.update(cx, BlinkManager::show_cursor);
2850
2851 cx.notify();
2852 }
2853
2854 pub fn set_current_line_highlight(
2855 &mut self,
2856 current_line_highlight: Option<CurrentLineHighlight>,
2857 ) {
2858 self.current_line_highlight = current_line_highlight;
2859 }
2860
2861 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2862 self.collapse_matches = collapse_matches;
2863 }
2864
2865 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2866 let buffers = self.buffer.read(cx).all_buffers();
2867 let Some(project) = self.project.as_ref() else {
2868 return;
2869 };
2870 project.update(cx, |project, cx| {
2871 for buffer in buffers {
2872 self.registered_buffers
2873 .entry(buffer.read(cx).remote_id())
2874 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2875 }
2876 })
2877 }
2878
2879 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2880 if self.collapse_matches {
2881 return range.start..range.start;
2882 }
2883 range.clone()
2884 }
2885
2886 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2887 if self.display_map.read(cx).clip_at_line_ends != clip {
2888 self.display_map
2889 .update(cx, |map, _| map.clip_at_line_ends = clip);
2890 }
2891 }
2892
2893 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2894 self.input_enabled = input_enabled;
2895 }
2896
2897 pub fn set_edit_predictions_hidden_for_vim_mode(
2898 &mut self,
2899 hidden: bool,
2900 window: &mut Window,
2901 cx: &mut Context<Self>,
2902 ) {
2903 if hidden != self.edit_predictions_hidden_for_vim_mode {
2904 self.edit_predictions_hidden_for_vim_mode = hidden;
2905 if hidden {
2906 self.update_visible_edit_prediction(window, cx);
2907 } else {
2908 self.refresh_edit_prediction(true, false, window, cx);
2909 }
2910 }
2911 }
2912
2913 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2914 self.menu_edit_predictions_policy = value;
2915 }
2916
2917 pub fn set_autoindent(&mut self, autoindent: bool) {
2918 if autoindent {
2919 self.autoindent_mode = Some(AutoindentMode::EachLine);
2920 } else {
2921 self.autoindent_mode = None;
2922 }
2923 }
2924
2925 pub fn read_only(&self, cx: &App) -> bool {
2926 self.read_only || self.buffer.read(cx).read_only()
2927 }
2928
2929 pub fn set_read_only(&mut self, read_only: bool) {
2930 self.read_only = read_only;
2931 }
2932
2933 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2934 self.use_autoclose = autoclose;
2935 }
2936
2937 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2938 self.use_auto_surround = auto_surround;
2939 }
2940
2941 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2942 self.auto_replace_emoji_shortcode = auto_replace;
2943 }
2944
2945 pub fn toggle_edit_predictions(
2946 &mut self,
2947 _: &ToggleEditPrediction,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) {
2951 if self.show_edit_predictions_override.is_some() {
2952 self.set_show_edit_predictions(None, window, cx);
2953 } else {
2954 let show_edit_predictions = !self.edit_predictions_enabled();
2955 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2956 }
2957 }
2958
2959 pub fn set_show_edit_predictions(
2960 &mut self,
2961 show_edit_predictions: Option<bool>,
2962 window: &mut Window,
2963 cx: &mut Context<Self>,
2964 ) {
2965 self.show_edit_predictions_override = show_edit_predictions;
2966 self.update_edit_prediction_settings(cx);
2967
2968 if let Some(false) = show_edit_predictions {
2969 self.discard_edit_prediction(false, cx);
2970 } else {
2971 self.refresh_edit_prediction(false, true, window, cx);
2972 }
2973 }
2974
2975 fn edit_predictions_disabled_in_scope(
2976 &self,
2977 buffer: &Entity<Buffer>,
2978 buffer_position: language::Anchor,
2979 cx: &App,
2980 ) -> bool {
2981 let snapshot = buffer.read(cx).snapshot();
2982 let settings = snapshot.settings_at(buffer_position, cx);
2983
2984 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2985 return false;
2986 };
2987
2988 scope.override_name().is_some_and(|scope_name| {
2989 settings
2990 .edit_predictions_disabled_in
2991 .iter()
2992 .any(|s| s == scope_name)
2993 })
2994 }
2995
2996 pub fn set_use_modal_editing(&mut self, to: bool) {
2997 self.use_modal_editing = to;
2998 }
2999
3000 pub fn use_modal_editing(&self) -> bool {
3001 self.use_modal_editing
3002 }
3003
3004 fn selections_did_change(
3005 &mut self,
3006 local: bool,
3007 old_cursor_position: &Anchor,
3008 effects: SelectionEffects,
3009 window: &mut Window,
3010 cx: &mut Context<Self>,
3011 ) {
3012 window.invalidate_character_coordinates();
3013
3014 // Copy selections to primary selection buffer
3015 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3016 if local {
3017 let selections = self.selections.all::<usize>(cx);
3018 let buffer_handle = self.buffer.read(cx).read(cx);
3019
3020 let mut text = String::new();
3021 for (index, selection) in selections.iter().enumerate() {
3022 let text_for_selection = buffer_handle
3023 .text_for_range(selection.start..selection.end)
3024 .collect::<String>();
3025
3026 text.push_str(&text_for_selection);
3027 if index != selections.len() - 1 {
3028 text.push('\n');
3029 }
3030 }
3031
3032 if !text.is_empty() {
3033 cx.write_to_primary(ClipboardItem::new_string(text));
3034 }
3035 }
3036
3037 let selection_anchors = self.selections.disjoint_anchors();
3038
3039 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3040 self.buffer.update(cx, |buffer, cx| {
3041 buffer.set_active_selections(
3042 &selection_anchors,
3043 self.selections.line_mode,
3044 self.cursor_shape,
3045 cx,
3046 )
3047 });
3048 }
3049 let display_map = self
3050 .display_map
3051 .update(cx, |display_map, cx| display_map.snapshot(cx));
3052 let buffer = &display_map.buffer_snapshot;
3053 if self.selections.count() == 1 {
3054 self.add_selections_state = None;
3055 }
3056 self.select_next_state = None;
3057 self.select_prev_state = None;
3058 self.select_syntax_node_history.try_clear();
3059 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3060 self.snippet_stack.invalidate(&selection_anchors, buffer);
3061 self.take_rename(false, window, cx);
3062
3063 let newest_selection = self.selections.newest_anchor();
3064 let new_cursor_position = newest_selection.head();
3065 let selection_start = newest_selection.start;
3066
3067 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3068 self.push_to_nav_history(
3069 *old_cursor_position,
3070 Some(new_cursor_position.to_point(buffer)),
3071 false,
3072 effects.nav_history == Some(true),
3073 cx,
3074 );
3075 }
3076
3077 if local {
3078 if let Some(buffer_id) = new_cursor_position.buffer_id
3079 && !self.registered_buffers.contains_key(&buffer_id)
3080 && let Some(project) = self.project.as_ref()
3081 {
3082 project.update(cx, |project, cx| {
3083 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3084 return;
3085 };
3086 self.registered_buffers.insert(
3087 buffer_id,
3088 project.register_buffer_with_language_servers(&buffer, cx),
3089 );
3090 })
3091 }
3092
3093 let mut context_menu = self.context_menu.borrow_mut();
3094 let completion_menu = match context_menu.as_ref() {
3095 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3096 Some(CodeContextMenu::CodeActions(_)) => {
3097 *context_menu = None;
3098 None
3099 }
3100 None => None,
3101 };
3102 let completion_position = completion_menu.map(|menu| menu.initial_position);
3103 drop(context_menu);
3104
3105 if effects.completions
3106 && let Some(completion_position) = completion_position
3107 {
3108 let start_offset = selection_start.to_offset(buffer);
3109 let position_matches = start_offset == completion_position.to_offset(buffer);
3110 let continue_showing = if position_matches {
3111 if self.snippet_stack.is_empty() {
3112 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
3113 } else {
3114 // Snippet choices can be shown even when the cursor is in whitespace.
3115 // Dismissing the menu with actions like backspace is handled by
3116 // invalidation regions.
3117 true
3118 }
3119 } else {
3120 false
3121 };
3122
3123 if continue_showing {
3124 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3125 } else {
3126 self.hide_context_menu(window, cx);
3127 }
3128 }
3129
3130 hide_hover(self, cx);
3131
3132 if old_cursor_position.to_display_point(&display_map).row()
3133 != new_cursor_position.to_display_point(&display_map).row()
3134 {
3135 self.available_code_actions.take();
3136 }
3137 self.refresh_code_actions(window, cx);
3138 self.refresh_document_highlights(cx);
3139 self.refresh_selected_text_highlights(false, window, cx);
3140 refresh_matching_bracket_highlights(self, window, cx);
3141 self.update_visible_edit_prediction(window, cx);
3142 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3143 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3144 self.inline_blame_popover.take();
3145 if self.git_blame_inline_enabled {
3146 self.start_inline_blame_timer(window, cx);
3147 }
3148 }
3149
3150 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3151 cx.emit(EditorEvent::SelectionsChanged { local });
3152
3153 let selections = &self.selections.disjoint;
3154 if selections.len() == 1 {
3155 cx.emit(SearchEvent::ActiveMatchChanged)
3156 }
3157 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3158 let inmemory_selections = selections
3159 .iter()
3160 .map(|s| {
3161 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3162 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3163 })
3164 .collect();
3165 self.update_restoration_data(cx, |data| {
3166 data.selections = inmemory_selections;
3167 });
3168
3169 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3170 && let Some(workspace_id) =
3171 self.workspace.as_ref().and_then(|workspace| workspace.1)
3172 {
3173 let snapshot = self.buffer().read(cx).snapshot(cx);
3174 let selections = selections.clone();
3175 let background_executor = cx.background_executor().clone();
3176 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3177 self.serialize_selections = cx.background_spawn(async move {
3178 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3179 let db_selections = selections
3180 .iter()
3181 .map(|selection| {
3182 (
3183 selection.start.to_offset(&snapshot),
3184 selection.end.to_offset(&snapshot),
3185 )
3186 })
3187 .collect();
3188
3189 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3190 .await
3191 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3192 .log_err();
3193 });
3194 }
3195 }
3196
3197 cx.notify();
3198 }
3199
3200 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3201 use text::ToOffset as _;
3202 use text::ToPoint as _;
3203
3204 if self.mode.is_minimap()
3205 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3206 {
3207 return;
3208 }
3209
3210 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3211 return;
3212 };
3213
3214 let snapshot = singleton.read(cx).snapshot();
3215 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3216 let display_snapshot = display_map.snapshot(cx);
3217
3218 display_snapshot
3219 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3220 .map(|fold| {
3221 fold.range.start.text_anchor.to_point(&snapshot)
3222 ..fold.range.end.text_anchor.to_point(&snapshot)
3223 })
3224 .collect()
3225 });
3226 self.update_restoration_data(cx, |data| {
3227 data.folds = inmemory_folds;
3228 });
3229
3230 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3231 return;
3232 };
3233 let background_executor = cx.background_executor().clone();
3234 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3235 let db_folds = self.display_map.update(cx, |display_map, cx| {
3236 display_map
3237 .snapshot(cx)
3238 .folds_in_range(0..snapshot.len())
3239 .map(|fold| {
3240 (
3241 fold.range.start.text_anchor.to_offset(&snapshot),
3242 fold.range.end.text_anchor.to_offset(&snapshot),
3243 )
3244 })
3245 .collect()
3246 });
3247 self.serialize_folds = cx.background_spawn(async move {
3248 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3249 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3250 .await
3251 .with_context(|| {
3252 format!(
3253 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3254 )
3255 })
3256 .log_err();
3257 });
3258 }
3259
3260 pub fn sync_selections(
3261 &mut self,
3262 other: Entity<Editor>,
3263 cx: &mut Context<Self>,
3264 ) -> gpui::Subscription {
3265 let other_selections = other.read(cx).selections.disjoint.to_vec();
3266 self.selections.change_with(cx, |selections| {
3267 selections.select_anchors(other_selections);
3268 });
3269
3270 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3271 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3272 let other_selections = other.read(cx).selections.disjoint.to_vec();
3273 if other_selections.is_empty() {
3274 return;
3275 }
3276 this.selections.change_with(cx, |selections| {
3277 selections.select_anchors(other_selections);
3278 });
3279 }
3280 });
3281
3282 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3283 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3284 let these_selections = this.selections.disjoint.to_vec();
3285 if these_selections.is_empty() {
3286 return;
3287 }
3288 other.update(cx, |other_editor, cx| {
3289 other_editor.selections.change_with(cx, |selections| {
3290 selections.select_anchors(these_selections);
3291 })
3292 });
3293 }
3294 });
3295
3296 Subscription::join(other_subscription, this_subscription)
3297 }
3298
3299 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3300 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3301 /// effects of selection change occur at the end of the transaction.
3302 pub fn change_selections<R>(
3303 &mut self,
3304 effects: SelectionEffects,
3305 window: &mut Window,
3306 cx: &mut Context<Self>,
3307 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3308 ) -> R {
3309 if let Some(state) = &mut self.deferred_selection_effects_state {
3310 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3311 state.effects.completions = effects.completions;
3312 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3313 let (changed, result) = self.selections.change_with(cx, change);
3314 state.changed |= changed;
3315 return result;
3316 }
3317 let mut state = DeferredSelectionEffectsState {
3318 changed: false,
3319 effects,
3320 old_cursor_position: self.selections.newest_anchor().head(),
3321 history_entry: SelectionHistoryEntry {
3322 selections: self.selections.disjoint_anchors(),
3323 select_next_state: self.select_next_state.clone(),
3324 select_prev_state: self.select_prev_state.clone(),
3325 add_selections_state: self.add_selections_state.clone(),
3326 },
3327 };
3328 let (changed, result) = self.selections.change_with(cx, change);
3329 state.changed = state.changed || changed;
3330 if self.defer_selection_effects {
3331 self.deferred_selection_effects_state = Some(state);
3332 } else {
3333 self.apply_selection_effects(state, window, cx);
3334 }
3335 result
3336 }
3337
3338 /// Defers the effects of selection change, so that the effects of multiple calls to
3339 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3340 /// to selection history and the state of popovers based on selection position aren't
3341 /// erroneously updated.
3342 pub fn with_selection_effects_deferred<R>(
3343 &mut self,
3344 window: &mut Window,
3345 cx: &mut Context<Self>,
3346 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3347 ) -> R {
3348 let already_deferred = self.defer_selection_effects;
3349 self.defer_selection_effects = true;
3350 let result = update(self, window, cx);
3351 if !already_deferred {
3352 self.defer_selection_effects = false;
3353 if let Some(state) = self.deferred_selection_effects_state.take() {
3354 self.apply_selection_effects(state, window, cx);
3355 }
3356 }
3357 result
3358 }
3359
3360 fn apply_selection_effects(
3361 &mut self,
3362 state: DeferredSelectionEffectsState,
3363 window: &mut Window,
3364 cx: &mut Context<Self>,
3365 ) {
3366 if state.changed {
3367 self.selection_history.push(state.history_entry);
3368
3369 if let Some(autoscroll) = state.effects.scroll {
3370 self.request_autoscroll(autoscroll, cx);
3371 }
3372
3373 let old_cursor_position = &state.old_cursor_position;
3374
3375 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3376
3377 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3378 self.show_signature_help(&ShowSignatureHelp, window, cx);
3379 }
3380 }
3381 }
3382
3383 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3384 where
3385 I: IntoIterator<Item = (Range<S>, T)>,
3386 S: ToOffset,
3387 T: Into<Arc<str>>,
3388 {
3389 if self.read_only(cx) {
3390 return;
3391 }
3392
3393 self.buffer
3394 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3395 }
3396
3397 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3398 where
3399 I: IntoIterator<Item = (Range<S>, T)>,
3400 S: ToOffset,
3401 T: Into<Arc<str>>,
3402 {
3403 if self.read_only(cx) {
3404 return;
3405 }
3406
3407 self.buffer.update(cx, |buffer, cx| {
3408 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3409 });
3410 }
3411
3412 pub fn edit_with_block_indent<I, S, T>(
3413 &mut self,
3414 edits: I,
3415 original_indent_columns: Vec<Option<u32>>,
3416 cx: &mut Context<Self>,
3417 ) where
3418 I: IntoIterator<Item = (Range<S>, T)>,
3419 S: ToOffset,
3420 T: Into<Arc<str>>,
3421 {
3422 if self.read_only(cx) {
3423 return;
3424 }
3425
3426 self.buffer.update(cx, |buffer, cx| {
3427 buffer.edit(
3428 edits,
3429 Some(AutoindentMode::Block {
3430 original_indent_columns,
3431 }),
3432 cx,
3433 )
3434 });
3435 }
3436
3437 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3438 self.hide_context_menu(window, cx);
3439
3440 match phase {
3441 SelectPhase::Begin {
3442 position,
3443 add,
3444 click_count,
3445 } => self.begin_selection(position, add, click_count, window, cx),
3446 SelectPhase::BeginColumnar {
3447 position,
3448 goal_column,
3449 reset,
3450 mode,
3451 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3452 SelectPhase::Extend {
3453 position,
3454 click_count,
3455 } => self.extend_selection(position, click_count, window, cx),
3456 SelectPhase::Update {
3457 position,
3458 goal_column,
3459 scroll_delta,
3460 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3461 SelectPhase::End => self.end_selection(window, cx),
3462 }
3463 }
3464
3465 fn extend_selection(
3466 &mut self,
3467 position: DisplayPoint,
3468 click_count: usize,
3469 window: &mut Window,
3470 cx: &mut Context<Self>,
3471 ) {
3472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3473 let tail = self.selections.newest::<usize>(cx).tail();
3474 self.begin_selection(position, false, click_count, window, cx);
3475
3476 let position = position.to_offset(&display_map, Bias::Left);
3477 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3478
3479 let mut pending_selection = self
3480 .selections
3481 .pending_anchor()
3482 .expect("extend_selection not called with pending selection");
3483 if position >= tail {
3484 pending_selection.start = tail_anchor;
3485 } else {
3486 pending_selection.end = tail_anchor;
3487 pending_selection.reversed = true;
3488 }
3489
3490 let mut pending_mode = self.selections.pending_mode().unwrap();
3491 match &mut pending_mode {
3492 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3493 _ => {}
3494 }
3495
3496 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3497 SelectionEffects::scroll(Autoscroll::fit())
3498 } else {
3499 SelectionEffects::no_scroll()
3500 };
3501
3502 self.change_selections(effects, window, cx, |s| {
3503 s.set_pending(pending_selection, pending_mode)
3504 });
3505 }
3506
3507 fn begin_selection(
3508 &mut self,
3509 position: DisplayPoint,
3510 add: bool,
3511 click_count: usize,
3512 window: &mut Window,
3513 cx: &mut Context<Self>,
3514 ) {
3515 if !self.focus_handle.is_focused(window) {
3516 self.last_focused_descendant = None;
3517 window.focus(&self.focus_handle);
3518 }
3519
3520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3521 let buffer = &display_map.buffer_snapshot;
3522 let position = display_map.clip_point(position, Bias::Left);
3523
3524 let start;
3525 let end;
3526 let mode;
3527 let mut auto_scroll;
3528 match click_count {
3529 1 => {
3530 start = buffer.anchor_before(position.to_point(&display_map));
3531 end = start;
3532 mode = SelectMode::Character;
3533 auto_scroll = true;
3534 }
3535 2 => {
3536 let position = display_map
3537 .clip_point(position, Bias::Left)
3538 .to_offset(&display_map, Bias::Left);
3539 let (range, _) = buffer.surrounding_word(position, false);
3540 start = buffer.anchor_before(range.start);
3541 end = buffer.anchor_before(range.end);
3542 mode = SelectMode::Word(start..end);
3543 auto_scroll = true;
3544 }
3545 3 => {
3546 let position = display_map
3547 .clip_point(position, Bias::Left)
3548 .to_point(&display_map);
3549 let line_start = display_map.prev_line_boundary(position).0;
3550 let next_line_start = buffer.clip_point(
3551 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3552 Bias::Left,
3553 );
3554 start = buffer.anchor_before(line_start);
3555 end = buffer.anchor_before(next_line_start);
3556 mode = SelectMode::Line(start..end);
3557 auto_scroll = true;
3558 }
3559 _ => {
3560 start = buffer.anchor_before(0);
3561 end = buffer.anchor_before(buffer.len());
3562 mode = SelectMode::All;
3563 auto_scroll = false;
3564 }
3565 }
3566 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3567
3568 let point_to_delete: Option<usize> = {
3569 let selected_points: Vec<Selection<Point>> =
3570 self.selections.disjoint_in_range(start..end, cx);
3571
3572 if !add || click_count > 1 {
3573 None
3574 } else if !selected_points.is_empty() {
3575 Some(selected_points[0].id)
3576 } else {
3577 let clicked_point_already_selected =
3578 self.selections.disjoint.iter().find(|selection| {
3579 selection.start.to_point(buffer) == start.to_point(buffer)
3580 || selection.end.to_point(buffer) == end.to_point(buffer)
3581 });
3582
3583 clicked_point_already_selected.map(|selection| selection.id)
3584 }
3585 };
3586
3587 let selections_count = self.selections.count();
3588 let effects = if auto_scroll {
3589 SelectionEffects::default()
3590 } else {
3591 SelectionEffects::no_scroll()
3592 };
3593
3594 self.change_selections(effects, window, cx, |s| {
3595 if let Some(point_to_delete) = point_to_delete {
3596 s.delete(point_to_delete);
3597
3598 if selections_count == 1 {
3599 s.set_pending_anchor_range(start..end, mode);
3600 }
3601 } else {
3602 if !add {
3603 s.clear_disjoint();
3604 }
3605
3606 s.set_pending_anchor_range(start..end, mode);
3607 }
3608 });
3609 }
3610
3611 fn begin_columnar_selection(
3612 &mut self,
3613 position: DisplayPoint,
3614 goal_column: u32,
3615 reset: bool,
3616 mode: ColumnarMode,
3617 window: &mut Window,
3618 cx: &mut Context<Self>,
3619 ) {
3620 if !self.focus_handle.is_focused(window) {
3621 self.last_focused_descendant = None;
3622 window.focus(&self.focus_handle);
3623 }
3624
3625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3626
3627 if reset {
3628 let pointer_position = display_map
3629 .buffer_snapshot
3630 .anchor_before(position.to_point(&display_map));
3631
3632 self.change_selections(
3633 SelectionEffects::scroll(Autoscroll::newest()),
3634 window,
3635 cx,
3636 |s| {
3637 s.clear_disjoint();
3638 s.set_pending_anchor_range(
3639 pointer_position..pointer_position,
3640 SelectMode::Character,
3641 );
3642 },
3643 );
3644 };
3645
3646 let tail = self.selections.newest::<Point>(cx).tail();
3647 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3648 self.columnar_selection_state = match mode {
3649 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3650 selection_tail: selection_anchor,
3651 display_point: if reset {
3652 if position.column() != goal_column {
3653 Some(DisplayPoint::new(position.row(), goal_column))
3654 } else {
3655 None
3656 }
3657 } else {
3658 None
3659 },
3660 }),
3661 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3662 selection_tail: selection_anchor,
3663 }),
3664 };
3665
3666 if !reset {
3667 self.select_columns(position, goal_column, &display_map, window, cx);
3668 }
3669 }
3670
3671 fn update_selection(
3672 &mut self,
3673 position: DisplayPoint,
3674 goal_column: u32,
3675 scroll_delta: gpui::Point<f32>,
3676 window: &mut Window,
3677 cx: &mut Context<Self>,
3678 ) {
3679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3680
3681 if self.columnar_selection_state.is_some() {
3682 self.select_columns(position, goal_column, &display_map, window, cx);
3683 } else if let Some(mut pending) = self.selections.pending_anchor() {
3684 let buffer = &display_map.buffer_snapshot;
3685 let head;
3686 let tail;
3687 let mode = self.selections.pending_mode().unwrap();
3688 match &mode {
3689 SelectMode::Character => {
3690 head = position.to_point(&display_map);
3691 tail = pending.tail().to_point(buffer);
3692 }
3693 SelectMode::Word(original_range) => {
3694 let offset = display_map
3695 .clip_point(position, Bias::Left)
3696 .to_offset(&display_map, Bias::Left);
3697 let original_range = original_range.to_offset(buffer);
3698
3699 let head_offset = if buffer.is_inside_word(offset, false)
3700 || original_range.contains(&offset)
3701 {
3702 let (word_range, _) = buffer.surrounding_word(offset, false);
3703 if word_range.start < original_range.start {
3704 word_range.start
3705 } else {
3706 word_range.end
3707 }
3708 } else {
3709 offset
3710 };
3711
3712 head = head_offset.to_point(buffer);
3713 if head_offset <= original_range.start {
3714 tail = original_range.end.to_point(buffer);
3715 } else {
3716 tail = original_range.start.to_point(buffer);
3717 }
3718 }
3719 SelectMode::Line(original_range) => {
3720 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3721
3722 let position = display_map
3723 .clip_point(position, Bias::Left)
3724 .to_point(&display_map);
3725 let line_start = display_map.prev_line_boundary(position).0;
3726 let next_line_start = buffer.clip_point(
3727 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3728 Bias::Left,
3729 );
3730
3731 if line_start < original_range.start {
3732 head = line_start
3733 } else {
3734 head = next_line_start
3735 }
3736
3737 if head <= original_range.start {
3738 tail = original_range.end;
3739 } else {
3740 tail = original_range.start;
3741 }
3742 }
3743 SelectMode::All => {
3744 return;
3745 }
3746 };
3747
3748 if head < tail {
3749 pending.start = buffer.anchor_before(head);
3750 pending.end = buffer.anchor_before(tail);
3751 pending.reversed = true;
3752 } else {
3753 pending.start = buffer.anchor_before(tail);
3754 pending.end = buffer.anchor_before(head);
3755 pending.reversed = false;
3756 }
3757
3758 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3759 s.set_pending(pending, mode);
3760 });
3761 } else {
3762 log::error!("update_selection dispatched with no pending selection");
3763 return;
3764 }
3765
3766 self.apply_scroll_delta(scroll_delta, window, cx);
3767 cx.notify();
3768 }
3769
3770 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3771 self.columnar_selection_state.take();
3772 if self.selections.pending_anchor().is_some() {
3773 let selections = self.selections.all::<usize>(cx);
3774 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3775 s.select(selections);
3776 s.clear_pending();
3777 });
3778 }
3779 }
3780
3781 fn select_columns(
3782 &mut self,
3783 head: DisplayPoint,
3784 goal_column: u32,
3785 display_map: &DisplaySnapshot,
3786 window: &mut Window,
3787 cx: &mut Context<Self>,
3788 ) {
3789 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3790 return;
3791 };
3792
3793 let tail = match columnar_state {
3794 ColumnarSelectionState::FromMouse {
3795 selection_tail,
3796 display_point,
3797 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3798 ColumnarSelectionState::FromSelection { selection_tail } => {
3799 selection_tail.to_display_point(display_map)
3800 }
3801 };
3802
3803 let start_row = cmp::min(tail.row(), head.row());
3804 let end_row = cmp::max(tail.row(), head.row());
3805 let start_column = cmp::min(tail.column(), goal_column);
3806 let end_column = cmp::max(tail.column(), goal_column);
3807 let reversed = start_column < tail.column();
3808
3809 let selection_ranges = (start_row.0..=end_row.0)
3810 .map(DisplayRow)
3811 .filter_map(|row| {
3812 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3813 || start_column <= display_map.line_len(row))
3814 && !display_map.is_block_line(row)
3815 {
3816 let start = display_map
3817 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3818 .to_point(display_map);
3819 let end = display_map
3820 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3821 .to_point(display_map);
3822 if reversed {
3823 Some(end..start)
3824 } else {
3825 Some(start..end)
3826 }
3827 } else {
3828 None
3829 }
3830 })
3831 .collect::<Vec<_>>();
3832
3833 let ranges = match columnar_state {
3834 ColumnarSelectionState::FromMouse { .. } => {
3835 let mut non_empty_ranges = selection_ranges
3836 .iter()
3837 .filter(|selection_range| selection_range.start != selection_range.end)
3838 .peekable();
3839 if non_empty_ranges.peek().is_some() {
3840 non_empty_ranges.cloned().collect()
3841 } else {
3842 selection_ranges
3843 }
3844 }
3845 _ => selection_ranges,
3846 };
3847
3848 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3849 s.select_ranges(ranges);
3850 });
3851 cx.notify();
3852 }
3853
3854 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3855 self.selections
3856 .all_adjusted(cx)
3857 .iter()
3858 .any(|selection| !selection.is_empty())
3859 }
3860
3861 pub fn has_pending_nonempty_selection(&self) -> bool {
3862 let pending_nonempty_selection = match self.selections.pending_anchor() {
3863 Some(Selection { start, end, .. }) => start != end,
3864 None => false,
3865 };
3866
3867 pending_nonempty_selection
3868 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3869 }
3870
3871 pub fn has_pending_selection(&self) -> bool {
3872 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3873 }
3874
3875 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3876 self.selection_mark_mode = false;
3877 self.selection_drag_state = SelectionDragState::None;
3878
3879 if self.clear_expanded_diff_hunks(cx) {
3880 cx.notify();
3881 return;
3882 }
3883 if self.dismiss_menus_and_popups(true, window, cx) {
3884 return;
3885 }
3886
3887 if self.mode.is_full()
3888 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3889 {
3890 return;
3891 }
3892
3893 cx.propagate();
3894 }
3895
3896 pub fn dismiss_menus_and_popups(
3897 &mut self,
3898 is_user_requested: bool,
3899 window: &mut Window,
3900 cx: &mut Context<Self>,
3901 ) -> bool {
3902 if self.take_rename(false, window, cx).is_some() {
3903 return true;
3904 }
3905
3906 if hide_hover(self, cx) {
3907 return true;
3908 }
3909
3910 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3911 return true;
3912 }
3913
3914 if self.hide_context_menu(window, cx).is_some() {
3915 return true;
3916 }
3917
3918 if self.mouse_context_menu.take().is_some() {
3919 return true;
3920 }
3921
3922 if is_user_requested && self.discard_edit_prediction(true, cx) {
3923 return true;
3924 }
3925
3926 if self.snippet_stack.pop().is_some() {
3927 return true;
3928 }
3929
3930 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3931 self.dismiss_diagnostics(cx);
3932 return true;
3933 }
3934
3935 false
3936 }
3937
3938 fn linked_editing_ranges_for(
3939 &self,
3940 selection: Range<text::Anchor>,
3941 cx: &App,
3942 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3943 if self.linked_edit_ranges.is_empty() {
3944 return None;
3945 }
3946 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3947 selection.end.buffer_id.and_then(|end_buffer_id| {
3948 if selection.start.buffer_id != Some(end_buffer_id) {
3949 return None;
3950 }
3951 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3952 let snapshot = buffer.read(cx).snapshot();
3953 self.linked_edit_ranges
3954 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3955 .map(|ranges| (ranges, snapshot, buffer))
3956 })?;
3957 use text::ToOffset as TO;
3958 // find offset from the start of current range to current cursor position
3959 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3960
3961 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3962 let start_difference = start_offset - start_byte_offset;
3963 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3964 let end_difference = end_offset - start_byte_offset;
3965 // Current range has associated linked ranges.
3966 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3967 for range in linked_ranges.iter() {
3968 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3969 let end_offset = start_offset + end_difference;
3970 let start_offset = start_offset + start_difference;
3971 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3972 continue;
3973 }
3974 if self.selections.disjoint_anchor_ranges().any(|s| {
3975 if s.start.buffer_id != selection.start.buffer_id
3976 || s.end.buffer_id != selection.end.buffer_id
3977 {
3978 return false;
3979 }
3980 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3981 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3982 }) {
3983 continue;
3984 }
3985 let start = buffer_snapshot.anchor_after(start_offset);
3986 let end = buffer_snapshot.anchor_after(end_offset);
3987 linked_edits
3988 .entry(buffer.clone())
3989 .or_default()
3990 .push(start..end);
3991 }
3992 Some(linked_edits)
3993 }
3994
3995 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3996 let text: Arc<str> = text.into();
3997
3998 if self.read_only(cx) {
3999 return;
4000 }
4001
4002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4003
4004 let selections = self.selections.all_adjusted(cx);
4005 let mut bracket_inserted = false;
4006 let mut edits = Vec::new();
4007 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4008 let mut new_selections = Vec::with_capacity(selections.len());
4009 let mut new_autoclose_regions = Vec::new();
4010 let snapshot = self.buffer.read(cx).read(cx);
4011 let mut clear_linked_edit_ranges = false;
4012
4013 for (selection, autoclose_region) in
4014 self.selections_with_autoclose_regions(selections, &snapshot)
4015 {
4016 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4017 // Determine if the inserted text matches the opening or closing
4018 // bracket of any of this language's bracket pairs.
4019 let mut bracket_pair = None;
4020 let mut is_bracket_pair_start = false;
4021 let mut is_bracket_pair_end = false;
4022 if !text.is_empty() {
4023 let mut bracket_pair_matching_end = None;
4024 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4025 // and they are removing the character that triggered IME popup.
4026 for (pair, enabled) in scope.brackets() {
4027 if !pair.close && !pair.surround {
4028 continue;
4029 }
4030
4031 if enabled && pair.start.ends_with(text.as_ref()) {
4032 let prefix_len = pair.start.len() - text.len();
4033 let preceding_text_matches_prefix = prefix_len == 0
4034 || (selection.start.column >= (prefix_len as u32)
4035 && snapshot.contains_str_at(
4036 Point::new(
4037 selection.start.row,
4038 selection.start.column - (prefix_len as u32),
4039 ),
4040 &pair.start[..prefix_len],
4041 ));
4042 if preceding_text_matches_prefix {
4043 bracket_pair = Some(pair.clone());
4044 is_bracket_pair_start = true;
4045 break;
4046 }
4047 }
4048 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4049 {
4050 // take first bracket pair matching end, but don't break in case a later bracket
4051 // pair matches start
4052 bracket_pair_matching_end = Some(pair.clone());
4053 }
4054 }
4055 if let Some(end) = bracket_pair_matching_end
4056 && bracket_pair.is_none()
4057 {
4058 bracket_pair = Some(end);
4059 is_bracket_pair_end = true;
4060 }
4061 }
4062
4063 if let Some(bracket_pair) = bracket_pair {
4064 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4065 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4066 let auto_surround =
4067 self.use_auto_surround && snapshot_settings.use_auto_surround;
4068 if selection.is_empty() {
4069 if is_bracket_pair_start {
4070 // If the inserted text is a suffix of an opening bracket and the
4071 // selection is preceded by the rest of the opening bracket, then
4072 // insert the closing bracket.
4073 let following_text_allows_autoclose = snapshot
4074 .chars_at(selection.start)
4075 .next()
4076 .is_none_or(|c| scope.should_autoclose_before(c));
4077
4078 let preceding_text_allows_autoclose = selection.start.column == 0
4079 || snapshot
4080 .reversed_chars_at(selection.start)
4081 .next()
4082 .is_none_or(|c| {
4083 bracket_pair.start != bracket_pair.end
4084 || !snapshot
4085 .char_classifier_at(selection.start)
4086 .is_word(c)
4087 });
4088
4089 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4090 && bracket_pair.start.len() == 1
4091 {
4092 let target = bracket_pair.start.chars().next().unwrap();
4093 let current_line_count = snapshot
4094 .reversed_chars_at(selection.start)
4095 .take_while(|&c| c != '\n')
4096 .filter(|&c| c == target)
4097 .count();
4098 current_line_count % 2 == 1
4099 } else {
4100 false
4101 };
4102
4103 if autoclose
4104 && bracket_pair.close
4105 && following_text_allows_autoclose
4106 && preceding_text_allows_autoclose
4107 && !is_closing_quote
4108 {
4109 let anchor = snapshot.anchor_before(selection.end);
4110 new_selections.push((selection.map(|_| anchor), text.len()));
4111 new_autoclose_regions.push((
4112 anchor,
4113 text.len(),
4114 selection.id,
4115 bracket_pair.clone(),
4116 ));
4117 edits.push((
4118 selection.range(),
4119 format!("{}{}", text, bracket_pair.end).into(),
4120 ));
4121 bracket_inserted = true;
4122 continue;
4123 }
4124 }
4125
4126 if let Some(region) = autoclose_region {
4127 // If the selection is followed by an auto-inserted closing bracket,
4128 // then don't insert that closing bracket again; just move the selection
4129 // past the closing bracket.
4130 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4131 && text.as_ref() == region.pair.end.as_str()
4132 && snapshot.contains_str_at(region.range.end, text.as_ref());
4133 if should_skip {
4134 let anchor = snapshot.anchor_after(selection.end);
4135 new_selections
4136 .push((selection.map(|_| anchor), region.pair.end.len()));
4137 continue;
4138 }
4139 }
4140
4141 let always_treat_brackets_as_autoclosed = snapshot
4142 .language_settings_at(selection.start, cx)
4143 .always_treat_brackets_as_autoclosed;
4144 if always_treat_brackets_as_autoclosed
4145 && is_bracket_pair_end
4146 && snapshot.contains_str_at(selection.end, text.as_ref())
4147 {
4148 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4149 // and the inserted text is a closing bracket and the selection is followed
4150 // by the closing bracket then move the selection past the closing bracket.
4151 let anchor = snapshot.anchor_after(selection.end);
4152 new_selections.push((selection.map(|_| anchor), text.len()));
4153 continue;
4154 }
4155 }
4156 // If an opening bracket is 1 character long and is typed while
4157 // text is selected, then surround that text with the bracket pair.
4158 else if auto_surround
4159 && bracket_pair.surround
4160 && is_bracket_pair_start
4161 && bracket_pair.start.chars().count() == 1
4162 {
4163 edits.push((selection.start..selection.start, text.clone()));
4164 edits.push((
4165 selection.end..selection.end,
4166 bracket_pair.end.as_str().into(),
4167 ));
4168 bracket_inserted = true;
4169 new_selections.push((
4170 Selection {
4171 id: selection.id,
4172 start: snapshot.anchor_after(selection.start),
4173 end: snapshot.anchor_before(selection.end),
4174 reversed: selection.reversed,
4175 goal: selection.goal,
4176 },
4177 0,
4178 ));
4179 continue;
4180 }
4181 }
4182 }
4183
4184 if self.auto_replace_emoji_shortcode
4185 && selection.is_empty()
4186 && text.as_ref().ends_with(':')
4187 && let Some(possible_emoji_short_code) =
4188 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4189 && !possible_emoji_short_code.is_empty()
4190 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4191 {
4192 let emoji_shortcode_start = Point::new(
4193 selection.start.row,
4194 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4195 );
4196
4197 // Remove shortcode from buffer
4198 edits.push((
4199 emoji_shortcode_start..selection.start,
4200 "".to_string().into(),
4201 ));
4202 new_selections.push((
4203 Selection {
4204 id: selection.id,
4205 start: snapshot.anchor_after(emoji_shortcode_start),
4206 end: snapshot.anchor_before(selection.start),
4207 reversed: selection.reversed,
4208 goal: selection.goal,
4209 },
4210 0,
4211 ));
4212
4213 // Insert emoji
4214 let selection_start_anchor = snapshot.anchor_after(selection.start);
4215 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4216 edits.push((selection.start..selection.end, emoji.to_string().into()));
4217
4218 continue;
4219 }
4220
4221 // If not handling any auto-close operation, then just replace the selected
4222 // text with the given input and move the selection to the end of the
4223 // newly inserted text.
4224 let anchor = snapshot.anchor_after(selection.end);
4225 if !self.linked_edit_ranges.is_empty() {
4226 let start_anchor = snapshot.anchor_before(selection.start);
4227
4228 let is_word_char = text.chars().next().is_none_or(|char| {
4229 let classifier = snapshot
4230 .char_classifier_at(start_anchor.to_offset(&snapshot))
4231 .ignore_punctuation(true);
4232 classifier.is_word(char)
4233 });
4234
4235 if is_word_char {
4236 if let Some(ranges) = self
4237 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4238 {
4239 for (buffer, edits) in ranges {
4240 linked_edits
4241 .entry(buffer.clone())
4242 .or_default()
4243 .extend(edits.into_iter().map(|range| (range, text.clone())));
4244 }
4245 }
4246 } else {
4247 clear_linked_edit_ranges = true;
4248 }
4249 }
4250
4251 new_selections.push((selection.map(|_| anchor), 0));
4252 edits.push((selection.start..selection.end, text.clone()));
4253 }
4254
4255 drop(snapshot);
4256
4257 self.transact(window, cx, |this, window, cx| {
4258 if clear_linked_edit_ranges {
4259 this.linked_edit_ranges.clear();
4260 }
4261 let initial_buffer_versions =
4262 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4263
4264 this.buffer.update(cx, |buffer, cx| {
4265 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4266 });
4267 for (buffer, edits) in linked_edits {
4268 buffer.update(cx, |buffer, cx| {
4269 let snapshot = buffer.snapshot();
4270 let edits = edits
4271 .into_iter()
4272 .map(|(range, text)| {
4273 use text::ToPoint as TP;
4274 let end_point = TP::to_point(&range.end, &snapshot);
4275 let start_point = TP::to_point(&range.start, &snapshot);
4276 (start_point..end_point, text)
4277 })
4278 .sorted_by_key(|(range, _)| range.start);
4279 buffer.edit(edits, None, cx);
4280 })
4281 }
4282 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4283 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4284 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4285 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4286 .zip(new_selection_deltas)
4287 .map(|(selection, delta)| Selection {
4288 id: selection.id,
4289 start: selection.start + delta,
4290 end: selection.end + delta,
4291 reversed: selection.reversed,
4292 goal: SelectionGoal::None,
4293 })
4294 .collect::<Vec<_>>();
4295
4296 let mut i = 0;
4297 for (position, delta, selection_id, pair) in new_autoclose_regions {
4298 let position = position.to_offset(&map.buffer_snapshot) + delta;
4299 let start = map.buffer_snapshot.anchor_before(position);
4300 let end = map.buffer_snapshot.anchor_after(position);
4301 while let Some(existing_state) = this.autoclose_regions.get(i) {
4302 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4303 Ordering::Less => i += 1,
4304 Ordering::Greater => break,
4305 Ordering::Equal => {
4306 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4307 Ordering::Less => i += 1,
4308 Ordering::Equal => break,
4309 Ordering::Greater => break,
4310 }
4311 }
4312 }
4313 }
4314 this.autoclose_regions.insert(
4315 i,
4316 AutocloseRegion {
4317 selection_id,
4318 range: start..end,
4319 pair,
4320 },
4321 );
4322 }
4323
4324 let had_active_edit_prediction = this.has_active_edit_prediction();
4325 this.change_selections(
4326 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4327 window,
4328 cx,
4329 |s| s.select(new_selections),
4330 );
4331
4332 if !bracket_inserted
4333 && let Some(on_type_format_task) =
4334 this.trigger_on_type_formatting(text.to_string(), window, cx)
4335 {
4336 on_type_format_task.detach_and_log_err(cx);
4337 }
4338
4339 let editor_settings = EditorSettings::get_global(cx);
4340 if bracket_inserted
4341 && (editor_settings.auto_signature_help
4342 || editor_settings.show_signature_help_after_edits)
4343 {
4344 this.show_signature_help(&ShowSignatureHelp, window, cx);
4345 }
4346
4347 let trigger_in_words =
4348 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4349 if this.hard_wrap.is_some() {
4350 let latest: Range<Point> = this.selections.newest(cx).range();
4351 if latest.is_empty()
4352 && this
4353 .buffer()
4354 .read(cx)
4355 .snapshot(cx)
4356 .line_len(MultiBufferRow(latest.start.row))
4357 == latest.start.column
4358 {
4359 this.rewrap_impl(
4360 RewrapOptions {
4361 override_language_settings: true,
4362 preserve_existing_whitespace: true,
4363 },
4364 cx,
4365 )
4366 }
4367 }
4368 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4369 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4370 this.refresh_edit_prediction(true, false, window, cx);
4371 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4372 });
4373 }
4374
4375 fn find_possible_emoji_shortcode_at_position(
4376 snapshot: &MultiBufferSnapshot,
4377 position: Point,
4378 ) -> Option<String> {
4379 let mut chars = Vec::new();
4380 let mut found_colon = false;
4381 for char in snapshot.reversed_chars_at(position).take(100) {
4382 // Found a possible emoji shortcode in the middle of the buffer
4383 if found_colon {
4384 if char.is_whitespace() {
4385 chars.reverse();
4386 return Some(chars.iter().collect());
4387 }
4388 // If the previous character is not a whitespace, we are in the middle of a word
4389 // and we only want to complete the shortcode if the word is made up of other emojis
4390 let mut containing_word = String::new();
4391 for ch in snapshot
4392 .reversed_chars_at(position)
4393 .skip(chars.len() + 1)
4394 .take(100)
4395 {
4396 if ch.is_whitespace() {
4397 break;
4398 }
4399 containing_word.push(ch);
4400 }
4401 let containing_word = containing_word.chars().rev().collect::<String>();
4402 if util::word_consists_of_emojis(containing_word.as_str()) {
4403 chars.reverse();
4404 return Some(chars.iter().collect());
4405 }
4406 }
4407
4408 if char.is_whitespace() || !char.is_ascii() {
4409 return None;
4410 }
4411 if char == ':' {
4412 found_colon = true;
4413 } else {
4414 chars.push(char);
4415 }
4416 }
4417 // Found a possible emoji shortcode at the beginning of the buffer
4418 chars.reverse();
4419 Some(chars.iter().collect())
4420 }
4421
4422 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4423 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4424 self.transact(window, cx, |this, window, cx| {
4425 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4426 let selections = this.selections.all::<usize>(cx);
4427 let multi_buffer = this.buffer.read(cx);
4428 let buffer = multi_buffer.snapshot(cx);
4429 selections
4430 .iter()
4431 .map(|selection| {
4432 let start_point = selection.start.to_point(&buffer);
4433 let mut existing_indent =
4434 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4435 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4436 let start = selection.start;
4437 let end = selection.end;
4438 let selection_is_empty = start == end;
4439 let language_scope = buffer.language_scope_at(start);
4440 let (
4441 comment_delimiter,
4442 doc_delimiter,
4443 insert_extra_newline,
4444 indent_on_newline,
4445 indent_on_extra_newline,
4446 ) = if let Some(language) = &language_scope {
4447 let mut insert_extra_newline =
4448 insert_extra_newline_brackets(&buffer, start..end, language)
4449 || insert_extra_newline_tree_sitter(&buffer, start..end);
4450
4451 // Comment extension on newline is allowed only for cursor selections
4452 let comment_delimiter = maybe!({
4453 if !selection_is_empty {
4454 return None;
4455 }
4456
4457 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4458 return None;
4459 }
4460
4461 let delimiters = language.line_comment_prefixes();
4462 let max_len_of_delimiter =
4463 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4464 let (snapshot, range) =
4465 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4466
4467 let num_of_whitespaces = snapshot
4468 .chars_for_range(range.clone())
4469 .take_while(|c| c.is_whitespace())
4470 .count();
4471 let comment_candidate = snapshot
4472 .chars_for_range(range.clone())
4473 .skip(num_of_whitespaces)
4474 .take(max_len_of_delimiter)
4475 .collect::<String>();
4476 let (delimiter, trimmed_len) = delimiters
4477 .iter()
4478 .filter_map(|delimiter| {
4479 let prefix = delimiter.trim_end();
4480 if comment_candidate.starts_with(prefix) {
4481 Some((delimiter, prefix.len()))
4482 } else {
4483 None
4484 }
4485 })
4486 .max_by_key(|(_, len)| *len)?;
4487
4488 if let Some(BlockCommentConfig {
4489 start: block_start, ..
4490 }) = language.block_comment()
4491 {
4492 let block_start_trimmed = block_start.trim_end();
4493 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4494 let line_content = snapshot
4495 .chars_for_range(range)
4496 .skip(num_of_whitespaces)
4497 .take(block_start_trimmed.len())
4498 .collect::<String>();
4499
4500 if line_content.starts_with(block_start_trimmed) {
4501 return None;
4502 }
4503 }
4504 }
4505
4506 let cursor_is_placed_after_comment_marker =
4507 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4508 if cursor_is_placed_after_comment_marker {
4509 Some(delimiter.clone())
4510 } else {
4511 None
4512 }
4513 });
4514
4515 let mut indent_on_newline = IndentSize::spaces(0);
4516 let mut indent_on_extra_newline = IndentSize::spaces(0);
4517
4518 let doc_delimiter = maybe!({
4519 if !selection_is_empty {
4520 return None;
4521 }
4522
4523 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4524 return None;
4525 }
4526
4527 let BlockCommentConfig {
4528 start: start_tag,
4529 end: end_tag,
4530 prefix: delimiter,
4531 tab_size: len,
4532 } = language.documentation_comment()?;
4533 let is_within_block_comment = buffer
4534 .language_scope_at(start_point)
4535 .is_some_and(|scope| scope.override_name() == Some("comment"));
4536 if !is_within_block_comment {
4537 return None;
4538 }
4539
4540 let (snapshot, range) =
4541 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4542
4543 let num_of_whitespaces = snapshot
4544 .chars_for_range(range.clone())
4545 .take_while(|c| c.is_whitespace())
4546 .count();
4547
4548 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4549 let column = start_point.column;
4550 let cursor_is_after_start_tag = {
4551 let start_tag_len = start_tag.len();
4552 let start_tag_line = snapshot
4553 .chars_for_range(range.clone())
4554 .skip(num_of_whitespaces)
4555 .take(start_tag_len)
4556 .collect::<String>();
4557 if start_tag_line.starts_with(start_tag.as_ref()) {
4558 num_of_whitespaces + start_tag_len <= column as usize
4559 } else {
4560 false
4561 }
4562 };
4563
4564 let cursor_is_after_delimiter = {
4565 let delimiter_trim = delimiter.trim_end();
4566 let delimiter_line = snapshot
4567 .chars_for_range(range.clone())
4568 .skip(num_of_whitespaces)
4569 .take(delimiter_trim.len())
4570 .collect::<String>();
4571 if delimiter_line.starts_with(delimiter_trim) {
4572 num_of_whitespaces + delimiter_trim.len() <= column as usize
4573 } else {
4574 false
4575 }
4576 };
4577
4578 let cursor_is_before_end_tag_if_exists = {
4579 let mut char_position = 0u32;
4580 let mut end_tag_offset = None;
4581
4582 'outer: for chunk in snapshot.text_for_range(range) {
4583 if let Some(byte_pos) = chunk.find(&**end_tag) {
4584 let chars_before_match =
4585 chunk[..byte_pos].chars().count() as u32;
4586 end_tag_offset =
4587 Some(char_position + chars_before_match);
4588 break 'outer;
4589 }
4590 char_position += chunk.chars().count() as u32;
4591 }
4592
4593 if let Some(end_tag_offset) = end_tag_offset {
4594 let cursor_is_before_end_tag = column <= end_tag_offset;
4595 if cursor_is_after_start_tag {
4596 if cursor_is_before_end_tag {
4597 insert_extra_newline = true;
4598 }
4599 let cursor_is_at_start_of_end_tag =
4600 column == end_tag_offset;
4601 if cursor_is_at_start_of_end_tag {
4602 indent_on_extra_newline.len = *len;
4603 }
4604 }
4605 cursor_is_before_end_tag
4606 } else {
4607 true
4608 }
4609 };
4610
4611 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4612 && cursor_is_before_end_tag_if_exists
4613 {
4614 if cursor_is_after_start_tag {
4615 indent_on_newline.len = *len;
4616 }
4617 Some(delimiter.clone())
4618 } else {
4619 None
4620 }
4621 });
4622
4623 (
4624 comment_delimiter,
4625 doc_delimiter,
4626 insert_extra_newline,
4627 indent_on_newline,
4628 indent_on_extra_newline,
4629 )
4630 } else {
4631 (
4632 None,
4633 None,
4634 false,
4635 IndentSize::default(),
4636 IndentSize::default(),
4637 )
4638 };
4639
4640 let prevent_auto_indent = doc_delimiter.is_some();
4641 let delimiter = comment_delimiter.or(doc_delimiter);
4642
4643 let capacity_for_delimiter =
4644 delimiter.as_deref().map(str::len).unwrap_or_default();
4645 let mut new_text = String::with_capacity(
4646 1 + capacity_for_delimiter
4647 + existing_indent.len as usize
4648 + indent_on_newline.len as usize
4649 + indent_on_extra_newline.len as usize,
4650 );
4651 new_text.push('\n');
4652 new_text.extend(existing_indent.chars());
4653 new_text.extend(indent_on_newline.chars());
4654
4655 if let Some(delimiter) = &delimiter {
4656 new_text.push_str(delimiter);
4657 }
4658
4659 if insert_extra_newline {
4660 new_text.push('\n');
4661 new_text.extend(existing_indent.chars());
4662 new_text.extend(indent_on_extra_newline.chars());
4663 }
4664
4665 let anchor = buffer.anchor_after(end);
4666 let new_selection = selection.map(|_| anchor);
4667 (
4668 ((start..end, new_text), prevent_auto_indent),
4669 (insert_extra_newline, new_selection),
4670 )
4671 })
4672 .unzip()
4673 };
4674
4675 let mut auto_indent_edits = Vec::new();
4676 let mut edits = Vec::new();
4677 for (edit, prevent_auto_indent) in edits_with_flags {
4678 if prevent_auto_indent {
4679 edits.push(edit);
4680 } else {
4681 auto_indent_edits.push(edit);
4682 }
4683 }
4684 if !edits.is_empty() {
4685 this.edit(edits, cx);
4686 }
4687 if !auto_indent_edits.is_empty() {
4688 this.edit_with_autoindent(auto_indent_edits, cx);
4689 }
4690
4691 let buffer = this.buffer.read(cx).snapshot(cx);
4692 let new_selections = selection_info
4693 .into_iter()
4694 .map(|(extra_newline_inserted, new_selection)| {
4695 let mut cursor = new_selection.end.to_point(&buffer);
4696 if extra_newline_inserted {
4697 cursor.row -= 1;
4698 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4699 }
4700 new_selection.map(|_| cursor)
4701 })
4702 .collect();
4703
4704 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4705 this.refresh_edit_prediction(true, false, window, cx);
4706 });
4707 }
4708
4709 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4711
4712 let buffer = self.buffer.read(cx);
4713 let snapshot = buffer.snapshot(cx);
4714
4715 let mut edits = Vec::new();
4716 let mut rows = Vec::new();
4717
4718 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4719 let cursor = selection.head();
4720 let row = cursor.row;
4721
4722 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4723
4724 let newline = "\n".to_string();
4725 edits.push((start_of_line..start_of_line, newline));
4726
4727 rows.push(row + rows_inserted as u32);
4728 }
4729
4730 self.transact(window, cx, |editor, window, cx| {
4731 editor.edit(edits, cx);
4732
4733 editor.change_selections(Default::default(), window, cx, |s| {
4734 let mut index = 0;
4735 s.move_cursors_with(|map, _, _| {
4736 let row = rows[index];
4737 index += 1;
4738
4739 let point = Point::new(row, 0);
4740 let boundary = map.next_line_boundary(point).1;
4741 let clipped = map.clip_point(boundary, Bias::Left);
4742
4743 (clipped, SelectionGoal::None)
4744 });
4745 });
4746
4747 let mut indent_edits = Vec::new();
4748 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4749 for row in rows {
4750 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4751 for (row, indent) in indents {
4752 if indent.len == 0 {
4753 continue;
4754 }
4755
4756 let text = match indent.kind {
4757 IndentKind::Space => " ".repeat(indent.len as usize),
4758 IndentKind::Tab => "\t".repeat(indent.len as usize),
4759 };
4760 let point = Point::new(row.0, 0);
4761 indent_edits.push((point..point, text));
4762 }
4763 }
4764 editor.edit(indent_edits, cx);
4765 });
4766 }
4767
4768 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4770
4771 let buffer = self.buffer.read(cx);
4772 let snapshot = buffer.snapshot(cx);
4773
4774 let mut edits = Vec::new();
4775 let mut rows = Vec::new();
4776 let mut rows_inserted = 0;
4777
4778 for selection in self.selections.all_adjusted(cx) {
4779 let cursor = selection.head();
4780 let row = cursor.row;
4781
4782 let point = Point::new(row + 1, 0);
4783 let start_of_line = snapshot.clip_point(point, Bias::Left);
4784
4785 let newline = "\n".to_string();
4786 edits.push((start_of_line..start_of_line, newline));
4787
4788 rows_inserted += 1;
4789 rows.push(row + rows_inserted);
4790 }
4791
4792 self.transact(window, cx, |editor, window, cx| {
4793 editor.edit(edits, cx);
4794
4795 editor.change_selections(Default::default(), window, cx, |s| {
4796 let mut index = 0;
4797 s.move_cursors_with(|map, _, _| {
4798 let row = rows[index];
4799 index += 1;
4800
4801 let point = Point::new(row, 0);
4802 let boundary = map.next_line_boundary(point).1;
4803 let clipped = map.clip_point(boundary, Bias::Left);
4804
4805 (clipped, SelectionGoal::None)
4806 });
4807 });
4808
4809 let mut indent_edits = Vec::new();
4810 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4811 for row in rows {
4812 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4813 for (row, indent) in indents {
4814 if indent.len == 0 {
4815 continue;
4816 }
4817
4818 let text = match indent.kind {
4819 IndentKind::Space => " ".repeat(indent.len as usize),
4820 IndentKind::Tab => "\t".repeat(indent.len as usize),
4821 };
4822 let point = Point::new(row.0, 0);
4823 indent_edits.push((point..point, text));
4824 }
4825 }
4826 editor.edit(indent_edits, cx);
4827 });
4828 }
4829
4830 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4831 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4832 original_indent_columns: Vec::new(),
4833 });
4834 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4835 }
4836
4837 fn insert_with_autoindent_mode(
4838 &mut self,
4839 text: &str,
4840 autoindent_mode: Option<AutoindentMode>,
4841 window: &mut Window,
4842 cx: &mut Context<Self>,
4843 ) {
4844 if self.read_only(cx) {
4845 return;
4846 }
4847
4848 let text: Arc<str> = text.into();
4849 self.transact(window, cx, |this, window, cx| {
4850 let old_selections = this.selections.all_adjusted(cx);
4851 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4852 let anchors = {
4853 let snapshot = buffer.read(cx);
4854 old_selections
4855 .iter()
4856 .map(|s| {
4857 let anchor = snapshot.anchor_after(s.head());
4858 s.map(|_| anchor)
4859 })
4860 .collect::<Vec<_>>()
4861 };
4862 buffer.edit(
4863 old_selections
4864 .iter()
4865 .map(|s| (s.start..s.end, text.clone())),
4866 autoindent_mode,
4867 cx,
4868 );
4869 anchors
4870 });
4871
4872 this.change_selections(Default::default(), window, cx, |s| {
4873 s.select_anchors(selection_anchors);
4874 });
4875
4876 cx.notify();
4877 });
4878 }
4879
4880 fn trigger_completion_on_input(
4881 &mut self,
4882 text: &str,
4883 trigger_in_words: bool,
4884 window: &mut Window,
4885 cx: &mut Context<Self>,
4886 ) {
4887 let completions_source = self
4888 .context_menu
4889 .borrow()
4890 .as_ref()
4891 .and_then(|menu| match menu {
4892 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4893 CodeContextMenu::CodeActions(_) => None,
4894 });
4895
4896 match completions_source {
4897 Some(CompletionsMenuSource::Words { .. }) => {
4898 self.open_or_update_completions_menu(
4899 Some(CompletionsMenuSource::Words {
4900 ignore_threshold: false,
4901 }),
4902 None,
4903 window,
4904 cx,
4905 );
4906 }
4907 Some(CompletionsMenuSource::Normal)
4908 | Some(CompletionsMenuSource::SnippetChoices)
4909 | None
4910 if self.is_completion_trigger(
4911 text,
4912 trigger_in_words,
4913 completions_source.is_some(),
4914 cx,
4915 ) =>
4916 {
4917 self.show_completions(
4918 &ShowCompletions {
4919 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4920 },
4921 window,
4922 cx,
4923 )
4924 }
4925 _ => {
4926 self.hide_context_menu(window, cx);
4927 }
4928 }
4929 }
4930
4931 fn is_completion_trigger(
4932 &self,
4933 text: &str,
4934 trigger_in_words: bool,
4935 menu_is_open: bool,
4936 cx: &mut Context<Self>,
4937 ) -> bool {
4938 let position = self.selections.newest_anchor().head();
4939 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4940 return false;
4941 };
4942
4943 if let Some(completion_provider) = &self.completion_provider {
4944 completion_provider.is_completion_trigger(
4945 &buffer,
4946 position.text_anchor,
4947 text,
4948 trigger_in_words,
4949 menu_is_open,
4950 cx,
4951 )
4952 } else {
4953 false
4954 }
4955 }
4956
4957 /// If any empty selections is touching the start of its innermost containing autoclose
4958 /// region, expand it to select the brackets.
4959 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4960 let selections = self.selections.all::<usize>(cx);
4961 let buffer = self.buffer.read(cx).read(cx);
4962 let new_selections = self
4963 .selections_with_autoclose_regions(selections, &buffer)
4964 .map(|(mut selection, region)| {
4965 if !selection.is_empty() {
4966 return selection;
4967 }
4968
4969 if let Some(region) = region {
4970 let mut range = region.range.to_offset(&buffer);
4971 if selection.start == range.start && range.start >= region.pair.start.len() {
4972 range.start -= region.pair.start.len();
4973 if buffer.contains_str_at(range.start, ®ion.pair.start)
4974 && buffer.contains_str_at(range.end, ®ion.pair.end)
4975 {
4976 range.end += region.pair.end.len();
4977 selection.start = range.start;
4978 selection.end = range.end;
4979
4980 return selection;
4981 }
4982 }
4983 }
4984
4985 let always_treat_brackets_as_autoclosed = buffer
4986 .language_settings_at(selection.start, cx)
4987 .always_treat_brackets_as_autoclosed;
4988
4989 if !always_treat_brackets_as_autoclosed {
4990 return selection;
4991 }
4992
4993 if let Some(scope) = buffer.language_scope_at(selection.start) {
4994 for (pair, enabled) in scope.brackets() {
4995 if !enabled || !pair.close {
4996 continue;
4997 }
4998
4999 if buffer.contains_str_at(selection.start, &pair.end) {
5000 let pair_start_len = pair.start.len();
5001 if buffer.contains_str_at(
5002 selection.start.saturating_sub(pair_start_len),
5003 &pair.start,
5004 ) {
5005 selection.start -= pair_start_len;
5006 selection.end += pair.end.len();
5007
5008 return selection;
5009 }
5010 }
5011 }
5012 }
5013
5014 selection
5015 })
5016 .collect();
5017
5018 drop(buffer);
5019 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5020 selections.select(new_selections)
5021 });
5022 }
5023
5024 /// Iterate the given selections, and for each one, find the smallest surrounding
5025 /// autoclose region. This uses the ordering of the selections and the autoclose
5026 /// regions to avoid repeated comparisons.
5027 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5028 &'a self,
5029 selections: impl IntoIterator<Item = Selection<D>>,
5030 buffer: &'a MultiBufferSnapshot,
5031 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5032 let mut i = 0;
5033 let mut regions = self.autoclose_regions.as_slice();
5034 selections.into_iter().map(move |selection| {
5035 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5036
5037 let mut enclosing = None;
5038 while let Some(pair_state) = regions.get(i) {
5039 if pair_state.range.end.to_offset(buffer) < range.start {
5040 regions = ®ions[i + 1..];
5041 i = 0;
5042 } else if pair_state.range.start.to_offset(buffer) > range.end {
5043 break;
5044 } else {
5045 if pair_state.selection_id == selection.id {
5046 enclosing = Some(pair_state);
5047 }
5048 i += 1;
5049 }
5050 }
5051
5052 (selection, enclosing)
5053 })
5054 }
5055
5056 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5057 fn invalidate_autoclose_regions(
5058 &mut self,
5059 mut selections: &[Selection<Anchor>],
5060 buffer: &MultiBufferSnapshot,
5061 ) {
5062 self.autoclose_regions.retain(|state| {
5063 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5064 return false;
5065 }
5066
5067 let mut i = 0;
5068 while let Some(selection) = selections.get(i) {
5069 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5070 selections = &selections[1..];
5071 continue;
5072 }
5073 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5074 break;
5075 }
5076 if selection.id == state.selection_id {
5077 return true;
5078 } else {
5079 i += 1;
5080 }
5081 }
5082 false
5083 });
5084 }
5085
5086 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5087 let offset = position.to_offset(buffer);
5088 let (word_range, kind) = buffer.surrounding_word(offset, true);
5089 if offset > word_range.start && kind == Some(CharKind::Word) {
5090 Some(
5091 buffer
5092 .text_for_range(word_range.start..offset)
5093 .collect::<String>(),
5094 )
5095 } else {
5096 None
5097 }
5098 }
5099
5100 pub fn toggle_inline_values(
5101 &mut self,
5102 _: &ToggleInlineValues,
5103 _: &mut Window,
5104 cx: &mut Context<Self>,
5105 ) {
5106 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5107
5108 self.refresh_inline_values(cx);
5109 }
5110
5111 pub fn toggle_inlay_hints(
5112 &mut self,
5113 _: &ToggleInlayHints,
5114 _: &mut Window,
5115 cx: &mut Context<Self>,
5116 ) {
5117 self.refresh_inlay_hints(
5118 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5119 cx,
5120 );
5121 }
5122
5123 pub fn inlay_hints_enabled(&self) -> bool {
5124 self.inlay_hint_cache.enabled
5125 }
5126
5127 pub fn inline_values_enabled(&self) -> bool {
5128 self.inline_value_cache.enabled
5129 }
5130
5131 #[cfg(any(test, feature = "test-support"))]
5132 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5133 self.display_map
5134 .read(cx)
5135 .current_inlays()
5136 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5137 .cloned()
5138 .collect()
5139 }
5140
5141 #[cfg(any(test, feature = "test-support"))]
5142 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5143 self.display_map
5144 .read(cx)
5145 .current_inlays()
5146 .cloned()
5147 .collect()
5148 }
5149
5150 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5151 if self.semantics_provider.is_none() || !self.mode.is_full() {
5152 return;
5153 }
5154
5155 let reason_description = reason.description();
5156 let ignore_debounce = matches!(
5157 reason,
5158 InlayHintRefreshReason::SettingsChange(_)
5159 | InlayHintRefreshReason::Toggle(_)
5160 | InlayHintRefreshReason::ExcerptsRemoved(_)
5161 | InlayHintRefreshReason::ModifiersChanged(_)
5162 );
5163 let (invalidate_cache, required_languages) = match reason {
5164 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5165 match self.inlay_hint_cache.modifiers_override(enabled) {
5166 Some(enabled) => {
5167 if enabled {
5168 (InvalidationStrategy::RefreshRequested, None)
5169 } else {
5170 self.splice_inlays(
5171 &self
5172 .visible_inlay_hints(cx)
5173 .iter()
5174 .map(|inlay| inlay.id)
5175 .collect::<Vec<InlayId>>(),
5176 Vec::new(),
5177 cx,
5178 );
5179 return;
5180 }
5181 }
5182 None => return,
5183 }
5184 }
5185 InlayHintRefreshReason::Toggle(enabled) => {
5186 if self.inlay_hint_cache.toggle(enabled) {
5187 if enabled {
5188 (InvalidationStrategy::RefreshRequested, None)
5189 } else {
5190 self.splice_inlays(
5191 &self
5192 .visible_inlay_hints(cx)
5193 .iter()
5194 .map(|inlay| inlay.id)
5195 .collect::<Vec<InlayId>>(),
5196 Vec::new(),
5197 cx,
5198 );
5199 return;
5200 }
5201 } else {
5202 return;
5203 }
5204 }
5205 InlayHintRefreshReason::SettingsChange(new_settings) => {
5206 match self.inlay_hint_cache.update_settings(
5207 &self.buffer,
5208 new_settings,
5209 self.visible_inlay_hints(cx),
5210 cx,
5211 ) {
5212 ControlFlow::Break(Some(InlaySplice {
5213 to_remove,
5214 to_insert,
5215 })) => {
5216 self.splice_inlays(&to_remove, to_insert, cx);
5217 return;
5218 }
5219 ControlFlow::Break(None) => return,
5220 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5221 }
5222 }
5223 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5224 if let Some(InlaySplice {
5225 to_remove,
5226 to_insert,
5227 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5228 {
5229 self.splice_inlays(&to_remove, to_insert, cx);
5230 }
5231 self.display_map.update(cx, |display_map, _| {
5232 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5233 });
5234 return;
5235 }
5236 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5237 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5238 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5239 }
5240 InlayHintRefreshReason::RefreshRequested => {
5241 (InvalidationStrategy::RefreshRequested, None)
5242 }
5243 };
5244
5245 if let Some(InlaySplice {
5246 to_remove,
5247 to_insert,
5248 }) = self.inlay_hint_cache.spawn_hint_refresh(
5249 reason_description,
5250 self.visible_excerpts(required_languages.as_ref(), cx),
5251 invalidate_cache,
5252 ignore_debounce,
5253 cx,
5254 ) {
5255 self.splice_inlays(&to_remove, to_insert, cx);
5256 }
5257 }
5258
5259 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5260 self.display_map
5261 .read(cx)
5262 .current_inlays()
5263 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5264 .cloned()
5265 .collect()
5266 }
5267
5268 pub fn visible_excerpts(
5269 &self,
5270 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5271 cx: &mut Context<Editor>,
5272 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5273 let Some(project) = self.project() else {
5274 return HashMap::default();
5275 };
5276 let project = project.read(cx);
5277 let multi_buffer = self.buffer().read(cx);
5278 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5279 let multi_buffer_visible_start = self
5280 .scroll_manager
5281 .anchor()
5282 .anchor
5283 .to_point(&multi_buffer_snapshot);
5284 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5285 multi_buffer_visible_start
5286 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5287 Bias::Left,
5288 );
5289 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5290 multi_buffer_snapshot
5291 .range_to_buffer_ranges(multi_buffer_visible_range)
5292 .into_iter()
5293 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5294 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5295 let buffer_file = project::File::from_dyn(buffer.file())?;
5296 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5297 let worktree_entry = buffer_worktree
5298 .read(cx)
5299 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5300 if worktree_entry.is_ignored {
5301 return None;
5302 }
5303
5304 let language = buffer.language()?;
5305 if let Some(restrict_to_languages) = restrict_to_languages
5306 && !restrict_to_languages.contains(language)
5307 {
5308 return None;
5309 }
5310 Some((
5311 excerpt_id,
5312 (
5313 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5314 buffer.version().clone(),
5315 excerpt_visible_range,
5316 ),
5317 ))
5318 })
5319 .collect()
5320 }
5321
5322 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5323 TextLayoutDetails {
5324 text_system: window.text_system().clone(),
5325 editor_style: self.style.clone().unwrap(),
5326 rem_size: window.rem_size(),
5327 scroll_anchor: self.scroll_manager.anchor(),
5328 visible_rows: self.visible_line_count(),
5329 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5330 }
5331 }
5332
5333 pub fn splice_inlays(
5334 &self,
5335 to_remove: &[InlayId],
5336 to_insert: Vec<Inlay>,
5337 cx: &mut Context<Self>,
5338 ) {
5339 self.display_map.update(cx, |display_map, cx| {
5340 display_map.splice_inlays(to_remove, to_insert, cx)
5341 });
5342 cx.notify();
5343 }
5344
5345 fn trigger_on_type_formatting(
5346 &self,
5347 input: String,
5348 window: &mut Window,
5349 cx: &mut Context<Self>,
5350 ) -> Option<Task<Result<()>>> {
5351 if input.len() != 1 {
5352 return None;
5353 }
5354
5355 let project = self.project()?;
5356 let position = self.selections.newest_anchor().head();
5357 let (buffer, buffer_position) = self
5358 .buffer
5359 .read(cx)
5360 .text_anchor_for_position(position, cx)?;
5361
5362 let settings = language_settings::language_settings(
5363 buffer
5364 .read(cx)
5365 .language_at(buffer_position)
5366 .map(|l| l.name()),
5367 buffer.read(cx).file(),
5368 cx,
5369 );
5370 if !settings.use_on_type_format {
5371 return None;
5372 }
5373
5374 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5375 // hence we do LSP request & edit on host side only — add formats to host's history.
5376 let push_to_lsp_host_history = true;
5377 // If this is not the host, append its history with new edits.
5378 let push_to_client_history = project.read(cx).is_via_collab();
5379
5380 let on_type_formatting = project.update(cx, |project, cx| {
5381 project.on_type_format(
5382 buffer.clone(),
5383 buffer_position,
5384 input,
5385 push_to_lsp_host_history,
5386 cx,
5387 )
5388 });
5389 Some(cx.spawn_in(window, async move |editor, cx| {
5390 if let Some(transaction) = on_type_formatting.await? {
5391 if push_to_client_history {
5392 buffer
5393 .update(cx, |buffer, _| {
5394 buffer.push_transaction(transaction, Instant::now());
5395 buffer.finalize_last_transaction();
5396 })
5397 .ok();
5398 }
5399 editor.update(cx, |editor, cx| {
5400 editor.refresh_document_highlights(cx);
5401 })?;
5402 }
5403 Ok(())
5404 }))
5405 }
5406
5407 pub fn show_word_completions(
5408 &mut self,
5409 _: &ShowWordCompletions,
5410 window: &mut Window,
5411 cx: &mut Context<Self>,
5412 ) {
5413 self.open_or_update_completions_menu(
5414 Some(CompletionsMenuSource::Words {
5415 ignore_threshold: true,
5416 }),
5417 None,
5418 window,
5419 cx,
5420 );
5421 }
5422
5423 pub fn show_completions(
5424 &mut self,
5425 options: &ShowCompletions,
5426 window: &mut Window,
5427 cx: &mut Context<Self>,
5428 ) {
5429 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5430 }
5431
5432 fn open_or_update_completions_menu(
5433 &mut self,
5434 requested_source: Option<CompletionsMenuSource>,
5435 trigger: Option<&str>,
5436 window: &mut Window,
5437 cx: &mut Context<Self>,
5438 ) {
5439 if self.pending_rename.is_some() {
5440 return;
5441 }
5442
5443 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5444
5445 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5446 // inserted and selected. To handle that case, the start of the selection is used so that
5447 // the menu starts with all choices.
5448 let position = self
5449 .selections
5450 .newest_anchor()
5451 .start
5452 .bias_right(&multibuffer_snapshot);
5453 if position.diff_base_anchor.is_some() {
5454 return;
5455 }
5456 let (buffer, buffer_position) =
5457 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5458 output
5459 } else {
5460 return;
5461 };
5462 let buffer_snapshot = buffer.read(cx).snapshot();
5463
5464 let query: Option<Arc<String>> =
5465 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5466
5467 drop(multibuffer_snapshot);
5468
5469 let mut ignore_word_threshold = false;
5470 let provider = match requested_source {
5471 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5472 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5473 ignore_word_threshold = ignore_threshold;
5474 None
5475 }
5476 Some(CompletionsMenuSource::SnippetChoices) => {
5477 log::error!("bug: SnippetChoices requested_source is not handled");
5478 None
5479 }
5480 };
5481
5482 let sort_completions = provider
5483 .as_ref()
5484 .is_some_and(|provider| provider.sort_completions());
5485
5486 let filter_completions = provider
5487 .as_ref()
5488 .is_none_or(|provider| provider.filter_completions());
5489
5490 let trigger_kind = match trigger {
5491 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5492 CompletionTriggerKind::TRIGGER_CHARACTER
5493 }
5494 _ => CompletionTriggerKind::INVOKED,
5495 };
5496 let completion_context = CompletionContext {
5497 trigger_character: trigger.and_then(|trigger| {
5498 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5499 Some(String::from(trigger))
5500 } else {
5501 None
5502 }
5503 }),
5504 trigger_kind,
5505 };
5506
5507 // Hide the current completions menu when a trigger char is typed. Without this, cached
5508 // completions from before the trigger char may be reused (#32774). Snippet choices could
5509 // involve trigger chars, so this is skipped in that case.
5510 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5511 {
5512 let menu_is_open = matches!(
5513 self.context_menu.borrow().as_ref(),
5514 Some(CodeContextMenu::Completions(_))
5515 );
5516 if menu_is_open {
5517 self.hide_context_menu(window, cx);
5518 }
5519 }
5520
5521 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5522 if filter_completions {
5523 menu.filter(query.clone(), provider.clone(), window, cx);
5524 }
5525 // When `is_incomplete` is false, no need to re-query completions when the current query
5526 // is a suffix of the initial query.
5527 if !menu.is_incomplete {
5528 // If the new query is a suffix of the old query (typing more characters) and
5529 // the previous result was complete, the existing completions can be filtered.
5530 //
5531 // Note that this is always true for snippet completions.
5532 let query_matches = match (&menu.initial_query, &query) {
5533 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5534 (None, _) => true,
5535 _ => false,
5536 };
5537 if query_matches {
5538 let position_matches = if menu.initial_position == position {
5539 true
5540 } else {
5541 let snapshot = self.buffer.read(cx).read(cx);
5542 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5543 };
5544 if position_matches {
5545 return;
5546 }
5547 }
5548 }
5549 };
5550
5551 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5552 buffer_snapshot.surrounding_word(buffer_position, false)
5553 {
5554 let word_to_exclude = buffer_snapshot
5555 .text_for_range(word_range.clone())
5556 .collect::<String>();
5557 (
5558 buffer_snapshot.anchor_before(word_range.start)
5559 ..buffer_snapshot.anchor_after(buffer_position),
5560 Some(word_to_exclude),
5561 )
5562 } else {
5563 (buffer_position..buffer_position, None)
5564 };
5565
5566 let language = buffer_snapshot
5567 .language_at(buffer_position)
5568 .map(|language| language.name());
5569
5570 let completion_settings =
5571 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5572
5573 let show_completion_documentation = buffer_snapshot
5574 .settings_at(buffer_position, cx)
5575 .show_completion_documentation;
5576
5577 // The document can be large, so stay in reasonable bounds when searching for words,
5578 // otherwise completion pop-up might be slow to appear.
5579 const WORD_LOOKUP_ROWS: u32 = 5_000;
5580 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5581 let min_word_search = buffer_snapshot.clip_point(
5582 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5583 Bias::Left,
5584 );
5585 let max_word_search = buffer_snapshot.clip_point(
5586 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5587 Bias::Right,
5588 );
5589 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5590 ..buffer_snapshot.point_to_offset(max_word_search);
5591
5592 let skip_digits = query
5593 .as_ref()
5594 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5595
5596 let omit_word_completions = !self.word_completions_enabled
5597 || (!ignore_word_threshold
5598 && match &query {
5599 Some(query) => query.chars().count() < completion_settings.words_min_length,
5600 None => completion_settings.words_min_length != 0,
5601 });
5602
5603 let (mut words, provider_responses) = match &provider {
5604 Some(provider) => {
5605 let provider_responses = provider.completions(
5606 position.excerpt_id,
5607 &buffer,
5608 buffer_position,
5609 completion_context,
5610 window,
5611 cx,
5612 );
5613
5614 let words = match (omit_word_completions, completion_settings.words) {
5615 (true, _) | (_, WordsCompletionMode::Disabled) => {
5616 Task::ready(BTreeMap::default())
5617 }
5618 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5619 .background_spawn(async move {
5620 buffer_snapshot.words_in_range(WordsQuery {
5621 fuzzy_contents: None,
5622 range: word_search_range,
5623 skip_digits,
5624 })
5625 }),
5626 };
5627
5628 (words, provider_responses)
5629 }
5630 None => {
5631 let words = if omit_word_completions {
5632 Task::ready(BTreeMap::default())
5633 } else {
5634 cx.background_spawn(async move {
5635 buffer_snapshot.words_in_range(WordsQuery {
5636 fuzzy_contents: None,
5637 range: word_search_range,
5638 skip_digits,
5639 })
5640 })
5641 };
5642 (words, Task::ready(Ok(Vec::new())))
5643 }
5644 };
5645
5646 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5647
5648 let id = post_inc(&mut self.next_completion_id);
5649 let task = cx.spawn_in(window, async move |editor, cx| {
5650 let Ok(()) = editor.update(cx, |this, _| {
5651 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5652 }) else {
5653 return;
5654 };
5655
5656 // TODO: Ideally completions from different sources would be selectively re-queried, so
5657 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5658 let mut completions = Vec::new();
5659 let mut is_incomplete = false;
5660 let mut display_options: Option<CompletionDisplayOptions> = None;
5661 if let Some(provider_responses) = provider_responses.await.log_err()
5662 && !provider_responses.is_empty()
5663 {
5664 for response in provider_responses {
5665 completions.extend(response.completions);
5666 is_incomplete = is_incomplete || response.is_incomplete;
5667 match display_options.as_mut() {
5668 None => {
5669 display_options = Some(response.display_options);
5670 }
5671 Some(options) => options.merge(&response.display_options),
5672 }
5673 }
5674 if completion_settings.words == WordsCompletionMode::Fallback {
5675 words = Task::ready(BTreeMap::default());
5676 }
5677 }
5678 let display_options = display_options.unwrap_or_default();
5679
5680 let mut words = words.await;
5681 if let Some(word_to_exclude) = &word_to_exclude {
5682 words.remove(word_to_exclude);
5683 }
5684 for lsp_completion in &completions {
5685 words.remove(&lsp_completion.new_text);
5686 }
5687 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5688 replace_range: word_replace_range.clone(),
5689 new_text: word.clone(),
5690 label: CodeLabel::plain(word, None),
5691 icon_path: None,
5692 documentation: None,
5693 source: CompletionSource::BufferWord {
5694 word_range,
5695 resolved: false,
5696 },
5697 insert_text_mode: Some(InsertTextMode::AS_IS),
5698 confirm: None,
5699 }));
5700
5701 let menu = if completions.is_empty() {
5702 None
5703 } else {
5704 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5705 let languages = editor
5706 .workspace
5707 .as_ref()
5708 .and_then(|(workspace, _)| workspace.upgrade())
5709 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5710 let menu = CompletionsMenu::new(
5711 id,
5712 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5713 sort_completions,
5714 show_completion_documentation,
5715 position,
5716 query.clone(),
5717 is_incomplete,
5718 buffer.clone(),
5719 completions.into(),
5720 display_options,
5721 snippet_sort_order,
5722 languages,
5723 language,
5724 cx,
5725 );
5726
5727 let query = if filter_completions { query } else { None };
5728 let matches_task = if let Some(query) = query {
5729 menu.do_async_filtering(query, cx)
5730 } else {
5731 Task::ready(menu.unfiltered_matches())
5732 };
5733 (menu, matches_task)
5734 }) else {
5735 return;
5736 };
5737
5738 let matches = matches_task.await;
5739
5740 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5741 // Newer menu already set, so exit.
5742 if let Some(CodeContextMenu::Completions(prev_menu)) =
5743 editor.context_menu.borrow().as_ref()
5744 && prev_menu.id > id
5745 {
5746 return;
5747 };
5748
5749 // Only valid to take prev_menu because it the new menu is immediately set
5750 // below, or the menu is hidden.
5751 if let Some(CodeContextMenu::Completions(prev_menu)) =
5752 editor.context_menu.borrow_mut().take()
5753 {
5754 let position_matches =
5755 if prev_menu.initial_position == menu.initial_position {
5756 true
5757 } else {
5758 let snapshot = editor.buffer.read(cx).read(cx);
5759 prev_menu.initial_position.to_offset(&snapshot)
5760 == menu.initial_position.to_offset(&snapshot)
5761 };
5762 if position_matches {
5763 // Preserve markdown cache before `set_filter_results` because it will
5764 // try to populate the documentation cache.
5765 menu.preserve_markdown_cache(prev_menu);
5766 }
5767 };
5768
5769 menu.set_filter_results(matches, provider, window, cx);
5770 }) else {
5771 return;
5772 };
5773
5774 menu.visible().then_some(menu)
5775 };
5776
5777 editor
5778 .update_in(cx, |editor, window, cx| {
5779 if editor.focus_handle.is_focused(window)
5780 && let Some(menu) = menu
5781 {
5782 *editor.context_menu.borrow_mut() =
5783 Some(CodeContextMenu::Completions(menu));
5784
5785 crate::hover_popover::hide_hover(editor, cx);
5786 if editor.show_edit_predictions_in_menu() {
5787 editor.update_visible_edit_prediction(window, cx);
5788 } else {
5789 editor.discard_edit_prediction(false, cx);
5790 }
5791
5792 cx.notify();
5793 return;
5794 }
5795
5796 if editor.completion_tasks.len() <= 1 {
5797 // If there are no more completion tasks and the last menu was empty, we should hide it.
5798 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5799 // If it was already hidden and we don't show edit predictions in the menu,
5800 // we should also show the edit prediction when available.
5801 if was_hidden && editor.show_edit_predictions_in_menu() {
5802 editor.update_visible_edit_prediction(window, cx);
5803 }
5804 }
5805 })
5806 .ok();
5807 });
5808
5809 self.completion_tasks.push((id, task));
5810 }
5811
5812 #[cfg(feature = "test-support")]
5813 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5814 let menu = self.context_menu.borrow();
5815 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5816 let completions = menu.completions.borrow();
5817 Some(completions.to_vec())
5818 } else {
5819 None
5820 }
5821 }
5822
5823 pub fn with_completions_menu_matching_id<R>(
5824 &self,
5825 id: CompletionId,
5826 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5827 ) -> R {
5828 let mut context_menu = self.context_menu.borrow_mut();
5829 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5830 return f(None);
5831 };
5832 if completions_menu.id != id {
5833 return f(None);
5834 }
5835 f(Some(completions_menu))
5836 }
5837
5838 pub fn confirm_completion(
5839 &mut self,
5840 action: &ConfirmCompletion,
5841 window: &mut Window,
5842 cx: &mut Context<Self>,
5843 ) -> Option<Task<Result<()>>> {
5844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5845 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5846 }
5847
5848 pub fn confirm_completion_insert(
5849 &mut self,
5850 _: &ConfirmCompletionInsert,
5851 window: &mut Window,
5852 cx: &mut Context<Self>,
5853 ) -> Option<Task<Result<()>>> {
5854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5855 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5856 }
5857
5858 pub fn confirm_completion_replace(
5859 &mut self,
5860 _: &ConfirmCompletionReplace,
5861 window: &mut Window,
5862 cx: &mut Context<Self>,
5863 ) -> Option<Task<Result<()>>> {
5864 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5865 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5866 }
5867
5868 pub fn compose_completion(
5869 &mut self,
5870 action: &ComposeCompletion,
5871 window: &mut Window,
5872 cx: &mut Context<Self>,
5873 ) -> Option<Task<Result<()>>> {
5874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5875 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5876 }
5877
5878 fn do_completion(
5879 &mut self,
5880 item_ix: Option<usize>,
5881 intent: CompletionIntent,
5882 window: &mut Window,
5883 cx: &mut Context<Editor>,
5884 ) -> Option<Task<Result<()>>> {
5885 use language::ToOffset as _;
5886
5887 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5888 else {
5889 return None;
5890 };
5891
5892 let candidate_id = {
5893 let entries = completions_menu.entries.borrow();
5894 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5895 if self.show_edit_predictions_in_menu() {
5896 self.discard_edit_prediction(true, cx);
5897 }
5898 mat.candidate_id
5899 };
5900
5901 let completion = completions_menu
5902 .completions
5903 .borrow()
5904 .get(candidate_id)?
5905 .clone();
5906 cx.stop_propagation();
5907
5908 let buffer_handle = completions_menu.buffer.clone();
5909
5910 let CompletionEdit {
5911 new_text,
5912 snippet,
5913 replace_range,
5914 } = process_completion_for_edit(
5915 &completion,
5916 intent,
5917 &buffer_handle,
5918 &completions_menu.initial_position.text_anchor,
5919 cx,
5920 );
5921
5922 let buffer = buffer_handle.read(cx);
5923 let snapshot = self.buffer.read(cx).snapshot(cx);
5924 let newest_anchor = self.selections.newest_anchor();
5925 let replace_range_multibuffer = {
5926 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5927 let multibuffer_anchor = snapshot
5928 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5929 .unwrap()
5930 ..snapshot
5931 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5932 .unwrap();
5933 multibuffer_anchor.start.to_offset(&snapshot)
5934 ..multibuffer_anchor.end.to_offset(&snapshot)
5935 };
5936 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5937 return None;
5938 }
5939
5940 let old_text = buffer
5941 .text_for_range(replace_range.clone())
5942 .collect::<String>();
5943 let lookbehind = newest_anchor
5944 .start
5945 .text_anchor
5946 .to_offset(buffer)
5947 .saturating_sub(replace_range.start);
5948 let lookahead = replace_range
5949 .end
5950 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5951 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5952 let suffix = &old_text[lookbehind.min(old_text.len())..];
5953
5954 let selections = self.selections.all::<usize>(cx);
5955 let mut ranges = Vec::new();
5956 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5957
5958 for selection in &selections {
5959 let range = if selection.id == newest_anchor.id {
5960 replace_range_multibuffer.clone()
5961 } else {
5962 let mut range = selection.range();
5963
5964 // if prefix is present, don't duplicate it
5965 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5966 range.start = range.start.saturating_sub(lookbehind);
5967
5968 // if suffix is also present, mimic the newest cursor and replace it
5969 if selection.id != newest_anchor.id
5970 && snapshot.contains_str_at(range.end, suffix)
5971 {
5972 range.end += lookahead;
5973 }
5974 }
5975 range
5976 };
5977
5978 ranges.push(range.clone());
5979
5980 if !self.linked_edit_ranges.is_empty() {
5981 let start_anchor = snapshot.anchor_before(range.start);
5982 let end_anchor = snapshot.anchor_after(range.end);
5983 if let Some(ranges) = self
5984 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5985 {
5986 for (buffer, edits) in ranges {
5987 linked_edits
5988 .entry(buffer.clone())
5989 .or_default()
5990 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5991 }
5992 }
5993 }
5994 }
5995
5996 let common_prefix_len = old_text
5997 .chars()
5998 .zip(new_text.chars())
5999 .take_while(|(a, b)| a == b)
6000 .map(|(a, _)| a.len_utf8())
6001 .sum::<usize>();
6002
6003 cx.emit(EditorEvent::InputHandled {
6004 utf16_range_to_replace: None,
6005 text: new_text[common_prefix_len..].into(),
6006 });
6007
6008 self.transact(window, cx, |editor, window, cx| {
6009 if let Some(mut snippet) = snippet {
6010 snippet.text = new_text.to_string();
6011 editor
6012 .insert_snippet(&ranges, snippet, window, cx)
6013 .log_err();
6014 } else {
6015 editor.buffer.update(cx, |multi_buffer, cx| {
6016 let auto_indent = match completion.insert_text_mode {
6017 Some(InsertTextMode::AS_IS) => None,
6018 _ => editor.autoindent_mode.clone(),
6019 };
6020 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6021 multi_buffer.edit(edits, auto_indent, cx);
6022 });
6023 }
6024 for (buffer, edits) in linked_edits {
6025 buffer.update(cx, |buffer, cx| {
6026 let snapshot = buffer.snapshot();
6027 let edits = edits
6028 .into_iter()
6029 .map(|(range, text)| {
6030 use text::ToPoint as TP;
6031 let end_point = TP::to_point(&range.end, &snapshot);
6032 let start_point = TP::to_point(&range.start, &snapshot);
6033 (start_point..end_point, text)
6034 })
6035 .sorted_by_key(|(range, _)| range.start);
6036 buffer.edit(edits, None, cx);
6037 })
6038 }
6039
6040 editor.refresh_edit_prediction(true, false, window, cx);
6041 });
6042 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), &snapshot);
6043
6044 let show_new_completions_on_confirm = completion
6045 .confirm
6046 .as_ref()
6047 .is_some_and(|confirm| confirm(intent, window, cx));
6048 if show_new_completions_on_confirm {
6049 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6050 }
6051
6052 let provider = self.completion_provider.as_ref()?;
6053 drop(completion);
6054 let apply_edits = provider.apply_additional_edits_for_completion(
6055 buffer_handle,
6056 completions_menu.completions.clone(),
6057 candidate_id,
6058 true,
6059 cx,
6060 );
6061
6062 let editor_settings = EditorSettings::get_global(cx);
6063 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6064 // After the code completion is finished, users often want to know what signatures are needed.
6065 // so we should automatically call signature_help
6066 self.show_signature_help(&ShowSignatureHelp, window, cx);
6067 }
6068
6069 Some(cx.foreground_executor().spawn(async move {
6070 apply_edits.await?;
6071 Ok(())
6072 }))
6073 }
6074
6075 pub fn toggle_code_actions(
6076 &mut self,
6077 action: &ToggleCodeActions,
6078 window: &mut Window,
6079 cx: &mut Context<Self>,
6080 ) {
6081 let quick_launch = action.quick_launch;
6082 let mut context_menu = self.context_menu.borrow_mut();
6083 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6084 if code_actions.deployed_from == action.deployed_from {
6085 // Toggle if we're selecting the same one
6086 *context_menu = None;
6087 cx.notify();
6088 return;
6089 } else {
6090 // Otherwise, clear it and start a new one
6091 *context_menu = None;
6092 cx.notify();
6093 }
6094 }
6095 drop(context_menu);
6096 let snapshot = self.snapshot(window, cx);
6097 let deployed_from = action.deployed_from.clone();
6098 let action = action.clone();
6099 self.completion_tasks.clear();
6100 self.discard_edit_prediction(false, cx);
6101
6102 let multibuffer_point = match &action.deployed_from {
6103 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6104 DisplayPoint::new(*row, 0).to_point(&snapshot)
6105 }
6106 _ => self.selections.newest::<Point>(cx).head(),
6107 };
6108 let Some((buffer, buffer_row)) = snapshot
6109 .buffer_snapshot
6110 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6111 .and_then(|(buffer_snapshot, range)| {
6112 self.buffer()
6113 .read(cx)
6114 .buffer(buffer_snapshot.remote_id())
6115 .map(|buffer| (buffer, range.start.row))
6116 })
6117 else {
6118 return;
6119 };
6120 let buffer_id = buffer.read(cx).remote_id();
6121 let tasks = self
6122 .tasks
6123 .get(&(buffer_id, buffer_row))
6124 .map(|t| Arc::new(t.to_owned()));
6125
6126 if !self.focus_handle.is_focused(window) {
6127 return;
6128 }
6129 let project = self.project.clone();
6130
6131 let code_actions_task = match deployed_from {
6132 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6133 _ => self.code_actions(buffer_row, window, cx),
6134 };
6135
6136 let runnable_task = match deployed_from {
6137 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6138 _ => {
6139 let mut task_context_task = Task::ready(None);
6140 if let Some(tasks) = &tasks
6141 && let Some(project) = project
6142 {
6143 task_context_task =
6144 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6145 }
6146
6147 cx.spawn_in(window, {
6148 let buffer = buffer.clone();
6149 async move |editor, cx| {
6150 let task_context = task_context_task.await;
6151
6152 let resolved_tasks =
6153 tasks
6154 .zip(task_context.clone())
6155 .map(|(tasks, task_context)| ResolvedTasks {
6156 templates: tasks.resolve(&task_context).collect(),
6157 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6158 multibuffer_point.row,
6159 tasks.column,
6160 )),
6161 });
6162 let debug_scenarios = editor
6163 .update(cx, |editor, cx| {
6164 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6165 })?
6166 .await;
6167 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6168 }
6169 })
6170 }
6171 };
6172
6173 cx.spawn_in(window, async move |editor, cx| {
6174 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6175 let code_actions = code_actions_task.await;
6176 let spawn_straight_away = quick_launch
6177 && resolved_tasks
6178 .as_ref()
6179 .is_some_and(|tasks| tasks.templates.len() == 1)
6180 && code_actions
6181 .as_ref()
6182 .is_none_or(|actions| actions.is_empty())
6183 && debug_scenarios.is_empty();
6184
6185 editor.update_in(cx, |editor, window, cx| {
6186 crate::hover_popover::hide_hover(editor, cx);
6187 let actions = CodeActionContents::new(
6188 resolved_tasks,
6189 code_actions,
6190 debug_scenarios,
6191 task_context.unwrap_or_default(),
6192 );
6193
6194 // Don't show the menu if there are no actions available
6195 if actions.is_empty() {
6196 cx.notify();
6197 return Task::ready(Ok(()));
6198 }
6199
6200 *editor.context_menu.borrow_mut() =
6201 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6202 buffer,
6203 actions,
6204 selected_item: Default::default(),
6205 scroll_handle: UniformListScrollHandle::default(),
6206 deployed_from,
6207 }));
6208 cx.notify();
6209 if spawn_straight_away
6210 && let Some(task) = editor.confirm_code_action(
6211 &ConfirmCodeAction { item_ix: Some(0) },
6212 window,
6213 cx,
6214 )
6215 {
6216 return task;
6217 }
6218
6219 Task::ready(Ok(()))
6220 })
6221 })
6222 .detach_and_log_err(cx);
6223 }
6224
6225 fn debug_scenarios(
6226 &mut self,
6227 resolved_tasks: &Option<ResolvedTasks>,
6228 buffer: &Entity<Buffer>,
6229 cx: &mut App,
6230 ) -> Task<Vec<task::DebugScenario>> {
6231 maybe!({
6232 let project = self.project()?;
6233 let dap_store = project.read(cx).dap_store();
6234 let mut scenarios = vec![];
6235 let resolved_tasks = resolved_tasks.as_ref()?;
6236 let buffer = buffer.read(cx);
6237 let language = buffer.language()?;
6238 let file = buffer.file();
6239 let debug_adapter = language_settings(language.name().into(), file, cx)
6240 .debuggers
6241 .first()
6242 .map(SharedString::from)
6243 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6244
6245 dap_store.update(cx, |dap_store, cx| {
6246 for (_, task) in &resolved_tasks.templates {
6247 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6248 task.original_task().clone(),
6249 debug_adapter.clone().into(),
6250 task.display_label().to_owned().into(),
6251 cx,
6252 );
6253 scenarios.push(maybe_scenario);
6254 }
6255 });
6256 Some(cx.background_spawn(async move {
6257 futures::future::join_all(scenarios)
6258 .await
6259 .into_iter()
6260 .flatten()
6261 .collect::<Vec<_>>()
6262 }))
6263 })
6264 .unwrap_or_else(|| Task::ready(vec![]))
6265 }
6266
6267 fn code_actions(
6268 &mut self,
6269 buffer_row: u32,
6270 window: &mut Window,
6271 cx: &mut Context<Self>,
6272 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6273 let mut task = self.code_actions_task.take();
6274 cx.spawn_in(window, async move |editor, cx| {
6275 while let Some(prev_task) = task {
6276 prev_task.await.log_err();
6277 task = editor
6278 .update(cx, |this, _| this.code_actions_task.take())
6279 .ok()?;
6280 }
6281
6282 editor
6283 .update(cx, |editor, cx| {
6284 editor
6285 .available_code_actions
6286 .clone()
6287 .and_then(|(location, code_actions)| {
6288 let snapshot = location.buffer.read(cx).snapshot();
6289 let point_range = location.range.to_point(&snapshot);
6290 let point_range = point_range.start.row..=point_range.end.row;
6291 if point_range.contains(&buffer_row) {
6292 Some(code_actions)
6293 } else {
6294 None
6295 }
6296 })
6297 })
6298 .ok()
6299 .flatten()
6300 })
6301 }
6302
6303 pub fn confirm_code_action(
6304 &mut self,
6305 action: &ConfirmCodeAction,
6306 window: &mut Window,
6307 cx: &mut Context<Self>,
6308 ) -> Option<Task<Result<()>>> {
6309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6310
6311 let actions_menu =
6312 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6313 menu
6314 } else {
6315 return None;
6316 };
6317
6318 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6319 let action = actions_menu.actions.get(action_ix)?;
6320 let title = action.label();
6321 let buffer = actions_menu.buffer;
6322 let workspace = self.workspace()?;
6323
6324 match action {
6325 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6326 workspace.update(cx, |workspace, cx| {
6327 workspace.schedule_resolved_task(
6328 task_source_kind,
6329 resolved_task,
6330 false,
6331 window,
6332 cx,
6333 );
6334
6335 Some(Task::ready(Ok(())))
6336 })
6337 }
6338 CodeActionsItem::CodeAction {
6339 excerpt_id,
6340 action,
6341 provider,
6342 } => {
6343 let apply_code_action =
6344 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6345 let workspace = workspace.downgrade();
6346 Some(cx.spawn_in(window, async move |editor, cx| {
6347 let project_transaction = apply_code_action.await?;
6348 Self::open_project_transaction(
6349 &editor,
6350 workspace,
6351 project_transaction,
6352 title,
6353 cx,
6354 )
6355 .await
6356 }))
6357 }
6358 CodeActionsItem::DebugScenario(scenario) => {
6359 let context = actions_menu.actions.context;
6360
6361 workspace.update(cx, |workspace, cx| {
6362 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6363 workspace.start_debug_session(
6364 scenario,
6365 context,
6366 Some(buffer),
6367 None,
6368 window,
6369 cx,
6370 );
6371 });
6372 Some(Task::ready(Ok(())))
6373 }
6374 }
6375 }
6376
6377 pub async fn open_project_transaction(
6378 editor: &WeakEntity<Editor>,
6379 workspace: WeakEntity<Workspace>,
6380 transaction: ProjectTransaction,
6381 title: String,
6382 cx: &mut AsyncWindowContext,
6383 ) -> Result<()> {
6384 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6385 cx.update(|_, cx| {
6386 entries.sort_unstable_by_key(|(buffer, _)| {
6387 buffer.read(cx).file().map(|f| f.path().clone())
6388 });
6389 })?;
6390
6391 // If the project transaction's edits are all contained within this editor, then
6392 // avoid opening a new editor to display them.
6393
6394 if let Some((buffer, transaction)) = entries.first() {
6395 if entries.len() == 1 {
6396 let excerpt = editor.update(cx, |editor, cx| {
6397 editor
6398 .buffer()
6399 .read(cx)
6400 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6401 })?;
6402 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6403 && excerpted_buffer == *buffer
6404 {
6405 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6406 let excerpt_range = excerpt_range.to_offset(buffer);
6407 buffer
6408 .edited_ranges_for_transaction::<usize>(transaction)
6409 .all(|range| {
6410 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6411 })
6412 })?;
6413
6414 if all_edits_within_excerpt {
6415 return Ok(());
6416 }
6417 }
6418 }
6419 } else {
6420 return Ok(());
6421 }
6422
6423 let mut ranges_to_highlight = Vec::new();
6424 let excerpt_buffer = cx.new(|cx| {
6425 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6426 for (buffer_handle, transaction) in &entries {
6427 let edited_ranges = buffer_handle
6428 .read(cx)
6429 .edited_ranges_for_transaction::<Point>(transaction)
6430 .collect::<Vec<_>>();
6431 let (ranges, _) = multibuffer.set_excerpts_for_path(
6432 PathKey::for_buffer(buffer_handle, cx),
6433 buffer_handle.clone(),
6434 edited_ranges,
6435 multibuffer_context_lines(cx),
6436 cx,
6437 );
6438
6439 ranges_to_highlight.extend(ranges);
6440 }
6441 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6442 multibuffer
6443 })?;
6444
6445 workspace.update_in(cx, |workspace, window, cx| {
6446 let project = workspace.project().clone();
6447 let editor =
6448 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6449 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6450 editor.update(cx, |editor, cx| {
6451 editor.highlight_background::<Self>(
6452 &ranges_to_highlight,
6453 |theme| theme.colors().editor_highlighted_line_background,
6454 cx,
6455 );
6456 });
6457 })?;
6458
6459 Ok(())
6460 }
6461
6462 pub fn clear_code_action_providers(&mut self) {
6463 self.code_action_providers.clear();
6464 self.available_code_actions.take();
6465 }
6466
6467 pub fn add_code_action_provider(
6468 &mut self,
6469 provider: Rc<dyn CodeActionProvider>,
6470 window: &mut Window,
6471 cx: &mut Context<Self>,
6472 ) {
6473 if self
6474 .code_action_providers
6475 .iter()
6476 .any(|existing_provider| existing_provider.id() == provider.id())
6477 {
6478 return;
6479 }
6480
6481 self.code_action_providers.push(provider);
6482 self.refresh_code_actions(window, cx);
6483 }
6484
6485 pub fn remove_code_action_provider(
6486 &mut self,
6487 id: Arc<str>,
6488 window: &mut Window,
6489 cx: &mut Context<Self>,
6490 ) {
6491 self.code_action_providers
6492 .retain(|provider| provider.id() != id);
6493 self.refresh_code_actions(window, cx);
6494 }
6495
6496 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6497 !self.code_action_providers.is_empty()
6498 && EditorSettings::get_global(cx).toolbar.code_actions
6499 }
6500
6501 pub fn has_available_code_actions(&self) -> bool {
6502 self.available_code_actions
6503 .as_ref()
6504 .is_some_and(|(_, actions)| !actions.is_empty())
6505 }
6506
6507 fn render_inline_code_actions(
6508 &self,
6509 icon_size: ui::IconSize,
6510 display_row: DisplayRow,
6511 is_active: bool,
6512 cx: &mut Context<Self>,
6513 ) -> AnyElement {
6514 let show_tooltip = !self.context_menu_visible();
6515 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6516 .icon_size(icon_size)
6517 .shape(ui::IconButtonShape::Square)
6518 .icon_color(ui::Color::Hidden)
6519 .toggle_state(is_active)
6520 .when(show_tooltip, |this| {
6521 this.tooltip({
6522 let focus_handle = self.focus_handle.clone();
6523 move |window, cx| {
6524 Tooltip::for_action_in(
6525 "Toggle Code Actions",
6526 &ToggleCodeActions {
6527 deployed_from: None,
6528 quick_launch: false,
6529 },
6530 &focus_handle,
6531 window,
6532 cx,
6533 )
6534 }
6535 })
6536 })
6537 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6538 window.focus(&editor.focus_handle(cx));
6539 editor.toggle_code_actions(
6540 &crate::actions::ToggleCodeActions {
6541 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6542 display_row,
6543 )),
6544 quick_launch: false,
6545 },
6546 window,
6547 cx,
6548 );
6549 }))
6550 .into_any_element()
6551 }
6552
6553 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6554 &self.context_menu
6555 }
6556
6557 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6558 let newest_selection = self.selections.newest_anchor().clone();
6559 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6560 let buffer = self.buffer.read(cx);
6561 if newest_selection.head().diff_base_anchor.is_some() {
6562 return None;
6563 }
6564 let (start_buffer, start) =
6565 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6566 let (end_buffer, end) =
6567 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6568 if start_buffer != end_buffer {
6569 return None;
6570 }
6571
6572 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6573 cx.background_executor()
6574 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6575 .await;
6576
6577 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6578 let providers = this.code_action_providers.clone();
6579 let tasks = this
6580 .code_action_providers
6581 .iter()
6582 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6583 .collect::<Vec<_>>();
6584 (providers, tasks)
6585 })?;
6586
6587 let mut actions = Vec::new();
6588 for (provider, provider_actions) in
6589 providers.into_iter().zip(future::join_all(tasks).await)
6590 {
6591 if let Some(provider_actions) = provider_actions.log_err() {
6592 actions.extend(provider_actions.into_iter().map(|action| {
6593 AvailableCodeAction {
6594 excerpt_id: newest_selection.start.excerpt_id,
6595 action,
6596 provider: provider.clone(),
6597 }
6598 }));
6599 }
6600 }
6601
6602 this.update(cx, |this, cx| {
6603 this.available_code_actions = if actions.is_empty() {
6604 None
6605 } else {
6606 Some((
6607 Location {
6608 buffer: start_buffer,
6609 range: start..end,
6610 },
6611 actions.into(),
6612 ))
6613 };
6614 cx.notify();
6615 })
6616 }));
6617 None
6618 }
6619
6620 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6621 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6622 self.show_git_blame_inline = false;
6623
6624 self.show_git_blame_inline_delay_task =
6625 Some(cx.spawn_in(window, async move |this, cx| {
6626 cx.background_executor().timer(delay).await;
6627
6628 this.update(cx, |this, cx| {
6629 this.show_git_blame_inline = true;
6630 cx.notify();
6631 })
6632 .log_err();
6633 }));
6634 }
6635 }
6636
6637 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6638 let snapshot = self.snapshot(window, cx);
6639 let cursor = self.selections.newest::<Point>(cx).head();
6640 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6641 else {
6642 return;
6643 };
6644
6645 let Some(blame) = self.blame.as_ref() else {
6646 return;
6647 };
6648
6649 let row_info = RowInfo {
6650 buffer_id: Some(buffer.remote_id()),
6651 buffer_row: Some(point.row),
6652 ..Default::default()
6653 };
6654 let Some((buffer, blame_entry)) = blame
6655 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6656 .flatten()
6657 else {
6658 return;
6659 };
6660
6661 let anchor = self.selections.newest_anchor().head();
6662 let position = self.to_pixel_point(anchor, &snapshot, window);
6663 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6664 self.show_blame_popover(
6665 buffer,
6666 &blame_entry,
6667 position + last_bounds.origin,
6668 true,
6669 cx,
6670 );
6671 };
6672 }
6673
6674 fn show_blame_popover(
6675 &mut self,
6676 buffer: BufferId,
6677 blame_entry: &BlameEntry,
6678 position: gpui::Point<Pixels>,
6679 ignore_timeout: bool,
6680 cx: &mut Context<Self>,
6681 ) {
6682 if let Some(state) = &mut self.inline_blame_popover {
6683 state.hide_task.take();
6684 } else {
6685 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6686 let blame_entry = blame_entry.clone();
6687 let show_task = cx.spawn(async move |editor, cx| {
6688 if !ignore_timeout {
6689 cx.background_executor()
6690 .timer(std::time::Duration::from_millis(blame_popover_delay))
6691 .await;
6692 }
6693 editor
6694 .update(cx, |editor, cx| {
6695 editor.inline_blame_popover_show_task.take();
6696 let Some(blame) = editor.blame.as_ref() else {
6697 return;
6698 };
6699 let blame = blame.read(cx);
6700 let details = blame.details_for_entry(buffer, &blame_entry);
6701 let markdown = cx.new(|cx| {
6702 Markdown::new(
6703 details
6704 .as_ref()
6705 .map(|message| message.message.clone())
6706 .unwrap_or_default(),
6707 None,
6708 None,
6709 cx,
6710 )
6711 });
6712 editor.inline_blame_popover = Some(InlineBlamePopover {
6713 position,
6714 hide_task: None,
6715 popover_bounds: None,
6716 popover_state: InlineBlamePopoverState {
6717 scroll_handle: ScrollHandle::new(),
6718 commit_message: details,
6719 markdown,
6720 },
6721 keyboard_grace: ignore_timeout,
6722 });
6723 cx.notify();
6724 })
6725 .ok();
6726 });
6727 self.inline_blame_popover_show_task = Some(show_task);
6728 }
6729 }
6730
6731 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6732 self.inline_blame_popover_show_task.take();
6733 if let Some(state) = &mut self.inline_blame_popover {
6734 let hide_task = cx.spawn(async move |editor, cx| {
6735 cx.background_executor()
6736 .timer(std::time::Duration::from_millis(100))
6737 .await;
6738 editor
6739 .update(cx, |editor, cx| {
6740 editor.inline_blame_popover.take();
6741 cx.notify();
6742 })
6743 .ok();
6744 });
6745 state.hide_task = Some(hide_task);
6746 }
6747 }
6748
6749 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6750 if self.pending_rename.is_some() {
6751 return None;
6752 }
6753
6754 let provider = self.semantics_provider.clone()?;
6755 let buffer = self.buffer.read(cx);
6756 let newest_selection = self.selections.newest_anchor().clone();
6757 let cursor_position = newest_selection.head();
6758 let (cursor_buffer, cursor_buffer_position) =
6759 buffer.text_anchor_for_position(cursor_position, cx)?;
6760 let (tail_buffer, tail_buffer_position) =
6761 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6762 if cursor_buffer != tail_buffer {
6763 return None;
6764 }
6765
6766 let snapshot = cursor_buffer.read(cx).snapshot();
6767 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, false);
6768 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, false);
6769 if start_word_range != end_word_range {
6770 self.document_highlights_task.take();
6771 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6772 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6773 return None;
6774 }
6775
6776 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6777 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6778 cx.background_executor()
6779 .timer(Duration::from_millis(debounce))
6780 .await;
6781
6782 let highlights = if let Some(highlights) = cx
6783 .update(|cx| {
6784 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6785 })
6786 .ok()
6787 .flatten()
6788 {
6789 highlights.await.log_err()
6790 } else {
6791 None
6792 };
6793
6794 if let Some(highlights) = highlights {
6795 this.update(cx, |this, cx| {
6796 if this.pending_rename.is_some() {
6797 return;
6798 }
6799
6800 let buffer = this.buffer.read(cx);
6801 if buffer
6802 .text_anchor_for_position(cursor_position, cx)
6803 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6804 {
6805 return;
6806 }
6807
6808 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6809 let mut write_ranges = Vec::new();
6810 let mut read_ranges = Vec::new();
6811 for highlight in highlights {
6812 let buffer_id = cursor_buffer.read(cx).remote_id();
6813 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6814 {
6815 let start = highlight
6816 .range
6817 .start
6818 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6819 let end = highlight
6820 .range
6821 .end
6822 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6823 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6824 continue;
6825 }
6826
6827 let range = Anchor {
6828 buffer_id: Some(buffer_id),
6829 excerpt_id,
6830 text_anchor: start,
6831 diff_base_anchor: None,
6832 }..Anchor {
6833 buffer_id: Some(buffer_id),
6834 excerpt_id,
6835 text_anchor: end,
6836 diff_base_anchor: None,
6837 };
6838 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6839 write_ranges.push(range);
6840 } else {
6841 read_ranges.push(range);
6842 }
6843 }
6844 }
6845
6846 this.highlight_background::<DocumentHighlightRead>(
6847 &read_ranges,
6848 |theme| theme.colors().editor_document_highlight_read_background,
6849 cx,
6850 );
6851 this.highlight_background::<DocumentHighlightWrite>(
6852 &write_ranges,
6853 |theme| theme.colors().editor_document_highlight_write_background,
6854 cx,
6855 );
6856 cx.notify();
6857 })
6858 .log_err();
6859 }
6860 }));
6861 None
6862 }
6863
6864 fn prepare_highlight_query_from_selection(
6865 &mut self,
6866 cx: &mut Context<Editor>,
6867 ) -> Option<(String, Range<Anchor>)> {
6868 if matches!(self.mode, EditorMode::SingleLine) {
6869 return None;
6870 }
6871 if !EditorSettings::get_global(cx).selection_highlight {
6872 return None;
6873 }
6874 if self.selections.count() != 1 || self.selections.line_mode {
6875 return None;
6876 }
6877 let selection = self.selections.newest::<Point>(cx);
6878 if selection.is_empty() || selection.start.row != selection.end.row {
6879 return None;
6880 }
6881 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6882 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6883 let query = multi_buffer_snapshot
6884 .text_for_range(selection_anchor_range.clone())
6885 .collect::<String>();
6886 if query.trim().is_empty() {
6887 return None;
6888 }
6889 Some((query, selection_anchor_range))
6890 }
6891
6892 fn update_selection_occurrence_highlights(
6893 &mut self,
6894 query_text: String,
6895 query_range: Range<Anchor>,
6896 multi_buffer_range_to_query: Range<Point>,
6897 use_debounce: bool,
6898 window: &mut Window,
6899 cx: &mut Context<Editor>,
6900 ) -> Task<()> {
6901 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6902 cx.spawn_in(window, async move |editor, cx| {
6903 if use_debounce {
6904 cx.background_executor()
6905 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6906 .await;
6907 }
6908 let match_task = cx.background_spawn(async move {
6909 let buffer_ranges = multi_buffer_snapshot
6910 .range_to_buffer_ranges(multi_buffer_range_to_query)
6911 .into_iter()
6912 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6913 let mut match_ranges = Vec::new();
6914 let Ok(regex) = project::search::SearchQuery::text(
6915 query_text.clone(),
6916 false,
6917 false,
6918 false,
6919 Default::default(),
6920 Default::default(),
6921 false,
6922 None,
6923 ) else {
6924 return Vec::default();
6925 };
6926 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6927 match_ranges.extend(
6928 regex
6929 .search(buffer_snapshot, Some(search_range.clone()))
6930 .await
6931 .into_iter()
6932 .filter_map(|match_range| {
6933 let match_start = buffer_snapshot
6934 .anchor_after(search_range.start + match_range.start);
6935 let match_end = buffer_snapshot
6936 .anchor_before(search_range.start + match_range.end);
6937 let match_anchor_range = Anchor::range_in_buffer(
6938 excerpt_id,
6939 buffer_snapshot.remote_id(),
6940 match_start..match_end,
6941 );
6942 (match_anchor_range != query_range).then_some(match_anchor_range)
6943 }),
6944 );
6945 }
6946 match_ranges
6947 });
6948 let match_ranges = match_task.await;
6949 editor
6950 .update_in(cx, |editor, _, cx| {
6951 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6952 if !match_ranges.is_empty() {
6953 editor.highlight_background::<SelectedTextHighlight>(
6954 &match_ranges,
6955 |theme| theme.colors().editor_document_highlight_bracket_background,
6956 cx,
6957 )
6958 }
6959 })
6960 .log_err();
6961 })
6962 }
6963
6964 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6965 struct NewlineFold;
6966 let type_id = std::any::TypeId::of::<NewlineFold>();
6967 if !self.mode.is_single_line() {
6968 return;
6969 }
6970 let snapshot = self.snapshot(window, cx);
6971 if snapshot.buffer_snapshot.max_point().row == 0 {
6972 return;
6973 }
6974 let task = cx.background_spawn(async move {
6975 let new_newlines = snapshot
6976 .buffer_chars_at(0)
6977 .filter_map(|(c, i)| {
6978 if c == '\n' {
6979 Some(
6980 snapshot.buffer_snapshot.anchor_after(i)
6981 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6982 )
6983 } else {
6984 None
6985 }
6986 })
6987 .collect::<Vec<_>>();
6988 let existing_newlines = snapshot
6989 .folds_in_range(0..snapshot.buffer_snapshot.len())
6990 .filter_map(|fold| {
6991 if fold.placeholder.type_tag == Some(type_id) {
6992 Some(fold.range.start..fold.range.end)
6993 } else {
6994 None
6995 }
6996 })
6997 .collect::<Vec<_>>();
6998
6999 (new_newlines, existing_newlines)
7000 });
7001 self.folding_newlines = cx.spawn(async move |this, cx| {
7002 let (new_newlines, existing_newlines) = task.await;
7003 if new_newlines == existing_newlines {
7004 return;
7005 }
7006 let placeholder = FoldPlaceholder {
7007 render: Arc::new(move |_, _, cx| {
7008 div()
7009 .bg(cx.theme().status().hint_background)
7010 .border_b_1()
7011 .size_full()
7012 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7013 .border_color(cx.theme().status().hint)
7014 .child("\\n")
7015 .into_any()
7016 }),
7017 constrain_width: false,
7018 merge_adjacent: false,
7019 type_tag: Some(type_id),
7020 };
7021 let creases = new_newlines
7022 .into_iter()
7023 .map(|range| Crease::simple(range, placeholder.clone()))
7024 .collect();
7025 this.update(cx, |this, cx| {
7026 this.display_map.update(cx, |display_map, cx| {
7027 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7028 display_map.fold(creases, cx);
7029 });
7030 })
7031 .ok();
7032 });
7033 }
7034
7035 fn refresh_selected_text_highlights(
7036 &mut self,
7037 on_buffer_edit: bool,
7038 window: &mut Window,
7039 cx: &mut Context<Editor>,
7040 ) {
7041 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7042 else {
7043 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7044 self.quick_selection_highlight_task.take();
7045 self.debounced_selection_highlight_task.take();
7046 return;
7047 };
7048 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7049 if on_buffer_edit
7050 || self
7051 .quick_selection_highlight_task
7052 .as_ref()
7053 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7054 {
7055 let multi_buffer_visible_start = self
7056 .scroll_manager
7057 .anchor()
7058 .anchor
7059 .to_point(&multi_buffer_snapshot);
7060 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7061 multi_buffer_visible_start
7062 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7063 Bias::Left,
7064 );
7065 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7066 self.quick_selection_highlight_task = Some((
7067 query_range.clone(),
7068 self.update_selection_occurrence_highlights(
7069 query_text.clone(),
7070 query_range.clone(),
7071 multi_buffer_visible_range,
7072 false,
7073 window,
7074 cx,
7075 ),
7076 ));
7077 }
7078 if on_buffer_edit
7079 || self
7080 .debounced_selection_highlight_task
7081 .as_ref()
7082 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7083 {
7084 let multi_buffer_start = multi_buffer_snapshot
7085 .anchor_before(0)
7086 .to_point(&multi_buffer_snapshot);
7087 let multi_buffer_end = multi_buffer_snapshot
7088 .anchor_after(multi_buffer_snapshot.len())
7089 .to_point(&multi_buffer_snapshot);
7090 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7091 self.debounced_selection_highlight_task = Some((
7092 query_range.clone(),
7093 self.update_selection_occurrence_highlights(
7094 query_text,
7095 query_range,
7096 multi_buffer_full_range,
7097 true,
7098 window,
7099 cx,
7100 ),
7101 ));
7102 }
7103 }
7104
7105 pub fn refresh_edit_prediction(
7106 &mut self,
7107 debounce: bool,
7108 user_requested: bool,
7109 window: &mut Window,
7110 cx: &mut Context<Self>,
7111 ) -> Option<()> {
7112 if DisableAiSettings::get_global(cx).disable_ai {
7113 return None;
7114 }
7115
7116 let provider = self.edit_prediction_provider()?;
7117 let cursor = self.selections.newest_anchor().head();
7118 let (buffer, cursor_buffer_position) =
7119 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7120
7121 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7122 self.discard_edit_prediction(false, cx);
7123 return None;
7124 }
7125
7126 if !user_requested
7127 && (!self.should_show_edit_predictions()
7128 || !self.is_focused(window)
7129 || buffer.read(cx).is_empty())
7130 {
7131 self.discard_edit_prediction(false, cx);
7132 return None;
7133 }
7134
7135 self.update_visible_edit_prediction(window, cx);
7136 provider.refresh(
7137 self.project.clone(),
7138 buffer,
7139 cursor_buffer_position,
7140 debounce,
7141 cx,
7142 );
7143 Some(())
7144 }
7145
7146 fn show_edit_predictions_in_menu(&self) -> bool {
7147 match self.edit_prediction_settings {
7148 EditPredictionSettings::Disabled => false,
7149 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7150 }
7151 }
7152
7153 pub fn edit_predictions_enabled(&self) -> bool {
7154 match self.edit_prediction_settings {
7155 EditPredictionSettings::Disabled => false,
7156 EditPredictionSettings::Enabled { .. } => true,
7157 }
7158 }
7159
7160 fn edit_prediction_requires_modifier(&self) -> bool {
7161 match self.edit_prediction_settings {
7162 EditPredictionSettings::Disabled => false,
7163 EditPredictionSettings::Enabled {
7164 preview_requires_modifier,
7165 ..
7166 } => preview_requires_modifier,
7167 }
7168 }
7169
7170 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7171 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7172 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7173 self.discard_edit_prediction(false, cx);
7174 } else {
7175 let selection = self.selections.newest_anchor();
7176 let cursor = selection.head();
7177
7178 if let Some((buffer, cursor_buffer_position)) =
7179 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7180 {
7181 self.edit_prediction_settings =
7182 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7183 }
7184 }
7185 }
7186
7187 fn edit_prediction_settings_at_position(
7188 &self,
7189 buffer: &Entity<Buffer>,
7190 buffer_position: language::Anchor,
7191 cx: &App,
7192 ) -> EditPredictionSettings {
7193 if !self.mode.is_full()
7194 || !self.show_edit_predictions_override.unwrap_or(true)
7195 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7196 {
7197 return EditPredictionSettings::Disabled;
7198 }
7199
7200 let buffer = buffer.read(cx);
7201
7202 let file = buffer.file();
7203
7204 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7205 return EditPredictionSettings::Disabled;
7206 };
7207
7208 let by_provider = matches!(
7209 self.menu_edit_predictions_policy,
7210 MenuEditPredictionsPolicy::ByProvider
7211 );
7212
7213 let show_in_menu = by_provider
7214 && self
7215 .edit_prediction_provider
7216 .as_ref()
7217 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7218
7219 let preview_requires_modifier =
7220 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7221
7222 EditPredictionSettings::Enabled {
7223 show_in_menu,
7224 preview_requires_modifier,
7225 }
7226 }
7227
7228 fn should_show_edit_predictions(&self) -> bool {
7229 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7230 }
7231
7232 pub fn edit_prediction_preview_is_active(&self) -> bool {
7233 matches!(
7234 self.edit_prediction_preview,
7235 EditPredictionPreview::Active { .. }
7236 )
7237 }
7238
7239 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7240 let cursor = self.selections.newest_anchor().head();
7241 if let Some((buffer, cursor_position)) =
7242 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7243 {
7244 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7245 } else {
7246 false
7247 }
7248 }
7249
7250 pub fn supports_minimap(&self, cx: &App) -> bool {
7251 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7252 }
7253
7254 fn edit_predictions_enabled_in_buffer(
7255 &self,
7256 buffer: &Entity<Buffer>,
7257 buffer_position: language::Anchor,
7258 cx: &App,
7259 ) -> bool {
7260 maybe!({
7261 if self.read_only(cx) {
7262 return Some(false);
7263 }
7264 let provider = self.edit_prediction_provider()?;
7265 if !provider.is_enabled(buffer, buffer_position, cx) {
7266 return Some(false);
7267 }
7268 let buffer = buffer.read(cx);
7269 let Some(file) = buffer.file() else {
7270 return Some(true);
7271 };
7272 let settings = all_language_settings(Some(file), cx);
7273 Some(settings.edit_predictions_enabled_for_file(file, cx))
7274 })
7275 .unwrap_or(false)
7276 }
7277
7278 fn cycle_edit_prediction(
7279 &mut self,
7280 direction: Direction,
7281 window: &mut Window,
7282 cx: &mut Context<Self>,
7283 ) -> Option<()> {
7284 let provider = self.edit_prediction_provider()?;
7285 let cursor = self.selections.newest_anchor().head();
7286 let (buffer, cursor_buffer_position) =
7287 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7288 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7289 return None;
7290 }
7291
7292 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7293 self.update_visible_edit_prediction(window, cx);
7294
7295 Some(())
7296 }
7297
7298 pub fn show_edit_prediction(
7299 &mut self,
7300 _: &ShowEditPrediction,
7301 window: &mut Window,
7302 cx: &mut Context<Self>,
7303 ) {
7304 if !self.has_active_edit_prediction() {
7305 self.refresh_edit_prediction(false, true, window, cx);
7306 return;
7307 }
7308
7309 self.update_visible_edit_prediction(window, cx);
7310 }
7311
7312 pub fn display_cursor_names(
7313 &mut self,
7314 _: &DisplayCursorNames,
7315 window: &mut Window,
7316 cx: &mut Context<Self>,
7317 ) {
7318 self.show_cursor_names(window, cx);
7319 }
7320
7321 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7322 self.show_cursor_names = true;
7323 cx.notify();
7324 cx.spawn_in(window, async move |this, cx| {
7325 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7326 this.update(cx, |this, cx| {
7327 this.show_cursor_names = false;
7328 cx.notify()
7329 })
7330 .ok()
7331 })
7332 .detach();
7333 }
7334
7335 pub fn next_edit_prediction(
7336 &mut self,
7337 _: &NextEditPrediction,
7338 window: &mut Window,
7339 cx: &mut Context<Self>,
7340 ) {
7341 if self.has_active_edit_prediction() {
7342 self.cycle_edit_prediction(Direction::Next, window, cx);
7343 } else {
7344 let is_copilot_disabled = self
7345 .refresh_edit_prediction(false, true, window, cx)
7346 .is_none();
7347 if is_copilot_disabled {
7348 cx.propagate();
7349 }
7350 }
7351 }
7352
7353 pub fn previous_edit_prediction(
7354 &mut self,
7355 _: &PreviousEditPrediction,
7356 window: &mut Window,
7357 cx: &mut Context<Self>,
7358 ) {
7359 if self.has_active_edit_prediction() {
7360 self.cycle_edit_prediction(Direction::Prev, window, cx);
7361 } else {
7362 let is_copilot_disabled = self
7363 .refresh_edit_prediction(false, true, window, cx)
7364 .is_none();
7365 if is_copilot_disabled {
7366 cx.propagate();
7367 }
7368 }
7369 }
7370
7371 pub fn accept_edit_prediction(
7372 &mut self,
7373 _: &AcceptEditPrediction,
7374 window: &mut Window,
7375 cx: &mut Context<Self>,
7376 ) {
7377 if self.show_edit_predictions_in_menu() {
7378 self.hide_context_menu(window, cx);
7379 }
7380
7381 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7382 return;
7383 };
7384
7385 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7386
7387 match &active_edit_prediction.completion {
7388 EditPrediction::Move { target, .. } => {
7389 let target = *target;
7390
7391 if let Some(position_map) = &self.last_position_map {
7392 if position_map
7393 .visible_row_range
7394 .contains(&target.to_display_point(&position_map.snapshot).row())
7395 || !self.edit_prediction_requires_modifier()
7396 {
7397 self.unfold_ranges(&[target..target], true, false, cx);
7398 // Note that this is also done in vim's handler of the Tab action.
7399 self.change_selections(
7400 SelectionEffects::scroll(Autoscroll::newest()),
7401 window,
7402 cx,
7403 |selections| {
7404 selections.select_anchor_ranges([target..target]);
7405 },
7406 );
7407 self.clear_row_highlights::<EditPredictionPreview>();
7408
7409 self.edit_prediction_preview
7410 .set_previous_scroll_position(None);
7411 } else {
7412 self.edit_prediction_preview
7413 .set_previous_scroll_position(Some(
7414 position_map.snapshot.scroll_anchor,
7415 ));
7416
7417 self.highlight_rows::<EditPredictionPreview>(
7418 target..target,
7419 cx.theme().colors().editor_highlighted_line_background,
7420 RowHighlightOptions {
7421 autoscroll: true,
7422 ..Default::default()
7423 },
7424 cx,
7425 );
7426 self.request_autoscroll(Autoscroll::fit(), cx);
7427 }
7428 }
7429 }
7430 EditPrediction::Edit { edits, .. } => {
7431 if let Some(provider) = self.edit_prediction_provider() {
7432 provider.accept(cx);
7433 }
7434
7435 // Store the transaction ID and selections before applying the edit
7436 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7437
7438 let snapshot = self.buffer.read(cx).snapshot(cx);
7439 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7440
7441 self.buffer.update(cx, |buffer, cx| {
7442 buffer.edit(edits.iter().cloned(), None, cx)
7443 });
7444
7445 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7446 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7447 });
7448
7449 let selections = self.selections.disjoint_anchors();
7450 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7451 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7452 if has_new_transaction {
7453 self.selection_history
7454 .insert_transaction(transaction_id_now, selections);
7455 }
7456 }
7457
7458 self.update_visible_edit_prediction(window, cx);
7459 if self.active_edit_prediction.is_none() {
7460 self.refresh_edit_prediction(true, true, window, cx);
7461 }
7462
7463 cx.notify();
7464 }
7465 }
7466
7467 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7468 }
7469
7470 pub fn accept_partial_edit_prediction(
7471 &mut self,
7472 _: &AcceptPartialEditPrediction,
7473 window: &mut Window,
7474 cx: &mut Context<Self>,
7475 ) {
7476 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7477 return;
7478 };
7479 if self.selections.count() != 1 {
7480 return;
7481 }
7482
7483 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7484
7485 match &active_edit_prediction.completion {
7486 EditPrediction::Move { target, .. } => {
7487 let target = *target;
7488 self.change_selections(
7489 SelectionEffects::scroll(Autoscroll::newest()),
7490 window,
7491 cx,
7492 |selections| {
7493 selections.select_anchor_ranges([target..target]);
7494 },
7495 );
7496 }
7497 EditPrediction::Edit { edits, .. } => {
7498 // Find an insertion that starts at the cursor position.
7499 let snapshot = self.buffer.read(cx).snapshot(cx);
7500 let cursor_offset = self.selections.newest::<usize>(cx).head();
7501 let insertion = edits.iter().find_map(|(range, text)| {
7502 let range = range.to_offset(&snapshot);
7503 if range.is_empty() && range.start == cursor_offset {
7504 Some(text)
7505 } else {
7506 None
7507 }
7508 });
7509
7510 if let Some(text) = insertion {
7511 let mut partial_completion = text
7512 .chars()
7513 .by_ref()
7514 .take_while(|c| c.is_alphabetic())
7515 .collect::<String>();
7516 if partial_completion.is_empty() {
7517 partial_completion = text
7518 .chars()
7519 .by_ref()
7520 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7521 .collect::<String>();
7522 }
7523
7524 cx.emit(EditorEvent::InputHandled {
7525 utf16_range_to_replace: None,
7526 text: partial_completion.clone().into(),
7527 });
7528
7529 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7530
7531 self.refresh_edit_prediction(true, true, window, cx);
7532 cx.notify();
7533 } else {
7534 self.accept_edit_prediction(&Default::default(), window, cx);
7535 }
7536 }
7537 }
7538 }
7539
7540 fn discard_edit_prediction(
7541 &mut self,
7542 should_report_edit_prediction_event: bool,
7543 cx: &mut Context<Self>,
7544 ) -> bool {
7545 if should_report_edit_prediction_event {
7546 let completion_id = self
7547 .active_edit_prediction
7548 .as_ref()
7549 .and_then(|active_completion| active_completion.completion_id.clone());
7550
7551 self.report_edit_prediction_event(completion_id, false, cx);
7552 }
7553
7554 if let Some(provider) = self.edit_prediction_provider() {
7555 provider.discard(cx);
7556 }
7557
7558 self.take_active_edit_prediction(cx)
7559 }
7560
7561 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7562 let Some(provider) = self.edit_prediction_provider() else {
7563 return;
7564 };
7565
7566 let Some((_, buffer, _)) = self
7567 .buffer
7568 .read(cx)
7569 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7570 else {
7571 return;
7572 };
7573
7574 let extension = buffer
7575 .read(cx)
7576 .file()
7577 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7578
7579 let event_type = match accepted {
7580 true => "Edit Prediction Accepted",
7581 false => "Edit Prediction Discarded",
7582 };
7583 telemetry::event!(
7584 event_type,
7585 provider = provider.name(),
7586 prediction_id = id,
7587 suggestion_accepted = accepted,
7588 file_extension = extension,
7589 );
7590 }
7591
7592 pub fn has_active_edit_prediction(&self) -> bool {
7593 self.active_edit_prediction.is_some()
7594 }
7595
7596 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7597 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7598 return false;
7599 };
7600
7601 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7602 self.clear_highlights::<EditPredictionHighlight>(cx);
7603 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7604 true
7605 }
7606
7607 /// Returns true when we're displaying the edit prediction popover below the cursor
7608 /// like we are not previewing and the LSP autocomplete menu is visible
7609 /// or we are in `when_holding_modifier` mode.
7610 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7611 if self.edit_prediction_preview_is_active()
7612 || !self.show_edit_predictions_in_menu()
7613 || !self.edit_predictions_enabled()
7614 {
7615 return false;
7616 }
7617
7618 if self.has_visible_completions_menu() {
7619 return true;
7620 }
7621
7622 has_completion && self.edit_prediction_requires_modifier()
7623 }
7624
7625 fn handle_modifiers_changed(
7626 &mut self,
7627 modifiers: Modifiers,
7628 position_map: &PositionMap,
7629 window: &mut Window,
7630 cx: &mut Context<Self>,
7631 ) {
7632 if self.show_edit_predictions_in_menu() {
7633 self.update_edit_prediction_preview(&modifiers, window, cx);
7634 }
7635
7636 self.update_selection_mode(&modifiers, position_map, window, cx);
7637
7638 let mouse_position = window.mouse_position();
7639 if !position_map.text_hitbox.is_hovered(window) {
7640 return;
7641 }
7642
7643 self.update_hovered_link(
7644 position_map.point_for_position(mouse_position),
7645 &position_map.snapshot,
7646 modifiers,
7647 window,
7648 cx,
7649 )
7650 }
7651
7652 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7653 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7654 if invert {
7655 match multi_cursor_setting {
7656 MultiCursorModifier::Alt => modifiers.alt,
7657 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7658 }
7659 } else {
7660 match multi_cursor_setting {
7661 MultiCursorModifier::Alt => modifiers.secondary(),
7662 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7663 }
7664 }
7665 }
7666
7667 fn columnar_selection_mode(
7668 modifiers: &Modifiers,
7669 cx: &mut Context<Self>,
7670 ) -> Option<ColumnarMode> {
7671 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7672 if Self::multi_cursor_modifier(false, modifiers, cx) {
7673 Some(ColumnarMode::FromMouse)
7674 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7675 Some(ColumnarMode::FromSelection)
7676 } else {
7677 None
7678 }
7679 } else {
7680 None
7681 }
7682 }
7683
7684 fn update_selection_mode(
7685 &mut self,
7686 modifiers: &Modifiers,
7687 position_map: &PositionMap,
7688 window: &mut Window,
7689 cx: &mut Context<Self>,
7690 ) {
7691 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7692 return;
7693 };
7694 if self.selections.pending.is_none() {
7695 return;
7696 }
7697
7698 let mouse_position = window.mouse_position();
7699 let point_for_position = position_map.point_for_position(mouse_position);
7700 let position = point_for_position.previous_valid;
7701
7702 self.select(
7703 SelectPhase::BeginColumnar {
7704 position,
7705 reset: false,
7706 mode,
7707 goal_column: point_for_position.exact_unclipped.column(),
7708 },
7709 window,
7710 cx,
7711 );
7712 }
7713
7714 fn update_edit_prediction_preview(
7715 &mut self,
7716 modifiers: &Modifiers,
7717 window: &mut Window,
7718 cx: &mut Context<Self>,
7719 ) {
7720 let mut modifiers_held = false;
7721 if let Some(accept_keystroke) = self
7722 .accept_edit_prediction_keybind(false, window, cx)
7723 .keystroke()
7724 {
7725 modifiers_held = modifiers_held
7726 || (accept_keystroke.modifiers() == modifiers
7727 && accept_keystroke.modifiers().modified());
7728 };
7729 if let Some(accept_partial_keystroke) = self
7730 .accept_edit_prediction_keybind(true, window, cx)
7731 .keystroke()
7732 {
7733 modifiers_held = modifiers_held
7734 || (accept_partial_keystroke.modifiers() == modifiers
7735 && accept_partial_keystroke.modifiers().modified());
7736 }
7737
7738 if modifiers_held {
7739 if matches!(
7740 self.edit_prediction_preview,
7741 EditPredictionPreview::Inactive { .. }
7742 ) {
7743 self.edit_prediction_preview = EditPredictionPreview::Active {
7744 previous_scroll_position: None,
7745 since: Instant::now(),
7746 };
7747
7748 self.update_visible_edit_prediction(window, cx);
7749 cx.notify();
7750 }
7751 } else if let EditPredictionPreview::Active {
7752 previous_scroll_position,
7753 since,
7754 } = self.edit_prediction_preview
7755 {
7756 if let (Some(previous_scroll_position), Some(position_map)) =
7757 (previous_scroll_position, self.last_position_map.as_ref())
7758 {
7759 self.set_scroll_position(
7760 previous_scroll_position
7761 .scroll_position(&position_map.snapshot.display_snapshot),
7762 window,
7763 cx,
7764 );
7765 }
7766
7767 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7768 released_too_fast: since.elapsed() < Duration::from_millis(200),
7769 };
7770 self.clear_row_highlights::<EditPredictionPreview>();
7771 self.update_visible_edit_prediction(window, cx);
7772 cx.notify();
7773 }
7774 }
7775
7776 fn update_visible_edit_prediction(
7777 &mut self,
7778 _window: &mut Window,
7779 cx: &mut Context<Self>,
7780 ) -> Option<()> {
7781 if DisableAiSettings::get_global(cx).disable_ai {
7782 return None;
7783 }
7784
7785 if self.ime_transaction.is_some() {
7786 self.discard_edit_prediction(false, cx);
7787 return None;
7788 }
7789
7790 let selection = self.selections.newest_anchor();
7791 let cursor = selection.head();
7792 let multibuffer = self.buffer.read(cx).snapshot(cx);
7793 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7794 let excerpt_id = cursor.excerpt_id;
7795
7796 let show_in_menu = self.show_edit_predictions_in_menu();
7797 let completions_menu_has_precedence = !show_in_menu
7798 && (self.context_menu.borrow().is_some()
7799 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7800
7801 if completions_menu_has_precedence
7802 || !offset_selection.is_empty()
7803 || self
7804 .active_edit_prediction
7805 .as_ref()
7806 .is_some_and(|completion| {
7807 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7808 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7809 !invalidation_range.contains(&offset_selection.head())
7810 })
7811 {
7812 self.discard_edit_prediction(false, cx);
7813 return None;
7814 }
7815
7816 self.take_active_edit_prediction(cx);
7817 let Some(provider) = self.edit_prediction_provider() else {
7818 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7819 return None;
7820 };
7821
7822 let (buffer, cursor_buffer_position) =
7823 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7824
7825 self.edit_prediction_settings =
7826 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7827
7828 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7829 self.discard_edit_prediction(false, cx);
7830 return None;
7831 };
7832
7833 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7834
7835 if self.edit_prediction_indent_conflict {
7836 let cursor_point = cursor.to_point(&multibuffer);
7837
7838 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7839
7840 if let Some((_, indent)) = indents.iter().next()
7841 && indent.len == cursor_point.column
7842 {
7843 self.edit_prediction_indent_conflict = false;
7844 }
7845 }
7846
7847 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7848 let edits = edit_prediction
7849 .edits
7850 .into_iter()
7851 .flat_map(|(range, new_text)| {
7852 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7853 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7854 Some((start..end, new_text))
7855 })
7856 .collect::<Vec<_>>();
7857 if edits.is_empty() {
7858 return None;
7859 }
7860
7861 let first_edit_start = edits.first().unwrap().0.start;
7862 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7863 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7864
7865 let last_edit_end = edits.last().unwrap().0.end;
7866 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7867 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7868
7869 let cursor_row = cursor.to_point(&multibuffer).row;
7870
7871 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7872
7873 let mut inlay_ids = Vec::new();
7874 let invalidation_row_range;
7875 let move_invalidation_row_range = if cursor_row < edit_start_row {
7876 Some(cursor_row..edit_end_row)
7877 } else if cursor_row > edit_end_row {
7878 Some(edit_start_row..cursor_row)
7879 } else {
7880 None
7881 };
7882 let supports_jump = self
7883 .edit_prediction_provider
7884 .as_ref()
7885 .map(|provider| provider.provider.supports_jump_to_edit())
7886 .unwrap_or(true);
7887
7888 let is_move = supports_jump
7889 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7890 let completion = if is_move {
7891 invalidation_row_range =
7892 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7893 let target = first_edit_start;
7894 EditPrediction::Move { target, snapshot }
7895 } else {
7896 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7897 && !self.edit_predictions_hidden_for_vim_mode;
7898
7899 if show_completions_in_buffer {
7900 if edits
7901 .iter()
7902 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7903 {
7904 let mut inlays = Vec::new();
7905 for (range, new_text) in &edits {
7906 let inlay = Inlay::edit_prediction(
7907 post_inc(&mut self.next_inlay_id),
7908 range.start,
7909 new_text.as_str(),
7910 );
7911 inlay_ids.push(inlay.id);
7912 inlays.push(inlay);
7913 }
7914
7915 self.splice_inlays(&[], inlays, cx);
7916 } else {
7917 let background_color = cx.theme().status().deleted_background;
7918 self.highlight_text::<EditPredictionHighlight>(
7919 edits.iter().map(|(range, _)| range.clone()).collect(),
7920 HighlightStyle {
7921 background_color: Some(background_color),
7922 ..Default::default()
7923 },
7924 cx,
7925 );
7926 }
7927 }
7928
7929 invalidation_row_range = edit_start_row..edit_end_row;
7930
7931 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7932 if provider.show_tab_accept_marker() {
7933 EditDisplayMode::TabAccept
7934 } else {
7935 EditDisplayMode::Inline
7936 }
7937 } else {
7938 EditDisplayMode::DiffPopover
7939 };
7940
7941 EditPrediction::Edit {
7942 edits,
7943 edit_preview: edit_prediction.edit_preview,
7944 display_mode,
7945 snapshot,
7946 }
7947 };
7948
7949 let invalidation_range = multibuffer
7950 .anchor_before(Point::new(invalidation_row_range.start, 0))
7951 ..multibuffer.anchor_after(Point::new(
7952 invalidation_row_range.end,
7953 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7954 ));
7955
7956 self.stale_edit_prediction_in_menu = None;
7957 self.active_edit_prediction = Some(EditPredictionState {
7958 inlay_ids,
7959 completion,
7960 completion_id: edit_prediction.id,
7961 invalidation_range,
7962 });
7963
7964 cx.notify();
7965
7966 Some(())
7967 }
7968
7969 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7970 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7971 }
7972
7973 fn clear_tasks(&mut self) {
7974 self.tasks.clear()
7975 }
7976
7977 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7978 if self.tasks.insert(key, value).is_some() {
7979 // This case should hopefully be rare, but just in case...
7980 log::error!(
7981 "multiple different run targets found on a single line, only the last target will be rendered"
7982 )
7983 }
7984 }
7985
7986 /// Get all display points of breakpoints that will be rendered within editor
7987 ///
7988 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7989 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7990 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7991 fn active_breakpoints(
7992 &self,
7993 range: Range<DisplayRow>,
7994 window: &mut Window,
7995 cx: &mut Context<Self>,
7996 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7997 let mut breakpoint_display_points = HashMap::default();
7998
7999 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8000 return breakpoint_display_points;
8001 };
8002
8003 let snapshot = self.snapshot(window, cx);
8004
8005 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8006 let Some(project) = self.project() else {
8007 return breakpoint_display_points;
8008 };
8009
8010 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8011 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8012
8013 for (buffer_snapshot, range, excerpt_id) in
8014 multi_buffer_snapshot.range_to_buffer_ranges(range)
8015 {
8016 let Some(buffer) = project
8017 .read(cx)
8018 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8019 else {
8020 continue;
8021 };
8022 let breakpoints = breakpoint_store.read(cx).breakpoints(
8023 &buffer,
8024 Some(
8025 buffer_snapshot.anchor_before(range.start)
8026 ..buffer_snapshot.anchor_after(range.end),
8027 ),
8028 buffer_snapshot,
8029 cx,
8030 );
8031 for (breakpoint, state) in breakpoints {
8032 let multi_buffer_anchor =
8033 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8034 let position = multi_buffer_anchor
8035 .to_point(multi_buffer_snapshot)
8036 .to_display_point(&snapshot);
8037
8038 breakpoint_display_points.insert(
8039 position.row(),
8040 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8041 );
8042 }
8043 }
8044
8045 breakpoint_display_points
8046 }
8047
8048 fn breakpoint_context_menu(
8049 &self,
8050 anchor: Anchor,
8051 window: &mut Window,
8052 cx: &mut Context<Self>,
8053 ) -> Entity<ui::ContextMenu> {
8054 let weak_editor = cx.weak_entity();
8055 let focus_handle = self.focus_handle(cx);
8056
8057 let row = self
8058 .buffer
8059 .read(cx)
8060 .snapshot(cx)
8061 .summary_for_anchor::<Point>(&anchor)
8062 .row;
8063
8064 let breakpoint = self
8065 .breakpoint_at_row(row, window, cx)
8066 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8067
8068 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8069 "Edit Log Breakpoint"
8070 } else {
8071 "Set Log Breakpoint"
8072 };
8073
8074 let condition_breakpoint_msg = if breakpoint
8075 .as_ref()
8076 .is_some_and(|bp| bp.1.condition.is_some())
8077 {
8078 "Edit Condition Breakpoint"
8079 } else {
8080 "Set Condition Breakpoint"
8081 };
8082
8083 let hit_condition_breakpoint_msg = if breakpoint
8084 .as_ref()
8085 .is_some_and(|bp| bp.1.hit_condition.is_some())
8086 {
8087 "Edit Hit Condition Breakpoint"
8088 } else {
8089 "Set Hit Condition Breakpoint"
8090 };
8091
8092 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8093 "Unset Breakpoint"
8094 } else {
8095 "Set Breakpoint"
8096 };
8097
8098 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8099
8100 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8101 BreakpointState::Enabled => Some("Disable"),
8102 BreakpointState::Disabled => Some("Enable"),
8103 });
8104
8105 let (anchor, breakpoint) =
8106 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8107
8108 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8109 menu.on_blur_subscription(Subscription::new(|| {}))
8110 .context(focus_handle)
8111 .when(run_to_cursor, |this| {
8112 let weak_editor = weak_editor.clone();
8113 this.entry("Run to cursor", None, move |window, cx| {
8114 weak_editor
8115 .update(cx, |editor, cx| {
8116 editor.change_selections(
8117 SelectionEffects::no_scroll(),
8118 window,
8119 cx,
8120 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8121 );
8122 })
8123 .ok();
8124
8125 window.dispatch_action(Box::new(RunToCursor), cx);
8126 })
8127 .separator()
8128 })
8129 .when_some(toggle_state_msg, |this, msg| {
8130 this.entry(msg, None, {
8131 let weak_editor = weak_editor.clone();
8132 let breakpoint = breakpoint.clone();
8133 move |_window, cx| {
8134 weak_editor
8135 .update(cx, |this, cx| {
8136 this.edit_breakpoint_at_anchor(
8137 anchor,
8138 breakpoint.as_ref().clone(),
8139 BreakpointEditAction::InvertState,
8140 cx,
8141 );
8142 })
8143 .log_err();
8144 }
8145 })
8146 })
8147 .entry(set_breakpoint_msg, None, {
8148 let weak_editor = weak_editor.clone();
8149 let breakpoint = breakpoint.clone();
8150 move |_window, cx| {
8151 weak_editor
8152 .update(cx, |this, cx| {
8153 this.edit_breakpoint_at_anchor(
8154 anchor,
8155 breakpoint.as_ref().clone(),
8156 BreakpointEditAction::Toggle,
8157 cx,
8158 );
8159 })
8160 .log_err();
8161 }
8162 })
8163 .entry(log_breakpoint_msg, None, {
8164 let breakpoint = breakpoint.clone();
8165 let weak_editor = weak_editor.clone();
8166 move |window, cx| {
8167 weak_editor
8168 .update(cx, |this, cx| {
8169 this.add_edit_breakpoint_block(
8170 anchor,
8171 breakpoint.as_ref(),
8172 BreakpointPromptEditAction::Log,
8173 window,
8174 cx,
8175 );
8176 })
8177 .log_err();
8178 }
8179 })
8180 .entry(condition_breakpoint_msg, None, {
8181 let breakpoint = breakpoint.clone();
8182 let weak_editor = weak_editor.clone();
8183 move |window, cx| {
8184 weak_editor
8185 .update(cx, |this, cx| {
8186 this.add_edit_breakpoint_block(
8187 anchor,
8188 breakpoint.as_ref(),
8189 BreakpointPromptEditAction::Condition,
8190 window,
8191 cx,
8192 );
8193 })
8194 .log_err();
8195 }
8196 })
8197 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8198 weak_editor
8199 .update(cx, |this, cx| {
8200 this.add_edit_breakpoint_block(
8201 anchor,
8202 breakpoint.as_ref(),
8203 BreakpointPromptEditAction::HitCondition,
8204 window,
8205 cx,
8206 );
8207 })
8208 .log_err();
8209 })
8210 })
8211 }
8212
8213 fn render_breakpoint(
8214 &self,
8215 position: Anchor,
8216 row: DisplayRow,
8217 breakpoint: &Breakpoint,
8218 state: Option<BreakpointSessionState>,
8219 cx: &mut Context<Self>,
8220 ) -> IconButton {
8221 let is_rejected = state.is_some_and(|s| !s.verified);
8222 // Is it a breakpoint that shows up when hovering over gutter?
8223 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8224 (false, false),
8225 |PhantomBreakpointIndicator {
8226 is_active,
8227 display_row,
8228 collides_with_existing_breakpoint,
8229 }| {
8230 (
8231 is_active && display_row == row,
8232 collides_with_existing_breakpoint,
8233 )
8234 },
8235 );
8236
8237 let (color, icon) = {
8238 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8239 (false, false) => ui::IconName::DebugBreakpoint,
8240 (true, false) => ui::IconName::DebugLogBreakpoint,
8241 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8242 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8243 };
8244
8245 let color = if is_phantom {
8246 Color::Hint
8247 } else if is_rejected {
8248 Color::Disabled
8249 } else {
8250 Color::Debugger
8251 };
8252
8253 (color, icon)
8254 };
8255
8256 let breakpoint = Arc::from(breakpoint.clone());
8257
8258 let alt_as_text = gpui::Keystroke {
8259 modifiers: Modifiers::secondary_key(),
8260 ..Default::default()
8261 };
8262 let primary_action_text = if breakpoint.is_disabled() {
8263 "Enable breakpoint"
8264 } else if is_phantom && !collides_with_existing {
8265 "Set breakpoint"
8266 } else {
8267 "Unset breakpoint"
8268 };
8269 let focus_handle = self.focus_handle.clone();
8270
8271 let meta = if is_rejected {
8272 SharedString::from("No executable code is associated with this line.")
8273 } else if collides_with_existing && !breakpoint.is_disabled() {
8274 SharedString::from(format!(
8275 "{alt_as_text}-click to disable,\nright-click for more options."
8276 ))
8277 } else {
8278 SharedString::from("Right-click for more options.")
8279 };
8280 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8281 .icon_size(IconSize::XSmall)
8282 .size(ui::ButtonSize::None)
8283 .when(is_rejected, |this| {
8284 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8285 })
8286 .icon_color(color)
8287 .style(ButtonStyle::Transparent)
8288 .on_click(cx.listener({
8289 move |editor, event: &ClickEvent, window, cx| {
8290 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8291 BreakpointEditAction::InvertState
8292 } else {
8293 BreakpointEditAction::Toggle
8294 };
8295
8296 window.focus(&editor.focus_handle(cx));
8297 editor.edit_breakpoint_at_anchor(
8298 position,
8299 breakpoint.as_ref().clone(),
8300 edit_action,
8301 cx,
8302 );
8303 }
8304 }))
8305 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8306 editor.set_breakpoint_context_menu(
8307 row,
8308 Some(position),
8309 event.position(),
8310 window,
8311 cx,
8312 );
8313 }))
8314 .tooltip(move |window, cx| {
8315 Tooltip::with_meta_in(
8316 primary_action_text,
8317 Some(&ToggleBreakpoint),
8318 meta.clone(),
8319 &focus_handle,
8320 window,
8321 cx,
8322 )
8323 })
8324 }
8325
8326 fn build_tasks_context(
8327 project: &Entity<Project>,
8328 buffer: &Entity<Buffer>,
8329 buffer_row: u32,
8330 tasks: &Arc<RunnableTasks>,
8331 cx: &mut Context<Self>,
8332 ) -> Task<Option<task::TaskContext>> {
8333 let position = Point::new(buffer_row, tasks.column);
8334 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8335 let location = Location {
8336 buffer: buffer.clone(),
8337 range: range_start..range_start,
8338 };
8339 // Fill in the environmental variables from the tree-sitter captures
8340 let mut captured_task_variables = TaskVariables::default();
8341 for (capture_name, value) in tasks.extra_variables.clone() {
8342 captured_task_variables.insert(
8343 task::VariableName::Custom(capture_name.into()),
8344 value.clone(),
8345 );
8346 }
8347 project.update(cx, |project, cx| {
8348 project.task_store().update(cx, |task_store, cx| {
8349 task_store.task_context_for_location(captured_task_variables, location, cx)
8350 })
8351 })
8352 }
8353
8354 pub fn spawn_nearest_task(
8355 &mut self,
8356 action: &SpawnNearestTask,
8357 window: &mut Window,
8358 cx: &mut Context<Self>,
8359 ) {
8360 let Some((workspace, _)) = self.workspace.clone() else {
8361 return;
8362 };
8363 let Some(project) = self.project.clone() else {
8364 return;
8365 };
8366
8367 // Try to find a closest, enclosing node using tree-sitter that has a task
8368 let Some((buffer, buffer_row, tasks)) = self
8369 .find_enclosing_node_task(cx)
8370 // Or find the task that's closest in row-distance.
8371 .or_else(|| self.find_closest_task(cx))
8372 else {
8373 return;
8374 };
8375
8376 let reveal_strategy = action.reveal;
8377 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8378 cx.spawn_in(window, async move |_, cx| {
8379 let context = task_context.await?;
8380 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8381
8382 let resolved = &mut resolved_task.resolved;
8383 resolved.reveal = reveal_strategy;
8384
8385 workspace
8386 .update_in(cx, |workspace, window, cx| {
8387 workspace.schedule_resolved_task(
8388 task_source_kind,
8389 resolved_task,
8390 false,
8391 window,
8392 cx,
8393 );
8394 })
8395 .ok()
8396 })
8397 .detach();
8398 }
8399
8400 fn find_closest_task(
8401 &mut self,
8402 cx: &mut Context<Self>,
8403 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8404 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8405
8406 let ((buffer_id, row), tasks) = self
8407 .tasks
8408 .iter()
8409 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8410
8411 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8412 let tasks = Arc::new(tasks.to_owned());
8413 Some((buffer, *row, tasks))
8414 }
8415
8416 fn find_enclosing_node_task(
8417 &mut self,
8418 cx: &mut Context<Self>,
8419 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8420 let snapshot = self.buffer.read(cx).snapshot(cx);
8421 let offset = self.selections.newest::<usize>(cx).head();
8422 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8423 let buffer_id = excerpt.buffer().remote_id();
8424
8425 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8426 let mut cursor = layer.node().walk();
8427
8428 while cursor.goto_first_child_for_byte(offset).is_some() {
8429 if cursor.node().end_byte() == offset {
8430 cursor.goto_next_sibling();
8431 }
8432 }
8433
8434 // Ascend to the smallest ancestor that contains the range and has a task.
8435 loop {
8436 let node = cursor.node();
8437 let node_range = node.byte_range();
8438 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8439
8440 // Check if this node contains our offset
8441 if node_range.start <= offset && node_range.end >= offset {
8442 // If it contains offset, check for task
8443 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8444 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8445 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8446 }
8447 }
8448
8449 if !cursor.goto_parent() {
8450 break;
8451 }
8452 }
8453 None
8454 }
8455
8456 fn render_run_indicator(
8457 &self,
8458 _style: &EditorStyle,
8459 is_active: bool,
8460 row: DisplayRow,
8461 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8462 cx: &mut Context<Self>,
8463 ) -> IconButton {
8464 let color = Color::Muted;
8465 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8466
8467 IconButton::new(
8468 ("run_indicator", row.0 as usize),
8469 ui::IconName::PlayOutlined,
8470 )
8471 .shape(ui::IconButtonShape::Square)
8472 .icon_size(IconSize::XSmall)
8473 .icon_color(color)
8474 .toggle_state(is_active)
8475 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8476 let quick_launch = match e {
8477 ClickEvent::Keyboard(_) => true,
8478 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8479 };
8480
8481 window.focus(&editor.focus_handle(cx));
8482 editor.toggle_code_actions(
8483 &ToggleCodeActions {
8484 deployed_from: Some(CodeActionSource::RunMenu(row)),
8485 quick_launch,
8486 },
8487 window,
8488 cx,
8489 );
8490 }))
8491 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8492 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8493 }))
8494 }
8495
8496 pub fn context_menu_visible(&self) -> bool {
8497 !self.edit_prediction_preview_is_active()
8498 && self
8499 .context_menu
8500 .borrow()
8501 .as_ref()
8502 .is_some_and(|menu| menu.visible())
8503 }
8504
8505 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8506 self.context_menu
8507 .borrow()
8508 .as_ref()
8509 .map(|menu| menu.origin())
8510 }
8511
8512 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8513 self.context_menu_options = Some(options);
8514 }
8515
8516 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8517 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8518
8519 fn render_edit_prediction_popover(
8520 &mut self,
8521 text_bounds: &Bounds<Pixels>,
8522 content_origin: gpui::Point<Pixels>,
8523 right_margin: Pixels,
8524 editor_snapshot: &EditorSnapshot,
8525 visible_row_range: Range<DisplayRow>,
8526 scroll_top: f32,
8527 scroll_bottom: f32,
8528 line_layouts: &[LineWithInvisibles],
8529 line_height: Pixels,
8530 scroll_pixel_position: gpui::Point<Pixels>,
8531 newest_selection_head: Option<DisplayPoint>,
8532 editor_width: Pixels,
8533 style: &EditorStyle,
8534 window: &mut Window,
8535 cx: &mut App,
8536 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8537 if self.mode().is_minimap() {
8538 return None;
8539 }
8540 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8541
8542 if self.edit_prediction_visible_in_cursor_popover(true) {
8543 return None;
8544 }
8545
8546 match &active_edit_prediction.completion {
8547 EditPrediction::Move { target, .. } => {
8548 let target_display_point = target.to_display_point(editor_snapshot);
8549
8550 if self.edit_prediction_requires_modifier() {
8551 if !self.edit_prediction_preview_is_active() {
8552 return None;
8553 }
8554
8555 self.render_edit_prediction_modifier_jump_popover(
8556 text_bounds,
8557 content_origin,
8558 visible_row_range,
8559 line_layouts,
8560 line_height,
8561 scroll_pixel_position,
8562 newest_selection_head,
8563 target_display_point,
8564 window,
8565 cx,
8566 )
8567 } else {
8568 self.render_edit_prediction_eager_jump_popover(
8569 text_bounds,
8570 content_origin,
8571 editor_snapshot,
8572 visible_row_range,
8573 scroll_top,
8574 scroll_bottom,
8575 line_height,
8576 scroll_pixel_position,
8577 target_display_point,
8578 editor_width,
8579 window,
8580 cx,
8581 )
8582 }
8583 }
8584 EditPrediction::Edit {
8585 display_mode: EditDisplayMode::Inline,
8586 ..
8587 } => None,
8588 EditPrediction::Edit {
8589 display_mode: EditDisplayMode::TabAccept,
8590 edits,
8591 ..
8592 } => {
8593 let range = &edits.first()?.0;
8594 let target_display_point = range.end.to_display_point(editor_snapshot);
8595
8596 self.render_edit_prediction_end_of_line_popover(
8597 "Accept",
8598 editor_snapshot,
8599 visible_row_range,
8600 target_display_point,
8601 line_height,
8602 scroll_pixel_position,
8603 content_origin,
8604 editor_width,
8605 window,
8606 cx,
8607 )
8608 }
8609 EditPrediction::Edit {
8610 edits,
8611 edit_preview,
8612 display_mode: EditDisplayMode::DiffPopover,
8613 snapshot,
8614 } => self.render_edit_prediction_diff_popover(
8615 text_bounds,
8616 content_origin,
8617 right_margin,
8618 editor_snapshot,
8619 visible_row_range,
8620 line_layouts,
8621 line_height,
8622 scroll_pixel_position,
8623 newest_selection_head,
8624 editor_width,
8625 style,
8626 edits,
8627 edit_preview,
8628 snapshot,
8629 window,
8630 cx,
8631 ),
8632 }
8633 }
8634
8635 fn render_edit_prediction_modifier_jump_popover(
8636 &mut self,
8637 text_bounds: &Bounds<Pixels>,
8638 content_origin: gpui::Point<Pixels>,
8639 visible_row_range: Range<DisplayRow>,
8640 line_layouts: &[LineWithInvisibles],
8641 line_height: Pixels,
8642 scroll_pixel_position: gpui::Point<Pixels>,
8643 newest_selection_head: Option<DisplayPoint>,
8644 target_display_point: DisplayPoint,
8645 window: &mut Window,
8646 cx: &mut App,
8647 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8648 let scrolled_content_origin =
8649 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8650
8651 const SCROLL_PADDING_Y: Pixels = px(12.);
8652
8653 if target_display_point.row() < visible_row_range.start {
8654 return self.render_edit_prediction_scroll_popover(
8655 |_| SCROLL_PADDING_Y,
8656 IconName::ArrowUp,
8657 visible_row_range,
8658 line_layouts,
8659 newest_selection_head,
8660 scrolled_content_origin,
8661 window,
8662 cx,
8663 );
8664 } else if target_display_point.row() >= visible_row_range.end {
8665 return self.render_edit_prediction_scroll_popover(
8666 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8667 IconName::ArrowDown,
8668 visible_row_range,
8669 line_layouts,
8670 newest_selection_head,
8671 scrolled_content_origin,
8672 window,
8673 cx,
8674 );
8675 }
8676
8677 const POLE_WIDTH: Pixels = px(2.);
8678
8679 let line_layout =
8680 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8681 let target_column = target_display_point.column() as usize;
8682
8683 let target_x = line_layout.x_for_index(target_column);
8684 let target_y =
8685 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8686
8687 let flag_on_right = target_x < text_bounds.size.width / 2.;
8688
8689 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8690 border_color.l += 0.001;
8691
8692 let mut element = v_flex()
8693 .items_end()
8694 .when(flag_on_right, |el| el.items_start())
8695 .child(if flag_on_right {
8696 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8697 .rounded_bl(px(0.))
8698 .rounded_tl(px(0.))
8699 .border_l_2()
8700 .border_color(border_color)
8701 } else {
8702 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8703 .rounded_br(px(0.))
8704 .rounded_tr(px(0.))
8705 .border_r_2()
8706 .border_color(border_color)
8707 })
8708 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8709 .into_any();
8710
8711 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8712
8713 let mut origin = scrolled_content_origin + point(target_x, target_y)
8714 - point(
8715 if flag_on_right {
8716 POLE_WIDTH
8717 } else {
8718 size.width - POLE_WIDTH
8719 },
8720 size.height - line_height,
8721 );
8722
8723 origin.x = origin.x.max(content_origin.x);
8724
8725 element.prepaint_at(origin, window, cx);
8726
8727 Some((element, origin))
8728 }
8729
8730 fn render_edit_prediction_scroll_popover(
8731 &mut self,
8732 to_y: impl Fn(Size<Pixels>) -> Pixels,
8733 scroll_icon: IconName,
8734 visible_row_range: Range<DisplayRow>,
8735 line_layouts: &[LineWithInvisibles],
8736 newest_selection_head: Option<DisplayPoint>,
8737 scrolled_content_origin: gpui::Point<Pixels>,
8738 window: &mut Window,
8739 cx: &mut App,
8740 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8741 let mut element = self
8742 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8743 .into_any();
8744
8745 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8746
8747 let cursor = newest_selection_head?;
8748 let cursor_row_layout =
8749 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8750 let cursor_column = cursor.column() as usize;
8751
8752 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8753
8754 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8755
8756 element.prepaint_at(origin, window, cx);
8757 Some((element, origin))
8758 }
8759
8760 fn render_edit_prediction_eager_jump_popover(
8761 &mut self,
8762 text_bounds: &Bounds<Pixels>,
8763 content_origin: gpui::Point<Pixels>,
8764 editor_snapshot: &EditorSnapshot,
8765 visible_row_range: Range<DisplayRow>,
8766 scroll_top: f32,
8767 scroll_bottom: f32,
8768 line_height: Pixels,
8769 scroll_pixel_position: gpui::Point<Pixels>,
8770 target_display_point: DisplayPoint,
8771 editor_width: Pixels,
8772 window: &mut Window,
8773 cx: &mut App,
8774 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8775 if target_display_point.row().as_f32() < scroll_top {
8776 let mut element = self
8777 .render_edit_prediction_line_popover(
8778 "Jump to Edit",
8779 Some(IconName::ArrowUp),
8780 window,
8781 cx,
8782 )?
8783 .into_any();
8784
8785 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8786 let offset = point(
8787 (text_bounds.size.width - size.width) / 2.,
8788 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8789 );
8790
8791 let origin = text_bounds.origin + offset;
8792 element.prepaint_at(origin, window, cx);
8793 Some((element, origin))
8794 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8795 let mut element = self
8796 .render_edit_prediction_line_popover(
8797 "Jump to Edit",
8798 Some(IconName::ArrowDown),
8799 window,
8800 cx,
8801 )?
8802 .into_any();
8803
8804 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8805 let offset = point(
8806 (text_bounds.size.width - size.width) / 2.,
8807 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8808 );
8809
8810 let origin = text_bounds.origin + offset;
8811 element.prepaint_at(origin, window, cx);
8812 Some((element, origin))
8813 } else {
8814 self.render_edit_prediction_end_of_line_popover(
8815 "Jump to Edit",
8816 editor_snapshot,
8817 visible_row_range,
8818 target_display_point,
8819 line_height,
8820 scroll_pixel_position,
8821 content_origin,
8822 editor_width,
8823 window,
8824 cx,
8825 )
8826 }
8827 }
8828
8829 fn render_edit_prediction_end_of_line_popover(
8830 self: &mut Editor,
8831 label: &'static str,
8832 editor_snapshot: &EditorSnapshot,
8833 visible_row_range: Range<DisplayRow>,
8834 target_display_point: DisplayPoint,
8835 line_height: Pixels,
8836 scroll_pixel_position: gpui::Point<Pixels>,
8837 content_origin: gpui::Point<Pixels>,
8838 editor_width: Pixels,
8839 window: &mut Window,
8840 cx: &mut App,
8841 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8842 let target_line_end = DisplayPoint::new(
8843 target_display_point.row(),
8844 editor_snapshot.line_len(target_display_point.row()),
8845 );
8846
8847 let mut element = self
8848 .render_edit_prediction_line_popover(label, None, window, cx)?
8849 .into_any();
8850
8851 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8852
8853 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8854
8855 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8856 let mut origin = start_point
8857 + line_origin
8858 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8859 origin.x = origin.x.max(content_origin.x);
8860
8861 let max_x = content_origin.x + editor_width - size.width;
8862
8863 if origin.x > max_x {
8864 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8865
8866 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8867 origin.y += offset;
8868 IconName::ArrowUp
8869 } else {
8870 origin.y -= offset;
8871 IconName::ArrowDown
8872 };
8873
8874 element = self
8875 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8876 .into_any();
8877
8878 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8879
8880 origin.x = content_origin.x + editor_width - size.width - px(2.);
8881 }
8882
8883 element.prepaint_at(origin, window, cx);
8884 Some((element, origin))
8885 }
8886
8887 fn render_edit_prediction_diff_popover(
8888 self: &Editor,
8889 text_bounds: &Bounds<Pixels>,
8890 content_origin: gpui::Point<Pixels>,
8891 right_margin: Pixels,
8892 editor_snapshot: &EditorSnapshot,
8893 visible_row_range: Range<DisplayRow>,
8894 line_layouts: &[LineWithInvisibles],
8895 line_height: Pixels,
8896 scroll_pixel_position: gpui::Point<Pixels>,
8897 newest_selection_head: Option<DisplayPoint>,
8898 editor_width: Pixels,
8899 style: &EditorStyle,
8900 edits: &Vec<(Range<Anchor>, String)>,
8901 edit_preview: &Option<language::EditPreview>,
8902 snapshot: &language::BufferSnapshot,
8903 window: &mut Window,
8904 cx: &mut App,
8905 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8906 let edit_start = edits
8907 .first()
8908 .unwrap()
8909 .0
8910 .start
8911 .to_display_point(editor_snapshot);
8912 let edit_end = edits
8913 .last()
8914 .unwrap()
8915 .0
8916 .end
8917 .to_display_point(editor_snapshot);
8918
8919 let is_visible = visible_row_range.contains(&edit_start.row())
8920 || visible_row_range.contains(&edit_end.row());
8921 if !is_visible {
8922 return None;
8923 }
8924
8925 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8926 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8927 } else {
8928 // Fallback for providers without edit_preview
8929 crate::edit_prediction_fallback_text(edits, cx)
8930 };
8931
8932 let styled_text = highlighted_edits.to_styled_text(&style.text);
8933 let line_count = highlighted_edits.text.lines().count();
8934
8935 const BORDER_WIDTH: Pixels = px(1.);
8936
8937 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8938 let has_keybind = keybind.is_some();
8939
8940 let mut element = h_flex()
8941 .items_start()
8942 .child(
8943 h_flex()
8944 .bg(cx.theme().colors().editor_background)
8945 .border(BORDER_WIDTH)
8946 .shadow_xs()
8947 .border_color(cx.theme().colors().border)
8948 .rounded_l_lg()
8949 .when(line_count > 1, |el| el.rounded_br_lg())
8950 .pr_1()
8951 .child(styled_text),
8952 )
8953 .child(
8954 h_flex()
8955 .h(line_height + BORDER_WIDTH * 2.)
8956 .px_1p5()
8957 .gap_1()
8958 // Workaround: For some reason, there's a gap if we don't do this
8959 .ml(-BORDER_WIDTH)
8960 .shadow(vec![gpui::BoxShadow {
8961 color: gpui::black().opacity(0.05),
8962 offset: point(px(1.), px(1.)),
8963 blur_radius: px(2.),
8964 spread_radius: px(0.),
8965 }])
8966 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8967 .border(BORDER_WIDTH)
8968 .border_color(cx.theme().colors().border)
8969 .rounded_r_lg()
8970 .id("edit_prediction_diff_popover_keybind")
8971 .when(!has_keybind, |el| {
8972 let status_colors = cx.theme().status();
8973
8974 el.bg(status_colors.error_background)
8975 .border_color(status_colors.error.opacity(0.6))
8976 .child(Icon::new(IconName::Info).color(Color::Error))
8977 .cursor_default()
8978 .hoverable_tooltip(move |_window, cx| {
8979 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8980 })
8981 })
8982 .children(keybind),
8983 )
8984 .into_any();
8985
8986 let longest_row =
8987 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8988 let longest_line_width = if visible_row_range.contains(&longest_row) {
8989 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8990 } else {
8991 layout_line(
8992 longest_row,
8993 editor_snapshot,
8994 style,
8995 editor_width,
8996 |_| false,
8997 window,
8998 cx,
8999 )
9000 .width
9001 };
9002
9003 let viewport_bounds =
9004 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9005 right: -right_margin,
9006 ..Default::default()
9007 });
9008
9009 let x_after_longest =
9010 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9011 - scroll_pixel_position.x;
9012
9013 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9014
9015 // Fully visible if it can be displayed within the window (allow overlapping other
9016 // panes). However, this is only allowed if the popover starts within text_bounds.
9017 let can_position_to_the_right = x_after_longest < text_bounds.right()
9018 && x_after_longest + element_bounds.width < viewport_bounds.right();
9019
9020 let mut origin = if can_position_to_the_right {
9021 point(
9022 x_after_longest,
9023 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9024 - scroll_pixel_position.y,
9025 )
9026 } else {
9027 let cursor_row = newest_selection_head.map(|head| head.row());
9028 let above_edit = edit_start
9029 .row()
9030 .0
9031 .checked_sub(line_count as u32)
9032 .map(DisplayRow);
9033 let below_edit = Some(edit_end.row() + 1);
9034 let above_cursor =
9035 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9036 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9037
9038 // Place the edit popover adjacent to the edit if there is a location
9039 // available that is onscreen and does not obscure the cursor. Otherwise,
9040 // place it adjacent to the cursor.
9041 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9042 .into_iter()
9043 .flatten()
9044 .find(|&start_row| {
9045 let end_row = start_row + line_count as u32;
9046 visible_row_range.contains(&start_row)
9047 && visible_row_range.contains(&end_row)
9048 && cursor_row
9049 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9050 })?;
9051
9052 content_origin
9053 + point(
9054 -scroll_pixel_position.x,
9055 row_target.as_f32() * line_height - scroll_pixel_position.y,
9056 )
9057 };
9058
9059 origin.x -= BORDER_WIDTH;
9060
9061 window.defer_draw(element, origin, 1);
9062
9063 // Do not return an element, since it will already be drawn due to defer_draw.
9064 None
9065 }
9066
9067 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9068 px(30.)
9069 }
9070
9071 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9072 if self.read_only(cx) {
9073 cx.theme().players().read_only()
9074 } else {
9075 self.style.as_ref().unwrap().local_player
9076 }
9077 }
9078
9079 fn render_edit_prediction_accept_keybind(
9080 &self,
9081 window: &mut Window,
9082 cx: &App,
9083 ) -> Option<AnyElement> {
9084 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9085 let accept_keystroke = accept_binding.keystroke()?;
9086
9087 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9088
9089 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9090 Color::Accent
9091 } else {
9092 Color::Muted
9093 };
9094
9095 h_flex()
9096 .px_0p5()
9097 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9098 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9099 .text_size(TextSize::XSmall.rems(cx))
9100 .child(h_flex().children(ui::render_modifiers(
9101 accept_keystroke.modifiers(),
9102 PlatformStyle::platform(),
9103 Some(modifiers_color),
9104 Some(IconSize::XSmall.rems().into()),
9105 true,
9106 )))
9107 .when(is_platform_style_mac, |parent| {
9108 parent.child(accept_keystroke.key().to_string())
9109 })
9110 .when(!is_platform_style_mac, |parent| {
9111 parent.child(
9112 Key::new(
9113 util::capitalize(accept_keystroke.key()),
9114 Some(Color::Default),
9115 )
9116 .size(Some(IconSize::XSmall.rems().into())),
9117 )
9118 })
9119 .into_any()
9120 .into()
9121 }
9122
9123 fn render_edit_prediction_line_popover(
9124 &self,
9125 label: impl Into<SharedString>,
9126 icon: Option<IconName>,
9127 window: &mut Window,
9128 cx: &App,
9129 ) -> Option<Stateful<Div>> {
9130 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9131
9132 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9133 let has_keybind = keybind.is_some();
9134
9135 let result = h_flex()
9136 .id("ep-line-popover")
9137 .py_0p5()
9138 .pl_1()
9139 .pr(padding_right)
9140 .gap_1()
9141 .rounded_md()
9142 .border_1()
9143 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9144 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9145 .shadow_xs()
9146 .when(!has_keybind, |el| {
9147 let status_colors = cx.theme().status();
9148
9149 el.bg(status_colors.error_background)
9150 .border_color(status_colors.error.opacity(0.6))
9151 .pl_2()
9152 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9153 .cursor_default()
9154 .hoverable_tooltip(move |_window, cx| {
9155 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9156 })
9157 })
9158 .children(keybind)
9159 .child(
9160 Label::new(label)
9161 .size(LabelSize::Small)
9162 .when(!has_keybind, |el| {
9163 el.color(cx.theme().status().error.into()).strikethrough()
9164 }),
9165 )
9166 .when(!has_keybind, |el| {
9167 el.child(
9168 h_flex().ml_1().child(
9169 Icon::new(IconName::Info)
9170 .size(IconSize::Small)
9171 .color(cx.theme().status().error.into()),
9172 ),
9173 )
9174 })
9175 .when_some(icon, |element, icon| {
9176 element.child(
9177 div()
9178 .mt(px(1.5))
9179 .child(Icon::new(icon).size(IconSize::Small)),
9180 )
9181 });
9182
9183 Some(result)
9184 }
9185
9186 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9187 let accent_color = cx.theme().colors().text_accent;
9188 let editor_bg_color = cx.theme().colors().editor_background;
9189 editor_bg_color.blend(accent_color.opacity(0.1))
9190 }
9191
9192 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9193 let accent_color = cx.theme().colors().text_accent;
9194 let editor_bg_color = cx.theme().colors().editor_background;
9195 editor_bg_color.blend(accent_color.opacity(0.6))
9196 }
9197 fn get_prediction_provider_icon_name(
9198 provider: &Option<RegisteredEditPredictionProvider>,
9199 ) -> IconName {
9200 match provider {
9201 Some(provider) => match provider.provider.name() {
9202 "copilot" => IconName::Copilot,
9203 "supermaven" => IconName::Supermaven,
9204 _ => IconName::ZedPredict,
9205 },
9206 None => IconName::ZedPredict,
9207 }
9208 }
9209
9210 fn render_edit_prediction_cursor_popover(
9211 &self,
9212 min_width: Pixels,
9213 max_width: Pixels,
9214 cursor_point: Point,
9215 style: &EditorStyle,
9216 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9217 _window: &Window,
9218 cx: &mut Context<Editor>,
9219 ) -> Option<AnyElement> {
9220 let provider = self.edit_prediction_provider.as_ref()?;
9221 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9222
9223 let is_refreshing = provider.provider.is_refreshing(cx);
9224
9225 fn pending_completion_container(icon: IconName) -> Div {
9226 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9227 }
9228
9229 let completion = match &self.active_edit_prediction {
9230 Some(prediction) => {
9231 if !self.has_visible_completions_menu() {
9232 const RADIUS: Pixels = px(6.);
9233 const BORDER_WIDTH: Pixels = px(1.);
9234
9235 return Some(
9236 h_flex()
9237 .elevation_2(cx)
9238 .border(BORDER_WIDTH)
9239 .border_color(cx.theme().colors().border)
9240 .when(accept_keystroke.is_none(), |el| {
9241 el.border_color(cx.theme().status().error)
9242 })
9243 .rounded(RADIUS)
9244 .rounded_tl(px(0.))
9245 .overflow_hidden()
9246 .child(div().px_1p5().child(match &prediction.completion {
9247 EditPrediction::Move { target, snapshot } => {
9248 use text::ToPoint as _;
9249 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9250 {
9251 Icon::new(IconName::ZedPredictDown)
9252 } else {
9253 Icon::new(IconName::ZedPredictUp)
9254 }
9255 }
9256 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9257 }))
9258 .child(
9259 h_flex()
9260 .gap_1()
9261 .py_1()
9262 .px_2()
9263 .rounded_r(RADIUS - BORDER_WIDTH)
9264 .border_l_1()
9265 .border_color(cx.theme().colors().border)
9266 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9267 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9268 el.child(
9269 Label::new("Hold")
9270 .size(LabelSize::Small)
9271 .when(accept_keystroke.is_none(), |el| {
9272 el.strikethrough()
9273 })
9274 .line_height_style(LineHeightStyle::UiLabel),
9275 )
9276 })
9277 .id("edit_prediction_cursor_popover_keybind")
9278 .when(accept_keystroke.is_none(), |el| {
9279 let status_colors = cx.theme().status();
9280
9281 el.bg(status_colors.error_background)
9282 .border_color(status_colors.error.opacity(0.6))
9283 .child(Icon::new(IconName::Info).color(Color::Error))
9284 .cursor_default()
9285 .hoverable_tooltip(move |_window, cx| {
9286 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9287 .into()
9288 })
9289 })
9290 .when_some(
9291 accept_keystroke.as_ref(),
9292 |el, accept_keystroke| {
9293 el.child(h_flex().children(ui::render_modifiers(
9294 accept_keystroke.modifiers(),
9295 PlatformStyle::platform(),
9296 Some(Color::Default),
9297 Some(IconSize::XSmall.rems().into()),
9298 false,
9299 )))
9300 },
9301 ),
9302 )
9303 .into_any(),
9304 );
9305 }
9306
9307 self.render_edit_prediction_cursor_popover_preview(
9308 prediction,
9309 cursor_point,
9310 style,
9311 cx,
9312 )?
9313 }
9314
9315 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9316 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9317 stale_completion,
9318 cursor_point,
9319 style,
9320 cx,
9321 )?,
9322
9323 None => pending_completion_container(provider_icon)
9324 .child(Label::new("...").size(LabelSize::Small)),
9325 },
9326
9327 None => pending_completion_container(provider_icon)
9328 .child(Label::new("...").size(LabelSize::Small)),
9329 };
9330
9331 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9332 completion
9333 .with_animation(
9334 "loading-completion",
9335 Animation::new(Duration::from_secs(2))
9336 .repeat()
9337 .with_easing(pulsating_between(0.4, 0.8)),
9338 |label, delta| label.opacity(delta),
9339 )
9340 .into_any_element()
9341 } else {
9342 completion.into_any_element()
9343 };
9344
9345 let has_completion = self.active_edit_prediction.is_some();
9346
9347 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9348 Some(
9349 h_flex()
9350 .min_w(min_width)
9351 .max_w(max_width)
9352 .flex_1()
9353 .elevation_2(cx)
9354 .border_color(cx.theme().colors().border)
9355 .child(
9356 div()
9357 .flex_1()
9358 .py_1()
9359 .px_2()
9360 .overflow_hidden()
9361 .child(completion),
9362 )
9363 .when_some(accept_keystroke, |el, accept_keystroke| {
9364 if !accept_keystroke.modifiers().modified() {
9365 return el;
9366 }
9367
9368 el.child(
9369 h_flex()
9370 .h_full()
9371 .border_l_1()
9372 .rounded_r_lg()
9373 .border_color(cx.theme().colors().border)
9374 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9375 .gap_1()
9376 .py_1()
9377 .px_2()
9378 .child(
9379 h_flex()
9380 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9381 .when(is_platform_style_mac, |parent| parent.gap_1())
9382 .child(h_flex().children(ui::render_modifiers(
9383 accept_keystroke.modifiers(),
9384 PlatformStyle::platform(),
9385 Some(if !has_completion {
9386 Color::Muted
9387 } else {
9388 Color::Default
9389 }),
9390 None,
9391 false,
9392 ))),
9393 )
9394 .child(Label::new("Preview").into_any_element())
9395 .opacity(if has_completion { 1.0 } else { 0.4 }),
9396 )
9397 })
9398 .into_any(),
9399 )
9400 }
9401
9402 fn render_edit_prediction_cursor_popover_preview(
9403 &self,
9404 completion: &EditPredictionState,
9405 cursor_point: Point,
9406 style: &EditorStyle,
9407 cx: &mut Context<Editor>,
9408 ) -> Option<Div> {
9409 use text::ToPoint as _;
9410
9411 fn render_relative_row_jump(
9412 prefix: impl Into<String>,
9413 current_row: u32,
9414 target_row: u32,
9415 ) -> Div {
9416 let (row_diff, arrow) = if target_row < current_row {
9417 (current_row - target_row, IconName::ArrowUp)
9418 } else {
9419 (target_row - current_row, IconName::ArrowDown)
9420 };
9421
9422 h_flex()
9423 .child(
9424 Label::new(format!("{}{}", prefix.into(), row_diff))
9425 .color(Color::Muted)
9426 .size(LabelSize::Small),
9427 )
9428 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9429 }
9430
9431 let supports_jump = self
9432 .edit_prediction_provider
9433 .as_ref()
9434 .map(|provider| provider.provider.supports_jump_to_edit())
9435 .unwrap_or(true);
9436
9437 match &completion.completion {
9438 EditPrediction::Move {
9439 target, snapshot, ..
9440 } => {
9441 if !supports_jump {
9442 return None;
9443 }
9444
9445 Some(
9446 h_flex()
9447 .px_2()
9448 .gap_2()
9449 .flex_1()
9450 .child(
9451 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9452 Icon::new(IconName::ZedPredictDown)
9453 } else {
9454 Icon::new(IconName::ZedPredictUp)
9455 },
9456 )
9457 .child(Label::new("Jump to Edit")),
9458 )
9459 }
9460
9461 EditPrediction::Edit {
9462 edits,
9463 edit_preview,
9464 snapshot,
9465 display_mode: _,
9466 } => {
9467 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9468
9469 let (highlighted_edits, has_more_lines) =
9470 if let Some(edit_preview) = edit_preview.as_ref() {
9471 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9472 .first_line_preview()
9473 } else {
9474 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9475 };
9476
9477 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9478 .with_default_highlights(&style.text, highlighted_edits.highlights);
9479
9480 let preview = h_flex()
9481 .gap_1()
9482 .min_w_16()
9483 .child(styled_text)
9484 .when(has_more_lines, |parent| parent.child("…"));
9485
9486 let left = if supports_jump && first_edit_row != cursor_point.row {
9487 render_relative_row_jump("", cursor_point.row, first_edit_row)
9488 .into_any_element()
9489 } else {
9490 let icon_name =
9491 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9492 Icon::new(icon_name).into_any_element()
9493 };
9494
9495 Some(
9496 h_flex()
9497 .h_full()
9498 .flex_1()
9499 .gap_2()
9500 .pr_1()
9501 .overflow_x_hidden()
9502 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9503 .child(left)
9504 .child(preview),
9505 )
9506 }
9507 }
9508 }
9509
9510 pub fn render_context_menu(
9511 &self,
9512 style: &EditorStyle,
9513 max_height_in_lines: u32,
9514 window: &mut Window,
9515 cx: &mut Context<Editor>,
9516 ) -> Option<AnyElement> {
9517 let menu = self.context_menu.borrow();
9518 let menu = menu.as_ref()?;
9519 if !menu.visible() {
9520 return None;
9521 };
9522 Some(menu.render(style, max_height_in_lines, window, cx))
9523 }
9524
9525 fn render_context_menu_aside(
9526 &mut self,
9527 max_size: Size<Pixels>,
9528 window: &mut Window,
9529 cx: &mut Context<Editor>,
9530 ) -> Option<AnyElement> {
9531 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9532 if menu.visible() {
9533 menu.render_aside(max_size, window, cx)
9534 } else {
9535 None
9536 }
9537 })
9538 }
9539
9540 fn hide_context_menu(
9541 &mut self,
9542 window: &mut Window,
9543 cx: &mut Context<Self>,
9544 ) -> Option<CodeContextMenu> {
9545 cx.notify();
9546 self.completion_tasks.clear();
9547 let context_menu = self.context_menu.borrow_mut().take();
9548 self.stale_edit_prediction_in_menu.take();
9549 self.update_visible_edit_prediction(window, cx);
9550 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9551 && let Some(completion_provider) = &self.completion_provider
9552 {
9553 completion_provider.selection_changed(None, window, cx);
9554 }
9555 context_menu
9556 }
9557
9558 fn show_snippet_choices(
9559 &mut self,
9560 choices: &Vec<String>,
9561 selection: Range<Anchor>,
9562 cx: &mut Context<Self>,
9563 ) {
9564 let Some((_, buffer, _)) = self
9565 .buffer()
9566 .read(cx)
9567 .excerpt_containing(selection.start, cx)
9568 else {
9569 return;
9570 };
9571 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9572 else {
9573 return;
9574 };
9575 if buffer != end_buffer {
9576 log::error!("expected anchor range to have matching buffer IDs");
9577 return;
9578 }
9579
9580 let id = post_inc(&mut self.next_completion_id);
9581 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9582 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9583 CompletionsMenu::new_snippet_choices(
9584 id,
9585 true,
9586 choices,
9587 selection,
9588 buffer,
9589 snippet_sort_order,
9590 ),
9591 ));
9592 }
9593
9594 pub fn insert_snippet(
9595 &mut self,
9596 insertion_ranges: &[Range<usize>],
9597 snippet: Snippet,
9598 window: &mut Window,
9599 cx: &mut Context<Self>,
9600 ) -> Result<()> {
9601 struct Tabstop<T> {
9602 is_end_tabstop: bool,
9603 ranges: Vec<Range<T>>,
9604 choices: Option<Vec<String>>,
9605 }
9606
9607 let tabstops = self.buffer.update(cx, |buffer, cx| {
9608 let snippet_text: Arc<str> = snippet.text.clone().into();
9609 let edits = insertion_ranges
9610 .iter()
9611 .cloned()
9612 .map(|range| (range, snippet_text.clone()));
9613 let autoindent_mode = AutoindentMode::Block {
9614 original_indent_columns: Vec::new(),
9615 };
9616 buffer.edit(edits, Some(autoindent_mode), cx);
9617
9618 let snapshot = &*buffer.read(cx);
9619 let snippet = &snippet;
9620 snippet
9621 .tabstops
9622 .iter()
9623 .map(|tabstop| {
9624 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9625 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9626 });
9627 let mut tabstop_ranges = tabstop
9628 .ranges
9629 .iter()
9630 .flat_map(|tabstop_range| {
9631 let mut delta = 0_isize;
9632 insertion_ranges.iter().map(move |insertion_range| {
9633 let insertion_start = insertion_range.start as isize + delta;
9634 delta +=
9635 snippet.text.len() as isize - insertion_range.len() as isize;
9636
9637 let start = ((insertion_start + tabstop_range.start) as usize)
9638 .min(snapshot.len());
9639 let end = ((insertion_start + tabstop_range.end) as usize)
9640 .min(snapshot.len());
9641 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9642 })
9643 })
9644 .collect::<Vec<_>>();
9645 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9646
9647 Tabstop {
9648 is_end_tabstop,
9649 ranges: tabstop_ranges,
9650 choices: tabstop.choices.clone(),
9651 }
9652 })
9653 .collect::<Vec<_>>()
9654 });
9655 if let Some(tabstop) = tabstops.first() {
9656 self.change_selections(Default::default(), window, cx, |s| {
9657 // Reverse order so that the first range is the newest created selection.
9658 // Completions will use it and autoscroll will prioritize it.
9659 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9660 });
9661
9662 if let Some(choices) = &tabstop.choices
9663 && let Some(selection) = tabstop.ranges.first()
9664 {
9665 self.show_snippet_choices(choices, selection.clone(), cx)
9666 }
9667
9668 // If we're already at the last tabstop and it's at the end of the snippet,
9669 // we're done, we don't need to keep the state around.
9670 if !tabstop.is_end_tabstop {
9671 let choices = tabstops
9672 .iter()
9673 .map(|tabstop| tabstop.choices.clone())
9674 .collect();
9675
9676 let ranges = tabstops
9677 .into_iter()
9678 .map(|tabstop| tabstop.ranges)
9679 .collect::<Vec<_>>();
9680
9681 self.snippet_stack.push(SnippetState {
9682 active_index: 0,
9683 ranges,
9684 choices,
9685 });
9686 }
9687
9688 // Check whether the just-entered snippet ends with an auto-closable bracket.
9689 if self.autoclose_regions.is_empty() {
9690 let snapshot = self.buffer.read(cx).snapshot(cx);
9691 let mut all_selections = self.selections.all::<Point>(cx);
9692 for selection in &mut all_selections {
9693 let selection_head = selection.head();
9694 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9695 continue;
9696 };
9697
9698 let mut bracket_pair = None;
9699 let max_lookup_length = scope
9700 .brackets()
9701 .map(|(pair, _)| {
9702 pair.start
9703 .as_str()
9704 .chars()
9705 .count()
9706 .max(pair.end.as_str().chars().count())
9707 })
9708 .max();
9709 if let Some(max_lookup_length) = max_lookup_length {
9710 let next_text = snapshot
9711 .chars_at(selection_head)
9712 .take(max_lookup_length)
9713 .collect::<String>();
9714 let prev_text = snapshot
9715 .reversed_chars_at(selection_head)
9716 .take(max_lookup_length)
9717 .collect::<String>();
9718
9719 for (pair, enabled) in scope.brackets() {
9720 if enabled
9721 && pair.close
9722 && prev_text.starts_with(pair.start.as_str())
9723 && next_text.starts_with(pair.end.as_str())
9724 {
9725 bracket_pair = Some(pair.clone());
9726 break;
9727 }
9728 }
9729 }
9730
9731 if let Some(pair) = bracket_pair {
9732 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9733 let autoclose_enabled =
9734 self.use_autoclose && snapshot_settings.use_autoclose;
9735 if autoclose_enabled {
9736 let start = snapshot.anchor_after(selection_head);
9737 let end = snapshot.anchor_after(selection_head);
9738 self.autoclose_regions.push(AutocloseRegion {
9739 selection_id: selection.id,
9740 range: start..end,
9741 pair,
9742 });
9743 }
9744 }
9745 }
9746 }
9747 }
9748 Ok(())
9749 }
9750
9751 pub fn move_to_next_snippet_tabstop(
9752 &mut self,
9753 window: &mut Window,
9754 cx: &mut Context<Self>,
9755 ) -> bool {
9756 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9757 }
9758
9759 pub fn move_to_prev_snippet_tabstop(
9760 &mut self,
9761 window: &mut Window,
9762 cx: &mut Context<Self>,
9763 ) -> bool {
9764 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9765 }
9766
9767 pub fn move_to_snippet_tabstop(
9768 &mut self,
9769 bias: Bias,
9770 window: &mut Window,
9771 cx: &mut Context<Self>,
9772 ) -> bool {
9773 if let Some(mut snippet) = self.snippet_stack.pop() {
9774 match bias {
9775 Bias::Left => {
9776 if snippet.active_index > 0 {
9777 snippet.active_index -= 1;
9778 } else {
9779 self.snippet_stack.push(snippet);
9780 return false;
9781 }
9782 }
9783 Bias::Right => {
9784 if snippet.active_index + 1 < snippet.ranges.len() {
9785 snippet.active_index += 1;
9786 } else {
9787 self.snippet_stack.push(snippet);
9788 return false;
9789 }
9790 }
9791 }
9792 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9793 self.change_selections(Default::default(), window, cx, |s| {
9794 // Reverse order so that the first range is the newest created selection.
9795 // Completions will use it and autoscroll will prioritize it.
9796 s.select_ranges(current_ranges.iter().rev().cloned())
9797 });
9798
9799 if let Some(choices) = &snippet.choices[snippet.active_index]
9800 && let Some(selection) = current_ranges.first()
9801 {
9802 self.show_snippet_choices(choices, selection.clone(), cx);
9803 }
9804
9805 // If snippet state is not at the last tabstop, push it back on the stack
9806 if snippet.active_index + 1 < snippet.ranges.len() {
9807 self.snippet_stack.push(snippet);
9808 }
9809 return true;
9810 }
9811 }
9812
9813 false
9814 }
9815
9816 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9817 self.transact(window, cx, |this, window, cx| {
9818 this.select_all(&SelectAll, window, cx);
9819 this.insert("", window, cx);
9820 });
9821 }
9822
9823 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9824 if self.read_only(cx) {
9825 return;
9826 }
9827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9828 self.transact(window, cx, |this, window, cx| {
9829 this.select_autoclose_pair(window, cx);
9830 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9831 if !this.linked_edit_ranges.is_empty() {
9832 let selections = this.selections.all::<MultiBufferPoint>(cx);
9833 let snapshot = this.buffer.read(cx).snapshot(cx);
9834
9835 for selection in selections.iter() {
9836 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9837 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9838 if selection_start.buffer_id != selection_end.buffer_id {
9839 continue;
9840 }
9841 if let Some(ranges) =
9842 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9843 {
9844 for (buffer, entries) in ranges {
9845 linked_ranges.entry(buffer).or_default().extend(entries);
9846 }
9847 }
9848 }
9849 }
9850
9851 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9852 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9853 for selection in &mut selections {
9854 if selection.is_empty() {
9855 let old_head = selection.head();
9856 let mut new_head =
9857 movement::left(&display_map, old_head.to_display_point(&display_map))
9858 .to_point(&display_map);
9859 if let Some((buffer, line_buffer_range)) = display_map
9860 .buffer_snapshot
9861 .buffer_line_for_row(MultiBufferRow(old_head.row))
9862 {
9863 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9864 let indent_len = match indent_size.kind {
9865 IndentKind::Space => {
9866 buffer.settings_at(line_buffer_range.start, cx).tab_size
9867 }
9868 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9869 };
9870 if old_head.column <= indent_size.len && old_head.column > 0 {
9871 let indent_len = indent_len.get();
9872 new_head = cmp::min(
9873 new_head,
9874 MultiBufferPoint::new(
9875 old_head.row,
9876 ((old_head.column - 1) / indent_len) * indent_len,
9877 ),
9878 );
9879 }
9880 }
9881
9882 selection.set_head(new_head, SelectionGoal::None);
9883 }
9884 }
9885
9886 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9887 this.insert("", window, cx);
9888 let empty_str: Arc<str> = Arc::from("");
9889 for (buffer, edits) in linked_ranges {
9890 let snapshot = buffer.read(cx).snapshot();
9891 use text::ToPoint as TP;
9892
9893 let edits = edits
9894 .into_iter()
9895 .map(|range| {
9896 let end_point = TP::to_point(&range.end, &snapshot);
9897 let mut start_point = TP::to_point(&range.start, &snapshot);
9898
9899 if end_point == start_point {
9900 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9901 .saturating_sub(1);
9902 start_point =
9903 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9904 };
9905
9906 (start_point..end_point, empty_str.clone())
9907 })
9908 .sorted_by_key(|(range, _)| range.start)
9909 .collect::<Vec<_>>();
9910 buffer.update(cx, |this, cx| {
9911 this.edit(edits, None, cx);
9912 })
9913 }
9914 this.refresh_edit_prediction(true, false, window, cx);
9915 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9916 });
9917 }
9918
9919 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9920 if self.read_only(cx) {
9921 return;
9922 }
9923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9924 self.transact(window, cx, |this, window, cx| {
9925 this.change_selections(Default::default(), window, cx, |s| {
9926 s.move_with(|map, selection| {
9927 if selection.is_empty() {
9928 let cursor = movement::right(map, selection.head());
9929 selection.end = cursor;
9930 selection.reversed = true;
9931 selection.goal = SelectionGoal::None;
9932 }
9933 })
9934 });
9935 this.insert("", window, cx);
9936 this.refresh_edit_prediction(true, false, window, cx);
9937 });
9938 }
9939
9940 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9941 if self.mode.is_single_line() {
9942 cx.propagate();
9943 return;
9944 }
9945
9946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9947 if self.move_to_prev_snippet_tabstop(window, cx) {
9948 return;
9949 }
9950 self.outdent(&Outdent, window, cx);
9951 }
9952
9953 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9954 if self.mode.is_single_line() {
9955 cx.propagate();
9956 return;
9957 }
9958
9959 if self.move_to_next_snippet_tabstop(window, cx) {
9960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9961 return;
9962 }
9963 if self.read_only(cx) {
9964 return;
9965 }
9966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9967 let mut selections = self.selections.all_adjusted(cx);
9968 let buffer = self.buffer.read(cx);
9969 let snapshot = buffer.snapshot(cx);
9970 let rows_iter = selections.iter().map(|s| s.head().row);
9971 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9972
9973 let has_some_cursor_in_whitespace = selections
9974 .iter()
9975 .filter(|selection| selection.is_empty())
9976 .any(|selection| {
9977 let cursor = selection.head();
9978 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9979 cursor.column < current_indent.len
9980 });
9981
9982 let mut edits = Vec::new();
9983 let mut prev_edited_row = 0;
9984 let mut row_delta = 0;
9985 for selection in &mut selections {
9986 if selection.start.row != prev_edited_row {
9987 row_delta = 0;
9988 }
9989 prev_edited_row = selection.end.row;
9990
9991 // If the selection is non-empty, then increase the indentation of the selected lines.
9992 if !selection.is_empty() {
9993 row_delta =
9994 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9995 continue;
9996 }
9997
9998 let cursor = selection.head();
9999 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10000 if let Some(suggested_indent) =
10001 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10002 {
10003 // Don't do anything if already at suggested indent
10004 // and there is any other cursor which is not
10005 if has_some_cursor_in_whitespace
10006 && cursor.column == current_indent.len
10007 && current_indent.len == suggested_indent.len
10008 {
10009 continue;
10010 }
10011
10012 // Adjust line and move cursor to suggested indent
10013 // if cursor is not at suggested indent
10014 if cursor.column < suggested_indent.len
10015 && cursor.column <= current_indent.len
10016 && current_indent.len <= suggested_indent.len
10017 {
10018 selection.start = Point::new(cursor.row, suggested_indent.len);
10019 selection.end = selection.start;
10020 if row_delta == 0 {
10021 edits.extend(Buffer::edit_for_indent_size_adjustment(
10022 cursor.row,
10023 current_indent,
10024 suggested_indent,
10025 ));
10026 row_delta = suggested_indent.len - current_indent.len;
10027 }
10028 continue;
10029 }
10030
10031 // If current indent is more than suggested indent
10032 // only move cursor to current indent and skip indent
10033 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10034 selection.start = Point::new(cursor.row, current_indent.len);
10035 selection.end = selection.start;
10036 continue;
10037 }
10038 }
10039
10040 // Otherwise, insert a hard or soft tab.
10041 let settings = buffer.language_settings_at(cursor, cx);
10042 let tab_size = if settings.hard_tabs {
10043 IndentSize::tab()
10044 } else {
10045 let tab_size = settings.tab_size.get();
10046 let indent_remainder = snapshot
10047 .text_for_range(Point::new(cursor.row, 0)..cursor)
10048 .flat_map(str::chars)
10049 .fold(row_delta % tab_size, |counter: u32, c| {
10050 if c == '\t' {
10051 0
10052 } else {
10053 (counter + 1) % tab_size
10054 }
10055 });
10056
10057 let chars_to_next_tab_stop = tab_size - indent_remainder;
10058 IndentSize::spaces(chars_to_next_tab_stop)
10059 };
10060 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10061 selection.end = selection.start;
10062 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10063 row_delta += tab_size.len;
10064 }
10065
10066 self.transact(window, cx, |this, window, cx| {
10067 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10068 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10069 this.refresh_edit_prediction(true, false, window, cx);
10070 });
10071 }
10072
10073 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10074 if self.read_only(cx) {
10075 return;
10076 }
10077 if self.mode.is_single_line() {
10078 cx.propagate();
10079 return;
10080 }
10081
10082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10083 let mut selections = self.selections.all::<Point>(cx);
10084 let mut prev_edited_row = 0;
10085 let mut row_delta = 0;
10086 let mut edits = Vec::new();
10087 let buffer = self.buffer.read(cx);
10088 let snapshot = buffer.snapshot(cx);
10089 for selection in &mut selections {
10090 if selection.start.row != prev_edited_row {
10091 row_delta = 0;
10092 }
10093 prev_edited_row = selection.end.row;
10094
10095 row_delta =
10096 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10097 }
10098
10099 self.transact(window, cx, |this, window, cx| {
10100 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10101 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10102 });
10103 }
10104
10105 fn indent_selection(
10106 buffer: &MultiBuffer,
10107 snapshot: &MultiBufferSnapshot,
10108 selection: &mut Selection<Point>,
10109 edits: &mut Vec<(Range<Point>, String)>,
10110 delta_for_start_row: u32,
10111 cx: &App,
10112 ) -> u32 {
10113 let settings = buffer.language_settings_at(selection.start, cx);
10114 let tab_size = settings.tab_size.get();
10115 let indent_kind = if settings.hard_tabs {
10116 IndentKind::Tab
10117 } else {
10118 IndentKind::Space
10119 };
10120 let mut start_row = selection.start.row;
10121 let mut end_row = selection.end.row + 1;
10122
10123 // If a selection ends at the beginning of a line, don't indent
10124 // that last line.
10125 if selection.end.column == 0 && selection.end.row > selection.start.row {
10126 end_row -= 1;
10127 }
10128
10129 // Avoid re-indenting a row that has already been indented by a
10130 // previous selection, but still update this selection's column
10131 // to reflect that indentation.
10132 if delta_for_start_row > 0 {
10133 start_row += 1;
10134 selection.start.column += delta_for_start_row;
10135 if selection.end.row == selection.start.row {
10136 selection.end.column += delta_for_start_row;
10137 }
10138 }
10139
10140 let mut delta_for_end_row = 0;
10141 let has_multiple_rows = start_row + 1 != end_row;
10142 for row in start_row..end_row {
10143 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10144 let indent_delta = match (current_indent.kind, indent_kind) {
10145 (IndentKind::Space, IndentKind::Space) => {
10146 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10147 IndentSize::spaces(columns_to_next_tab_stop)
10148 }
10149 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10150 (_, IndentKind::Tab) => IndentSize::tab(),
10151 };
10152
10153 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10154 0
10155 } else {
10156 selection.start.column
10157 };
10158 let row_start = Point::new(row, start);
10159 edits.push((
10160 row_start..row_start,
10161 indent_delta.chars().collect::<String>(),
10162 ));
10163
10164 // Update this selection's endpoints to reflect the indentation.
10165 if row == selection.start.row {
10166 selection.start.column += indent_delta.len;
10167 }
10168 if row == selection.end.row {
10169 selection.end.column += indent_delta.len;
10170 delta_for_end_row = indent_delta.len;
10171 }
10172 }
10173
10174 if selection.start.row == selection.end.row {
10175 delta_for_start_row + delta_for_end_row
10176 } else {
10177 delta_for_end_row
10178 }
10179 }
10180
10181 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10182 if self.read_only(cx) {
10183 return;
10184 }
10185 if self.mode.is_single_line() {
10186 cx.propagate();
10187 return;
10188 }
10189
10190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10192 let selections = self.selections.all::<Point>(cx);
10193 let mut deletion_ranges = Vec::new();
10194 let mut last_outdent = None;
10195 {
10196 let buffer = self.buffer.read(cx);
10197 let snapshot = buffer.snapshot(cx);
10198 for selection in &selections {
10199 let settings = buffer.language_settings_at(selection.start, cx);
10200 let tab_size = settings.tab_size.get();
10201 let mut rows = selection.spanned_rows(false, &display_map);
10202
10203 // Avoid re-outdenting a row that has already been outdented by a
10204 // previous selection.
10205 if let Some(last_row) = last_outdent
10206 && last_row == rows.start
10207 {
10208 rows.start = rows.start.next_row();
10209 }
10210 let has_multiple_rows = rows.len() > 1;
10211 for row in rows.iter_rows() {
10212 let indent_size = snapshot.indent_size_for_line(row);
10213 if indent_size.len > 0 {
10214 let deletion_len = match indent_size.kind {
10215 IndentKind::Space => {
10216 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10217 if columns_to_prev_tab_stop == 0 {
10218 tab_size
10219 } else {
10220 columns_to_prev_tab_stop
10221 }
10222 }
10223 IndentKind::Tab => 1,
10224 };
10225 let start = if has_multiple_rows
10226 || deletion_len > selection.start.column
10227 || indent_size.len < selection.start.column
10228 {
10229 0
10230 } else {
10231 selection.start.column - deletion_len
10232 };
10233 deletion_ranges.push(
10234 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10235 );
10236 last_outdent = Some(row);
10237 }
10238 }
10239 }
10240 }
10241
10242 self.transact(window, cx, |this, window, cx| {
10243 this.buffer.update(cx, |buffer, cx| {
10244 let empty_str: Arc<str> = Arc::default();
10245 buffer.edit(
10246 deletion_ranges
10247 .into_iter()
10248 .map(|range| (range, empty_str.clone())),
10249 None,
10250 cx,
10251 );
10252 });
10253 let selections = this.selections.all::<usize>(cx);
10254 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10255 });
10256 }
10257
10258 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10259 if self.read_only(cx) {
10260 return;
10261 }
10262 if self.mode.is_single_line() {
10263 cx.propagate();
10264 return;
10265 }
10266
10267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10268 let selections = self
10269 .selections
10270 .all::<usize>(cx)
10271 .into_iter()
10272 .map(|s| s.range());
10273
10274 self.transact(window, cx, |this, window, cx| {
10275 this.buffer.update(cx, |buffer, cx| {
10276 buffer.autoindent_ranges(selections, cx);
10277 });
10278 let selections = this.selections.all::<usize>(cx);
10279 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10280 });
10281 }
10282
10283 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10284 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10286 let selections = self.selections.all::<Point>(cx);
10287
10288 let mut new_cursors = Vec::new();
10289 let mut edit_ranges = Vec::new();
10290 let mut selections = selections.iter().peekable();
10291 while let Some(selection) = selections.next() {
10292 let mut rows = selection.spanned_rows(false, &display_map);
10293 let goal_display_column = selection.head().to_display_point(&display_map).column();
10294
10295 // Accumulate contiguous regions of rows that we want to delete.
10296 while let Some(next_selection) = selections.peek() {
10297 let next_rows = next_selection.spanned_rows(false, &display_map);
10298 if next_rows.start <= rows.end {
10299 rows.end = next_rows.end;
10300 selections.next().unwrap();
10301 } else {
10302 break;
10303 }
10304 }
10305
10306 let buffer = &display_map.buffer_snapshot;
10307 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10308 let edit_end;
10309 let cursor_buffer_row;
10310 if buffer.max_point().row >= rows.end.0 {
10311 // If there's a line after the range, delete the \n from the end of the row range
10312 // and position the cursor on the next line.
10313 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10314 cursor_buffer_row = rows.end;
10315 } else {
10316 // If there isn't a line after the range, delete the \n from the line before the
10317 // start of the row range and position the cursor there.
10318 edit_start = edit_start.saturating_sub(1);
10319 edit_end = buffer.len();
10320 cursor_buffer_row = rows.start.previous_row();
10321 }
10322
10323 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10324 *cursor.column_mut() =
10325 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10326
10327 new_cursors.push((
10328 selection.id,
10329 buffer.anchor_after(cursor.to_point(&display_map)),
10330 ));
10331 edit_ranges.push(edit_start..edit_end);
10332 }
10333
10334 self.transact(window, cx, |this, window, cx| {
10335 let buffer = this.buffer.update(cx, |buffer, cx| {
10336 let empty_str: Arc<str> = Arc::default();
10337 buffer.edit(
10338 edit_ranges
10339 .into_iter()
10340 .map(|range| (range, empty_str.clone())),
10341 None,
10342 cx,
10343 );
10344 buffer.snapshot(cx)
10345 });
10346 let new_selections = new_cursors
10347 .into_iter()
10348 .map(|(id, cursor)| {
10349 let cursor = cursor.to_point(&buffer);
10350 Selection {
10351 id,
10352 start: cursor,
10353 end: cursor,
10354 reversed: false,
10355 goal: SelectionGoal::None,
10356 }
10357 })
10358 .collect();
10359
10360 this.change_selections(Default::default(), window, cx, |s| {
10361 s.select(new_selections);
10362 });
10363 });
10364 }
10365
10366 pub fn join_lines_impl(
10367 &mut self,
10368 insert_whitespace: bool,
10369 window: &mut Window,
10370 cx: &mut Context<Self>,
10371 ) {
10372 if self.read_only(cx) {
10373 return;
10374 }
10375 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10376 for selection in self.selections.all::<Point>(cx) {
10377 let start = MultiBufferRow(selection.start.row);
10378 // Treat single line selections as if they include the next line. Otherwise this action
10379 // would do nothing for single line selections individual cursors.
10380 let end = if selection.start.row == selection.end.row {
10381 MultiBufferRow(selection.start.row + 1)
10382 } else {
10383 MultiBufferRow(selection.end.row)
10384 };
10385
10386 if let Some(last_row_range) = row_ranges.last_mut()
10387 && start <= last_row_range.end
10388 {
10389 last_row_range.end = end;
10390 continue;
10391 }
10392 row_ranges.push(start..end);
10393 }
10394
10395 let snapshot = self.buffer.read(cx).snapshot(cx);
10396 let mut cursor_positions = Vec::new();
10397 for row_range in &row_ranges {
10398 let anchor = snapshot.anchor_before(Point::new(
10399 row_range.end.previous_row().0,
10400 snapshot.line_len(row_range.end.previous_row()),
10401 ));
10402 cursor_positions.push(anchor..anchor);
10403 }
10404
10405 self.transact(window, cx, |this, window, cx| {
10406 for row_range in row_ranges.into_iter().rev() {
10407 for row in row_range.iter_rows().rev() {
10408 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10409 let next_line_row = row.next_row();
10410 let indent = snapshot.indent_size_for_line(next_line_row);
10411 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10412
10413 let replace =
10414 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10415 " "
10416 } else {
10417 ""
10418 };
10419
10420 this.buffer.update(cx, |buffer, cx| {
10421 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10422 });
10423 }
10424 }
10425
10426 this.change_selections(Default::default(), window, cx, |s| {
10427 s.select_anchor_ranges(cursor_positions)
10428 });
10429 });
10430 }
10431
10432 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10433 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10434 self.join_lines_impl(true, window, cx);
10435 }
10436
10437 pub fn sort_lines_case_sensitive(
10438 &mut self,
10439 _: &SortLinesCaseSensitive,
10440 window: &mut Window,
10441 cx: &mut Context<Self>,
10442 ) {
10443 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10444 }
10445
10446 pub fn sort_lines_by_length(
10447 &mut self,
10448 _: &SortLinesByLength,
10449 window: &mut Window,
10450 cx: &mut Context<Self>,
10451 ) {
10452 self.manipulate_immutable_lines(window, cx, |lines| {
10453 lines.sort_by_key(|&line| line.chars().count())
10454 })
10455 }
10456
10457 pub fn sort_lines_case_insensitive(
10458 &mut self,
10459 _: &SortLinesCaseInsensitive,
10460 window: &mut Window,
10461 cx: &mut Context<Self>,
10462 ) {
10463 self.manipulate_immutable_lines(window, cx, |lines| {
10464 lines.sort_by_key(|line| line.to_lowercase())
10465 })
10466 }
10467
10468 pub fn unique_lines_case_insensitive(
10469 &mut self,
10470 _: &UniqueLinesCaseInsensitive,
10471 window: &mut Window,
10472 cx: &mut Context<Self>,
10473 ) {
10474 self.manipulate_immutable_lines(window, cx, |lines| {
10475 let mut seen = HashSet::default();
10476 lines.retain(|line| seen.insert(line.to_lowercase()));
10477 })
10478 }
10479
10480 pub fn unique_lines_case_sensitive(
10481 &mut self,
10482 _: &UniqueLinesCaseSensitive,
10483 window: &mut Window,
10484 cx: &mut Context<Self>,
10485 ) {
10486 self.manipulate_immutable_lines(window, cx, |lines| {
10487 let mut seen = HashSet::default();
10488 lines.retain(|line| seen.insert(*line));
10489 })
10490 }
10491
10492 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10493 let snapshot = self.buffer.read(cx).snapshot(cx);
10494 for selection in self.selections.disjoint_anchors().iter() {
10495 if snapshot
10496 .language_at(selection.start)
10497 .and_then(|lang| lang.config().wrap_characters.as_ref())
10498 .is_some()
10499 {
10500 return true;
10501 }
10502 }
10503 false
10504 }
10505
10506 fn wrap_selections_in_tag(
10507 &mut self,
10508 _: &WrapSelectionsInTag,
10509 window: &mut Window,
10510 cx: &mut Context<Self>,
10511 ) {
10512 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10513
10514 let snapshot = self.buffer.read(cx).snapshot(cx);
10515
10516 let mut edits = Vec::new();
10517 let mut boundaries = Vec::new();
10518
10519 for selection in self.selections.all::<Point>(cx).iter() {
10520 let Some(wrap_config) = snapshot
10521 .language_at(selection.start)
10522 .and_then(|lang| lang.config().wrap_characters.clone())
10523 else {
10524 continue;
10525 };
10526
10527 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10528 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10529
10530 let start_before = snapshot.anchor_before(selection.start);
10531 let end_after = snapshot.anchor_after(selection.end);
10532
10533 edits.push((start_before..start_before, open_tag));
10534 edits.push((end_after..end_after, close_tag));
10535
10536 boundaries.push((
10537 start_before,
10538 end_after,
10539 wrap_config.start_prefix.len(),
10540 wrap_config.end_suffix.len(),
10541 ));
10542 }
10543
10544 if edits.is_empty() {
10545 return;
10546 }
10547
10548 self.transact(window, cx, |this, window, cx| {
10549 let buffer = this.buffer.update(cx, |buffer, cx| {
10550 buffer.edit(edits, None, cx);
10551 buffer.snapshot(cx)
10552 });
10553
10554 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10555 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10556 boundaries.into_iter()
10557 {
10558 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10559 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10560 new_selections.push(open_offset..open_offset);
10561 new_selections.push(close_offset..close_offset);
10562 }
10563
10564 this.change_selections(Default::default(), window, cx, |s| {
10565 s.select_ranges(new_selections);
10566 });
10567
10568 this.request_autoscroll(Autoscroll::fit(), cx);
10569 });
10570 }
10571
10572 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10573 let Some(project) = self.project.clone() else {
10574 return;
10575 };
10576 self.reload(project, window, cx)
10577 .detach_and_notify_err(window, cx);
10578 }
10579
10580 pub fn restore_file(
10581 &mut self,
10582 _: &::git::RestoreFile,
10583 window: &mut Window,
10584 cx: &mut Context<Self>,
10585 ) {
10586 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10587 let mut buffer_ids = HashSet::default();
10588 let snapshot = self.buffer().read(cx).snapshot(cx);
10589 for selection in self.selections.all::<usize>(cx) {
10590 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10591 }
10592
10593 let buffer = self.buffer().read(cx);
10594 let ranges = buffer_ids
10595 .into_iter()
10596 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10597 .collect::<Vec<_>>();
10598
10599 self.restore_hunks_in_ranges(ranges, window, cx);
10600 }
10601
10602 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10603 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10604 let selections = self
10605 .selections
10606 .all(cx)
10607 .into_iter()
10608 .map(|s| s.range())
10609 .collect();
10610 self.restore_hunks_in_ranges(selections, window, cx);
10611 }
10612
10613 pub fn restore_hunks_in_ranges(
10614 &mut self,
10615 ranges: Vec<Range<Point>>,
10616 window: &mut Window,
10617 cx: &mut Context<Editor>,
10618 ) {
10619 let mut revert_changes = HashMap::default();
10620 let chunk_by = self
10621 .snapshot(window, cx)
10622 .hunks_for_ranges(ranges)
10623 .into_iter()
10624 .chunk_by(|hunk| hunk.buffer_id);
10625 for (buffer_id, hunks) in &chunk_by {
10626 let hunks = hunks.collect::<Vec<_>>();
10627 for hunk in &hunks {
10628 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10629 }
10630 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10631 }
10632 drop(chunk_by);
10633 if !revert_changes.is_empty() {
10634 self.transact(window, cx, |editor, window, cx| {
10635 editor.restore(revert_changes, window, cx);
10636 });
10637 }
10638 }
10639
10640 pub fn open_active_item_in_terminal(
10641 &mut self,
10642 _: &OpenInTerminal,
10643 window: &mut Window,
10644 cx: &mut Context<Self>,
10645 ) {
10646 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10647 let project_path = buffer.read(cx).project_path(cx)?;
10648 let project = self.project()?.read(cx);
10649 let entry = project.entry_for_path(&project_path, cx)?;
10650 let parent = match &entry.canonical_path {
10651 Some(canonical_path) => canonical_path.to_path_buf(),
10652 None => project.absolute_path(&project_path, cx)?,
10653 }
10654 .parent()?
10655 .to_path_buf();
10656 Some(parent)
10657 }) {
10658 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10659 }
10660 }
10661
10662 fn set_breakpoint_context_menu(
10663 &mut self,
10664 display_row: DisplayRow,
10665 position: Option<Anchor>,
10666 clicked_point: gpui::Point<Pixels>,
10667 window: &mut Window,
10668 cx: &mut Context<Self>,
10669 ) {
10670 let source = self
10671 .buffer
10672 .read(cx)
10673 .snapshot(cx)
10674 .anchor_before(Point::new(display_row.0, 0u32));
10675
10676 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10677
10678 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10679 self,
10680 source,
10681 clicked_point,
10682 context_menu,
10683 window,
10684 cx,
10685 );
10686 }
10687
10688 fn add_edit_breakpoint_block(
10689 &mut self,
10690 anchor: Anchor,
10691 breakpoint: &Breakpoint,
10692 edit_action: BreakpointPromptEditAction,
10693 window: &mut Window,
10694 cx: &mut Context<Self>,
10695 ) {
10696 let weak_editor = cx.weak_entity();
10697 let bp_prompt = cx.new(|cx| {
10698 BreakpointPromptEditor::new(
10699 weak_editor,
10700 anchor,
10701 breakpoint.clone(),
10702 edit_action,
10703 window,
10704 cx,
10705 )
10706 });
10707
10708 let height = bp_prompt.update(cx, |this, cx| {
10709 this.prompt
10710 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10711 });
10712 let cloned_prompt = bp_prompt.clone();
10713 let blocks = vec![BlockProperties {
10714 style: BlockStyle::Sticky,
10715 placement: BlockPlacement::Above(anchor),
10716 height: Some(height),
10717 render: Arc::new(move |cx| {
10718 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10719 cloned_prompt.clone().into_any_element()
10720 }),
10721 priority: 0,
10722 }];
10723
10724 let focus_handle = bp_prompt.focus_handle(cx);
10725 window.focus(&focus_handle);
10726
10727 let block_ids = self.insert_blocks(blocks, None, cx);
10728 bp_prompt.update(cx, |prompt, _| {
10729 prompt.add_block_ids(block_ids);
10730 });
10731 }
10732
10733 pub(crate) fn breakpoint_at_row(
10734 &self,
10735 row: u32,
10736 window: &mut Window,
10737 cx: &mut Context<Self>,
10738 ) -> Option<(Anchor, Breakpoint)> {
10739 let snapshot = self.snapshot(window, cx);
10740 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10741
10742 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10743 }
10744
10745 pub(crate) fn breakpoint_at_anchor(
10746 &self,
10747 breakpoint_position: Anchor,
10748 snapshot: &EditorSnapshot,
10749 cx: &mut Context<Self>,
10750 ) -> Option<(Anchor, Breakpoint)> {
10751 let buffer = self
10752 .buffer
10753 .read(cx)
10754 .buffer_for_anchor(breakpoint_position, cx)?;
10755
10756 let enclosing_excerpt = breakpoint_position.excerpt_id;
10757 let buffer_snapshot = buffer.read(cx).snapshot();
10758
10759 let row = buffer_snapshot
10760 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10761 .row;
10762
10763 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10764 let anchor_end = snapshot
10765 .buffer_snapshot
10766 .anchor_after(Point::new(row, line_len));
10767
10768 self.breakpoint_store
10769 .as_ref()?
10770 .read_with(cx, |breakpoint_store, cx| {
10771 breakpoint_store
10772 .breakpoints(
10773 &buffer,
10774 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10775 &buffer_snapshot,
10776 cx,
10777 )
10778 .next()
10779 .and_then(|(bp, _)| {
10780 let breakpoint_row = buffer_snapshot
10781 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10782 .row;
10783
10784 if breakpoint_row == row {
10785 snapshot
10786 .buffer_snapshot
10787 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10788 .map(|position| (position, bp.bp.clone()))
10789 } else {
10790 None
10791 }
10792 })
10793 })
10794 }
10795
10796 pub fn edit_log_breakpoint(
10797 &mut self,
10798 _: &EditLogBreakpoint,
10799 window: &mut Window,
10800 cx: &mut Context<Self>,
10801 ) {
10802 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10803 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10804 message: None,
10805 state: BreakpointState::Enabled,
10806 condition: None,
10807 hit_condition: None,
10808 });
10809
10810 self.add_edit_breakpoint_block(
10811 anchor,
10812 &breakpoint,
10813 BreakpointPromptEditAction::Log,
10814 window,
10815 cx,
10816 );
10817 }
10818 }
10819
10820 fn breakpoints_at_cursors(
10821 &self,
10822 window: &mut Window,
10823 cx: &mut Context<Self>,
10824 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10825 let snapshot = self.snapshot(window, cx);
10826 let cursors = self
10827 .selections
10828 .disjoint_anchors()
10829 .iter()
10830 .map(|selection| {
10831 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10832
10833 let breakpoint_position = self
10834 .breakpoint_at_row(cursor_position.row, window, cx)
10835 .map(|bp| bp.0)
10836 .unwrap_or_else(|| {
10837 snapshot
10838 .display_snapshot
10839 .buffer_snapshot
10840 .anchor_after(Point::new(cursor_position.row, 0))
10841 });
10842
10843 let breakpoint = self
10844 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10845 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10846
10847 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10848 })
10849 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
10850 .collect::<HashMap<Anchor, _>>();
10851
10852 cursors.into_iter().collect()
10853 }
10854
10855 pub fn enable_breakpoint(
10856 &mut self,
10857 _: &crate::actions::EnableBreakpoint,
10858 window: &mut Window,
10859 cx: &mut Context<Self>,
10860 ) {
10861 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10862 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10863 continue;
10864 };
10865 self.edit_breakpoint_at_anchor(
10866 anchor,
10867 breakpoint,
10868 BreakpointEditAction::InvertState,
10869 cx,
10870 );
10871 }
10872 }
10873
10874 pub fn disable_breakpoint(
10875 &mut self,
10876 _: &crate::actions::DisableBreakpoint,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10881 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10882 continue;
10883 };
10884 self.edit_breakpoint_at_anchor(
10885 anchor,
10886 breakpoint,
10887 BreakpointEditAction::InvertState,
10888 cx,
10889 );
10890 }
10891 }
10892
10893 pub fn toggle_breakpoint(
10894 &mut self,
10895 _: &crate::actions::ToggleBreakpoint,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10900 if let Some(breakpoint) = breakpoint {
10901 self.edit_breakpoint_at_anchor(
10902 anchor,
10903 breakpoint,
10904 BreakpointEditAction::Toggle,
10905 cx,
10906 );
10907 } else {
10908 self.edit_breakpoint_at_anchor(
10909 anchor,
10910 Breakpoint::new_standard(),
10911 BreakpointEditAction::Toggle,
10912 cx,
10913 );
10914 }
10915 }
10916 }
10917
10918 pub fn edit_breakpoint_at_anchor(
10919 &mut self,
10920 breakpoint_position: Anchor,
10921 breakpoint: Breakpoint,
10922 edit_action: BreakpointEditAction,
10923 cx: &mut Context<Self>,
10924 ) {
10925 let Some(breakpoint_store) = &self.breakpoint_store else {
10926 return;
10927 };
10928
10929 let Some(buffer) = self
10930 .buffer
10931 .read(cx)
10932 .buffer_for_anchor(breakpoint_position, cx)
10933 else {
10934 return;
10935 };
10936
10937 breakpoint_store.update(cx, |breakpoint_store, cx| {
10938 breakpoint_store.toggle_breakpoint(
10939 buffer,
10940 BreakpointWithPosition {
10941 position: breakpoint_position.text_anchor,
10942 bp: breakpoint,
10943 },
10944 edit_action,
10945 cx,
10946 );
10947 });
10948
10949 cx.notify();
10950 }
10951
10952 #[cfg(any(test, feature = "test-support"))]
10953 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10954 self.breakpoint_store.clone()
10955 }
10956
10957 pub fn prepare_restore_change(
10958 &self,
10959 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10960 hunk: &MultiBufferDiffHunk,
10961 cx: &mut App,
10962 ) -> Option<()> {
10963 if hunk.is_created_file() {
10964 return None;
10965 }
10966 let buffer = self.buffer.read(cx);
10967 let diff = buffer.diff_for(hunk.buffer_id)?;
10968 let buffer = buffer.buffer(hunk.buffer_id)?;
10969 let buffer = buffer.read(cx);
10970 let original_text = diff
10971 .read(cx)
10972 .base_text()
10973 .as_rope()
10974 .slice(hunk.diff_base_byte_range.clone());
10975 let buffer_snapshot = buffer.snapshot();
10976 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10977 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10978 probe
10979 .0
10980 .start
10981 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10982 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10983 }) {
10984 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10985 Some(())
10986 } else {
10987 None
10988 }
10989 }
10990
10991 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10992 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10993 }
10994
10995 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10996 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
10997 }
10998
10999 fn manipulate_lines<M>(
11000 &mut self,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 mut manipulate: M,
11004 ) where
11005 M: FnMut(&str) -> LineManipulationResult,
11006 {
11007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11008
11009 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11010 let buffer = self.buffer.read(cx).snapshot(cx);
11011
11012 let mut edits = Vec::new();
11013
11014 let selections = self.selections.all::<Point>(cx);
11015 let mut selections = selections.iter().peekable();
11016 let mut contiguous_row_selections = Vec::new();
11017 let mut new_selections = Vec::new();
11018 let mut added_lines = 0;
11019 let mut removed_lines = 0;
11020
11021 while let Some(selection) = selections.next() {
11022 let (start_row, end_row) = consume_contiguous_rows(
11023 &mut contiguous_row_selections,
11024 selection,
11025 &display_map,
11026 &mut selections,
11027 );
11028
11029 let start_point = Point::new(start_row.0, 0);
11030 let end_point = Point::new(
11031 end_row.previous_row().0,
11032 buffer.line_len(end_row.previous_row()),
11033 );
11034 let text = buffer
11035 .text_for_range(start_point..end_point)
11036 .collect::<String>();
11037
11038 let LineManipulationResult {
11039 new_text,
11040 line_count_before,
11041 line_count_after,
11042 } = manipulate(&text);
11043
11044 edits.push((start_point..end_point, new_text));
11045
11046 // Selections must change based on added and removed line count
11047 let start_row =
11048 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11049 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11050 new_selections.push(Selection {
11051 id: selection.id,
11052 start: start_row,
11053 end: end_row,
11054 goal: SelectionGoal::None,
11055 reversed: selection.reversed,
11056 });
11057
11058 if line_count_after > line_count_before {
11059 added_lines += line_count_after - line_count_before;
11060 } else if line_count_before > line_count_after {
11061 removed_lines += line_count_before - line_count_after;
11062 }
11063 }
11064
11065 self.transact(window, cx, |this, window, cx| {
11066 let buffer = this.buffer.update(cx, |buffer, cx| {
11067 buffer.edit(edits, None, cx);
11068 buffer.snapshot(cx)
11069 });
11070
11071 // Recalculate offsets on newly edited buffer
11072 let new_selections = new_selections
11073 .iter()
11074 .map(|s| {
11075 let start_point = Point::new(s.start.0, 0);
11076 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11077 Selection {
11078 id: s.id,
11079 start: buffer.point_to_offset(start_point),
11080 end: buffer.point_to_offset(end_point),
11081 goal: s.goal,
11082 reversed: s.reversed,
11083 }
11084 })
11085 .collect();
11086
11087 this.change_selections(Default::default(), window, cx, |s| {
11088 s.select(new_selections);
11089 });
11090
11091 this.request_autoscroll(Autoscroll::fit(), cx);
11092 });
11093 }
11094
11095 fn manipulate_immutable_lines<Fn>(
11096 &mut self,
11097 window: &mut Window,
11098 cx: &mut Context<Self>,
11099 mut callback: Fn,
11100 ) where
11101 Fn: FnMut(&mut Vec<&str>),
11102 {
11103 self.manipulate_lines(window, cx, |text| {
11104 let mut lines: Vec<&str> = text.split('\n').collect();
11105 let line_count_before = lines.len();
11106
11107 callback(&mut lines);
11108
11109 LineManipulationResult {
11110 new_text: lines.join("\n"),
11111 line_count_before,
11112 line_count_after: lines.len(),
11113 }
11114 });
11115 }
11116
11117 fn manipulate_mutable_lines<Fn>(
11118 &mut self,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 mut callback: Fn,
11122 ) where
11123 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11124 {
11125 self.manipulate_lines(window, cx, |text| {
11126 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11127 let line_count_before = lines.len();
11128
11129 callback(&mut lines);
11130
11131 LineManipulationResult {
11132 new_text: lines.join("\n"),
11133 line_count_before,
11134 line_count_after: lines.len(),
11135 }
11136 });
11137 }
11138
11139 pub fn convert_indentation_to_spaces(
11140 &mut self,
11141 _: &ConvertIndentationToSpaces,
11142 window: &mut Window,
11143 cx: &mut Context<Self>,
11144 ) {
11145 let settings = self.buffer.read(cx).language_settings(cx);
11146 let tab_size = settings.tab_size.get() as usize;
11147
11148 self.manipulate_mutable_lines(window, cx, |lines| {
11149 // Allocates a reasonably sized scratch buffer once for the whole loop
11150 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11151 // Avoids recomputing spaces that could be inserted many times
11152 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11153 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11154 .collect();
11155
11156 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11157 let mut chars = line.as_ref().chars();
11158 let mut col = 0;
11159 let mut changed = false;
11160
11161 for ch in chars.by_ref() {
11162 match ch {
11163 ' ' => {
11164 reindented_line.push(' ');
11165 col += 1;
11166 }
11167 '\t' => {
11168 // \t are converted to spaces depending on the current column
11169 let spaces_len = tab_size - (col % tab_size);
11170 reindented_line.extend(&space_cache[spaces_len - 1]);
11171 col += spaces_len;
11172 changed = true;
11173 }
11174 _ => {
11175 // If we dont append before break, the character is consumed
11176 reindented_line.push(ch);
11177 break;
11178 }
11179 }
11180 }
11181
11182 if !changed {
11183 reindented_line.clear();
11184 continue;
11185 }
11186 // Append the rest of the line and replace old reference with new one
11187 reindented_line.extend(chars);
11188 *line = Cow::Owned(reindented_line.clone());
11189 reindented_line.clear();
11190 }
11191 });
11192 }
11193
11194 pub fn convert_indentation_to_tabs(
11195 &mut self,
11196 _: &ConvertIndentationToTabs,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 let settings = self.buffer.read(cx).language_settings(cx);
11201 let tab_size = settings.tab_size.get() as usize;
11202
11203 self.manipulate_mutable_lines(window, cx, |lines| {
11204 // Allocates a reasonably sized buffer once for the whole loop
11205 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11206 // Avoids recomputing spaces that could be inserted many times
11207 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11208 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11209 .collect();
11210
11211 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11212 let mut chars = line.chars();
11213 let mut spaces_count = 0;
11214 let mut first_non_indent_char = None;
11215 let mut changed = false;
11216
11217 for ch in chars.by_ref() {
11218 match ch {
11219 ' ' => {
11220 // Keep track of spaces. Append \t when we reach tab_size
11221 spaces_count += 1;
11222 changed = true;
11223 if spaces_count == tab_size {
11224 reindented_line.push('\t');
11225 spaces_count = 0;
11226 }
11227 }
11228 '\t' => {
11229 reindented_line.push('\t');
11230 spaces_count = 0;
11231 }
11232 _ => {
11233 // Dont append it yet, we might have remaining spaces
11234 first_non_indent_char = Some(ch);
11235 break;
11236 }
11237 }
11238 }
11239
11240 if !changed {
11241 reindented_line.clear();
11242 continue;
11243 }
11244 // Remaining spaces that didn't make a full tab stop
11245 if spaces_count > 0 {
11246 reindented_line.extend(&space_cache[spaces_count - 1]);
11247 }
11248 // If we consume an extra character that was not indentation, add it back
11249 if let Some(extra_char) = first_non_indent_char {
11250 reindented_line.push(extra_char);
11251 }
11252 // Append the rest of the line and replace old reference with new one
11253 reindented_line.extend(chars);
11254 *line = Cow::Owned(reindented_line.clone());
11255 reindented_line.clear();
11256 }
11257 });
11258 }
11259
11260 pub fn convert_to_upper_case(
11261 &mut self,
11262 _: &ConvertToUpperCase,
11263 window: &mut Window,
11264 cx: &mut Context<Self>,
11265 ) {
11266 self.manipulate_text(window, cx, |text| text.to_uppercase())
11267 }
11268
11269 pub fn convert_to_lower_case(
11270 &mut self,
11271 _: &ConvertToLowerCase,
11272 window: &mut Window,
11273 cx: &mut Context<Self>,
11274 ) {
11275 self.manipulate_text(window, cx, |text| text.to_lowercase())
11276 }
11277
11278 pub fn convert_to_title_case(
11279 &mut self,
11280 _: &ConvertToTitleCase,
11281 window: &mut Window,
11282 cx: &mut Context<Self>,
11283 ) {
11284 self.manipulate_text(window, cx, |text| {
11285 text.split('\n')
11286 .map(|line| line.to_case(Case::Title))
11287 .join("\n")
11288 })
11289 }
11290
11291 pub fn convert_to_snake_case(
11292 &mut self,
11293 _: &ConvertToSnakeCase,
11294 window: &mut Window,
11295 cx: &mut Context<Self>,
11296 ) {
11297 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11298 }
11299
11300 pub fn convert_to_kebab_case(
11301 &mut self,
11302 _: &ConvertToKebabCase,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11307 }
11308
11309 pub fn convert_to_upper_camel_case(
11310 &mut self,
11311 _: &ConvertToUpperCamelCase,
11312 window: &mut Window,
11313 cx: &mut Context<Self>,
11314 ) {
11315 self.manipulate_text(window, cx, |text| {
11316 text.split('\n')
11317 .map(|line| line.to_case(Case::UpperCamel))
11318 .join("\n")
11319 })
11320 }
11321
11322 pub fn convert_to_lower_camel_case(
11323 &mut self,
11324 _: &ConvertToLowerCamelCase,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11329 }
11330
11331 pub fn convert_to_opposite_case(
11332 &mut self,
11333 _: &ConvertToOppositeCase,
11334 window: &mut Window,
11335 cx: &mut Context<Self>,
11336 ) {
11337 self.manipulate_text(window, cx, |text| {
11338 text.chars()
11339 .fold(String::with_capacity(text.len()), |mut t, c| {
11340 if c.is_uppercase() {
11341 t.extend(c.to_lowercase());
11342 } else {
11343 t.extend(c.to_uppercase());
11344 }
11345 t
11346 })
11347 })
11348 }
11349
11350 pub fn convert_to_sentence_case(
11351 &mut self,
11352 _: &ConvertToSentenceCase,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11357 }
11358
11359 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11360 self.manipulate_text(window, cx, |text| {
11361 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11362 if has_upper_case_characters {
11363 text.to_lowercase()
11364 } else {
11365 text.to_uppercase()
11366 }
11367 })
11368 }
11369
11370 pub fn convert_to_rot13(
11371 &mut self,
11372 _: &ConvertToRot13,
11373 window: &mut Window,
11374 cx: &mut Context<Self>,
11375 ) {
11376 self.manipulate_text(window, cx, |text| {
11377 text.chars()
11378 .map(|c| match c {
11379 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11380 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11381 _ => c,
11382 })
11383 .collect()
11384 })
11385 }
11386
11387 pub fn convert_to_rot47(
11388 &mut self,
11389 _: &ConvertToRot47,
11390 window: &mut Window,
11391 cx: &mut Context<Self>,
11392 ) {
11393 self.manipulate_text(window, cx, |text| {
11394 text.chars()
11395 .map(|c| {
11396 let code_point = c as u32;
11397 if code_point >= 33 && code_point <= 126 {
11398 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11399 }
11400 c
11401 })
11402 .collect()
11403 })
11404 }
11405
11406 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11407 where
11408 Fn: FnMut(&str) -> String,
11409 {
11410 let buffer = self.buffer.read(cx).snapshot(cx);
11411
11412 let mut new_selections = Vec::new();
11413 let mut edits = Vec::new();
11414 let mut selection_adjustment = 0i32;
11415
11416 for selection in self.selections.all_adjusted(cx) {
11417 let selection_is_empty = selection.is_empty();
11418
11419 let (start, end) = if selection_is_empty {
11420 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11421 (word_range.start, word_range.end)
11422 } else {
11423 (
11424 buffer.point_to_offset(selection.start),
11425 buffer.point_to_offset(selection.end),
11426 )
11427 };
11428
11429 let text = buffer.text_for_range(start..end).collect::<String>();
11430 let old_length = text.len() as i32;
11431 let text = callback(&text);
11432
11433 new_selections.push(Selection {
11434 start: (start as i32 - selection_adjustment) as usize,
11435 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11436 goal: SelectionGoal::None,
11437 id: selection.id,
11438 reversed: selection.reversed,
11439 });
11440
11441 selection_adjustment += old_length - text.len() as i32;
11442
11443 edits.push((start..end, text));
11444 }
11445
11446 self.transact(window, cx, |this, window, cx| {
11447 this.buffer.update(cx, |buffer, cx| {
11448 buffer.edit(edits, None, cx);
11449 });
11450
11451 this.change_selections(Default::default(), window, cx, |s| {
11452 s.select(new_selections);
11453 });
11454
11455 this.request_autoscroll(Autoscroll::fit(), cx);
11456 });
11457 }
11458
11459 pub fn move_selection_on_drop(
11460 &mut self,
11461 selection: &Selection<Anchor>,
11462 target: DisplayPoint,
11463 is_cut: bool,
11464 window: &mut Window,
11465 cx: &mut Context<Self>,
11466 ) {
11467 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11468 let buffer = &display_map.buffer_snapshot;
11469 let mut edits = Vec::new();
11470 let insert_point = display_map
11471 .clip_point(target, Bias::Left)
11472 .to_point(&display_map);
11473 let text = buffer
11474 .text_for_range(selection.start..selection.end)
11475 .collect::<String>();
11476 if is_cut {
11477 edits.push(((selection.start..selection.end), String::new()));
11478 }
11479 let insert_anchor = buffer.anchor_before(insert_point);
11480 edits.push(((insert_anchor..insert_anchor), text));
11481 let last_edit_start = insert_anchor.bias_left(buffer);
11482 let last_edit_end = insert_anchor.bias_right(buffer);
11483 self.transact(window, cx, |this, window, cx| {
11484 this.buffer.update(cx, |buffer, cx| {
11485 buffer.edit(edits, None, cx);
11486 });
11487 this.change_selections(Default::default(), window, cx, |s| {
11488 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11489 });
11490 });
11491 }
11492
11493 pub fn clear_selection_drag_state(&mut self) {
11494 self.selection_drag_state = SelectionDragState::None;
11495 }
11496
11497 pub fn duplicate(
11498 &mut self,
11499 upwards: bool,
11500 whole_lines: bool,
11501 window: &mut Window,
11502 cx: &mut Context<Self>,
11503 ) {
11504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11505
11506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11507 let buffer = &display_map.buffer_snapshot;
11508 let selections = self.selections.all::<Point>(cx);
11509
11510 let mut edits = Vec::new();
11511 let mut selections_iter = selections.iter().peekable();
11512 while let Some(selection) = selections_iter.next() {
11513 let mut rows = selection.spanned_rows(false, &display_map);
11514 // duplicate line-wise
11515 if whole_lines || selection.start == selection.end {
11516 // Avoid duplicating the same lines twice.
11517 while let Some(next_selection) = selections_iter.peek() {
11518 let next_rows = next_selection.spanned_rows(false, &display_map);
11519 if next_rows.start < rows.end {
11520 rows.end = next_rows.end;
11521 selections_iter.next().unwrap();
11522 } else {
11523 break;
11524 }
11525 }
11526
11527 // Copy the text from the selected row region and splice it either at the start
11528 // or end of the region.
11529 let start = Point::new(rows.start.0, 0);
11530 let end = Point::new(
11531 rows.end.previous_row().0,
11532 buffer.line_len(rows.end.previous_row()),
11533 );
11534 let text = buffer
11535 .text_for_range(start..end)
11536 .chain(Some("\n"))
11537 .collect::<String>();
11538 let insert_location = if upwards {
11539 Point::new(rows.end.0, 0)
11540 } else {
11541 start
11542 };
11543 edits.push((insert_location..insert_location, text));
11544 } else {
11545 // duplicate character-wise
11546 let start = selection.start;
11547 let end = selection.end;
11548 let text = buffer.text_for_range(start..end).collect::<String>();
11549 edits.push((selection.end..selection.end, text));
11550 }
11551 }
11552
11553 self.transact(window, cx, |this, _, cx| {
11554 this.buffer.update(cx, |buffer, cx| {
11555 buffer.edit(edits, None, cx);
11556 });
11557
11558 this.request_autoscroll(Autoscroll::fit(), cx);
11559 });
11560 }
11561
11562 pub fn duplicate_line_up(
11563 &mut self,
11564 _: &DuplicateLineUp,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 ) {
11568 self.duplicate(true, true, window, cx);
11569 }
11570
11571 pub fn duplicate_line_down(
11572 &mut self,
11573 _: &DuplicateLineDown,
11574 window: &mut Window,
11575 cx: &mut Context<Self>,
11576 ) {
11577 self.duplicate(false, true, window, cx);
11578 }
11579
11580 pub fn duplicate_selection(
11581 &mut self,
11582 _: &DuplicateSelection,
11583 window: &mut Window,
11584 cx: &mut Context<Self>,
11585 ) {
11586 self.duplicate(false, false, window, cx);
11587 }
11588
11589 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11591 if self.mode.is_single_line() {
11592 cx.propagate();
11593 return;
11594 }
11595
11596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11597 let buffer = self.buffer.read(cx).snapshot(cx);
11598
11599 let mut edits = Vec::new();
11600 let mut unfold_ranges = Vec::new();
11601 let mut refold_creases = Vec::new();
11602
11603 let selections = self.selections.all::<Point>(cx);
11604 let mut selections = selections.iter().peekable();
11605 let mut contiguous_row_selections = Vec::new();
11606 let mut new_selections = Vec::new();
11607
11608 while let Some(selection) = selections.next() {
11609 // Find all the selections that span a contiguous row range
11610 let (start_row, end_row) = consume_contiguous_rows(
11611 &mut contiguous_row_selections,
11612 selection,
11613 &display_map,
11614 &mut selections,
11615 );
11616
11617 // Move the text spanned by the row range to be before the line preceding the row range
11618 if start_row.0 > 0 {
11619 let range_to_move = Point::new(
11620 start_row.previous_row().0,
11621 buffer.line_len(start_row.previous_row()),
11622 )
11623 ..Point::new(
11624 end_row.previous_row().0,
11625 buffer.line_len(end_row.previous_row()),
11626 );
11627 let insertion_point = display_map
11628 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11629 .0;
11630
11631 // Don't move lines across excerpts
11632 if buffer
11633 .excerpt_containing(insertion_point..range_to_move.end)
11634 .is_some()
11635 {
11636 let text = buffer
11637 .text_for_range(range_to_move.clone())
11638 .flat_map(|s| s.chars())
11639 .skip(1)
11640 .chain(['\n'])
11641 .collect::<String>();
11642
11643 edits.push((
11644 buffer.anchor_after(range_to_move.start)
11645 ..buffer.anchor_before(range_to_move.end),
11646 String::new(),
11647 ));
11648 let insertion_anchor = buffer.anchor_after(insertion_point);
11649 edits.push((insertion_anchor..insertion_anchor, text));
11650
11651 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11652
11653 // Move selections up
11654 new_selections.extend(contiguous_row_selections.drain(..).map(
11655 |mut selection| {
11656 selection.start.row -= row_delta;
11657 selection.end.row -= row_delta;
11658 selection
11659 },
11660 ));
11661
11662 // Move folds up
11663 unfold_ranges.push(range_to_move.clone());
11664 for fold in display_map.folds_in_range(
11665 buffer.anchor_before(range_to_move.start)
11666 ..buffer.anchor_after(range_to_move.end),
11667 ) {
11668 let mut start = fold.range.start.to_point(&buffer);
11669 let mut end = fold.range.end.to_point(&buffer);
11670 start.row -= row_delta;
11671 end.row -= row_delta;
11672 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11673 }
11674 }
11675 }
11676
11677 // If we didn't move line(s), preserve the existing selections
11678 new_selections.append(&mut contiguous_row_selections);
11679 }
11680
11681 self.transact(window, cx, |this, window, cx| {
11682 this.unfold_ranges(&unfold_ranges, true, true, cx);
11683 this.buffer.update(cx, |buffer, cx| {
11684 for (range, text) in edits {
11685 buffer.edit([(range, text)], None, cx);
11686 }
11687 });
11688 this.fold_creases(refold_creases, true, window, cx);
11689 this.change_selections(Default::default(), window, cx, |s| {
11690 s.select(new_selections);
11691 })
11692 });
11693 }
11694
11695 pub fn move_line_down(
11696 &mut self,
11697 _: &MoveLineDown,
11698 window: &mut Window,
11699 cx: &mut Context<Self>,
11700 ) {
11701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11702 if self.mode.is_single_line() {
11703 cx.propagate();
11704 return;
11705 }
11706
11707 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11708 let buffer = self.buffer.read(cx).snapshot(cx);
11709
11710 let mut edits = Vec::new();
11711 let mut unfold_ranges = Vec::new();
11712 let mut refold_creases = Vec::new();
11713
11714 let selections = self.selections.all::<Point>(cx);
11715 let mut selections = selections.iter().peekable();
11716 let mut contiguous_row_selections = Vec::new();
11717 let mut new_selections = Vec::new();
11718
11719 while let Some(selection) = selections.next() {
11720 // Find all the selections that span a contiguous row range
11721 let (start_row, end_row) = consume_contiguous_rows(
11722 &mut contiguous_row_selections,
11723 selection,
11724 &display_map,
11725 &mut selections,
11726 );
11727
11728 // Move the text spanned by the row range to be after the last line of the row range
11729 if end_row.0 <= buffer.max_point().row {
11730 let range_to_move =
11731 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11732 let insertion_point = display_map
11733 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11734 .0;
11735
11736 // Don't move lines across excerpt boundaries
11737 if buffer
11738 .excerpt_containing(range_to_move.start..insertion_point)
11739 .is_some()
11740 {
11741 let mut text = String::from("\n");
11742 text.extend(buffer.text_for_range(range_to_move.clone()));
11743 text.pop(); // Drop trailing newline
11744 edits.push((
11745 buffer.anchor_after(range_to_move.start)
11746 ..buffer.anchor_before(range_to_move.end),
11747 String::new(),
11748 ));
11749 let insertion_anchor = buffer.anchor_after(insertion_point);
11750 edits.push((insertion_anchor..insertion_anchor, text));
11751
11752 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11753
11754 // Move selections down
11755 new_selections.extend(contiguous_row_selections.drain(..).map(
11756 |mut selection| {
11757 selection.start.row += row_delta;
11758 selection.end.row += row_delta;
11759 selection
11760 },
11761 ));
11762
11763 // Move folds down
11764 unfold_ranges.push(range_to_move.clone());
11765 for fold in display_map.folds_in_range(
11766 buffer.anchor_before(range_to_move.start)
11767 ..buffer.anchor_after(range_to_move.end),
11768 ) {
11769 let mut start = fold.range.start.to_point(&buffer);
11770 let mut end = fold.range.end.to_point(&buffer);
11771 start.row += row_delta;
11772 end.row += row_delta;
11773 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11774 }
11775 }
11776 }
11777
11778 // If we didn't move line(s), preserve the existing selections
11779 new_selections.append(&mut contiguous_row_selections);
11780 }
11781
11782 self.transact(window, cx, |this, window, cx| {
11783 this.unfold_ranges(&unfold_ranges, true, true, cx);
11784 this.buffer.update(cx, |buffer, cx| {
11785 for (range, text) in edits {
11786 buffer.edit([(range, text)], None, cx);
11787 }
11788 });
11789 this.fold_creases(refold_creases, true, window, cx);
11790 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11791 });
11792 }
11793
11794 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11796 let text_layout_details = &self.text_layout_details(window);
11797 self.transact(window, cx, |this, window, cx| {
11798 let edits = this.change_selections(Default::default(), window, cx, |s| {
11799 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11800 s.move_with(|display_map, selection| {
11801 if !selection.is_empty() {
11802 return;
11803 }
11804
11805 let mut head = selection.head();
11806 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11807 if head.column() == display_map.line_len(head.row()) {
11808 transpose_offset = display_map
11809 .buffer_snapshot
11810 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11811 }
11812
11813 if transpose_offset == 0 {
11814 return;
11815 }
11816
11817 *head.column_mut() += 1;
11818 head = display_map.clip_point(head, Bias::Right);
11819 let goal = SelectionGoal::HorizontalPosition(
11820 display_map
11821 .x_for_display_point(head, text_layout_details)
11822 .into(),
11823 );
11824 selection.collapse_to(head, goal);
11825
11826 let transpose_start = display_map
11827 .buffer_snapshot
11828 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11829 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11830 let transpose_end = display_map
11831 .buffer_snapshot
11832 .clip_offset(transpose_offset + 1, Bias::Right);
11833 if let Some(ch) =
11834 display_map.buffer_snapshot.chars_at(transpose_start).next()
11835 {
11836 edits.push((transpose_start..transpose_offset, String::new()));
11837 edits.push((transpose_end..transpose_end, ch.to_string()));
11838 }
11839 }
11840 });
11841 edits
11842 });
11843 this.buffer
11844 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11845 let selections = this.selections.all::<usize>(cx);
11846 this.change_selections(Default::default(), window, cx, |s| {
11847 s.select(selections);
11848 });
11849 });
11850 }
11851
11852 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11854 if self.mode.is_single_line() {
11855 cx.propagate();
11856 return;
11857 }
11858
11859 self.rewrap_impl(RewrapOptions::default(), cx)
11860 }
11861
11862 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11863 let buffer = self.buffer.read(cx).snapshot(cx);
11864 let selections = self.selections.all::<Point>(cx);
11865
11866 #[derive(Clone, Debug, PartialEq)]
11867 enum CommentFormat {
11868 /// single line comment, with prefix for line
11869 Line(String),
11870 /// single line within a block comment, with prefix for line
11871 BlockLine(String),
11872 /// a single line of a block comment that includes the initial delimiter
11873 BlockCommentWithStart(BlockCommentConfig),
11874 /// a single line of a block comment that includes the ending delimiter
11875 BlockCommentWithEnd(BlockCommentConfig),
11876 }
11877
11878 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11879 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11880 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11881 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11882 .peekable();
11883
11884 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11885 row
11886 } else {
11887 return Vec::new();
11888 };
11889
11890 let language_settings = buffer.language_settings_at(selection.head(), cx);
11891 let language_scope = buffer.language_scope_at(selection.head());
11892
11893 let indent_and_prefix_for_row =
11894 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11895 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11896 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11897 &language_scope
11898 {
11899 let indent_end = Point::new(row, indent.len);
11900 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11901 let line_text_after_indent = buffer
11902 .text_for_range(indent_end..line_end)
11903 .collect::<String>();
11904
11905 let is_within_comment_override = buffer
11906 .language_scope_at(indent_end)
11907 .is_some_and(|scope| scope.override_name() == Some("comment"));
11908 let comment_delimiters = if is_within_comment_override {
11909 // we are within a comment syntax node, but we don't
11910 // yet know what kind of comment: block, doc or line
11911 match (
11912 language_scope.documentation_comment(),
11913 language_scope.block_comment(),
11914 ) {
11915 (Some(config), _) | (_, Some(config))
11916 if buffer.contains_str_at(indent_end, &config.start) =>
11917 {
11918 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11919 }
11920 (Some(config), _) | (_, Some(config))
11921 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11922 {
11923 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11924 }
11925 (Some(config), _) | (_, Some(config))
11926 if buffer.contains_str_at(indent_end, &config.prefix) =>
11927 {
11928 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11929 }
11930 (_, _) => language_scope
11931 .line_comment_prefixes()
11932 .iter()
11933 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11934 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11935 }
11936 } else {
11937 // we not in an overridden comment node, but we may
11938 // be within a non-overridden line comment node
11939 language_scope
11940 .line_comment_prefixes()
11941 .iter()
11942 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11943 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11944 };
11945
11946 let rewrap_prefix = language_scope
11947 .rewrap_prefixes()
11948 .iter()
11949 .find_map(|prefix_regex| {
11950 prefix_regex.find(&line_text_after_indent).map(|mat| {
11951 if mat.start() == 0 {
11952 Some(mat.as_str().to_string())
11953 } else {
11954 None
11955 }
11956 })
11957 })
11958 .flatten();
11959 (comment_delimiters, rewrap_prefix)
11960 } else {
11961 (None, None)
11962 };
11963 (indent, comment_prefix, rewrap_prefix)
11964 };
11965
11966 let mut ranges = Vec::new();
11967 let from_empty_selection = selection.is_empty();
11968
11969 let mut current_range_start = first_row;
11970 let mut prev_row = first_row;
11971 let (
11972 mut current_range_indent,
11973 mut current_range_comment_delimiters,
11974 mut current_range_rewrap_prefix,
11975 ) = indent_and_prefix_for_row(first_row);
11976
11977 for row in non_blank_rows_iter.skip(1) {
11978 let has_paragraph_break = row > prev_row + 1;
11979
11980 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
11981 indent_and_prefix_for_row(row);
11982
11983 let has_indent_change = row_indent != current_range_indent;
11984 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
11985
11986 let has_boundary_change = has_comment_change
11987 || row_rewrap_prefix.is_some()
11988 || (has_indent_change && current_range_comment_delimiters.is_some());
11989
11990 if has_paragraph_break || has_boundary_change {
11991 ranges.push((
11992 language_settings.clone(),
11993 Point::new(current_range_start, 0)
11994 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11995 current_range_indent,
11996 current_range_comment_delimiters.clone(),
11997 current_range_rewrap_prefix.clone(),
11998 from_empty_selection,
11999 ));
12000 current_range_start = row;
12001 current_range_indent = row_indent;
12002 current_range_comment_delimiters = row_comment_delimiters;
12003 current_range_rewrap_prefix = row_rewrap_prefix;
12004 }
12005 prev_row = row;
12006 }
12007
12008 ranges.push((
12009 language_settings.clone(),
12010 Point::new(current_range_start, 0)
12011 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12012 current_range_indent,
12013 current_range_comment_delimiters,
12014 current_range_rewrap_prefix,
12015 from_empty_selection,
12016 ));
12017
12018 ranges
12019 });
12020
12021 let mut edits = Vec::new();
12022 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12023
12024 for (
12025 language_settings,
12026 wrap_range,
12027 mut indent_size,
12028 comment_prefix,
12029 rewrap_prefix,
12030 from_empty_selection,
12031 ) in wrap_ranges
12032 {
12033 let mut start_row = wrap_range.start.row;
12034 let mut end_row = wrap_range.end.row;
12035
12036 // Skip selections that overlap with a range that has already been rewrapped.
12037 let selection_range = start_row..end_row;
12038 if rewrapped_row_ranges
12039 .iter()
12040 .any(|range| range.overlaps(&selection_range))
12041 {
12042 continue;
12043 }
12044
12045 let tab_size = language_settings.tab_size;
12046
12047 let (line_prefix, inside_comment) = match &comment_prefix {
12048 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12049 (Some(prefix.as_str()), true)
12050 }
12051 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12052 (Some(prefix.as_ref()), true)
12053 }
12054 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12055 start: _,
12056 end: _,
12057 prefix,
12058 tab_size,
12059 })) => {
12060 indent_size.len += tab_size;
12061 (Some(prefix.as_ref()), true)
12062 }
12063 None => (None, false),
12064 };
12065 let indent_prefix = indent_size.chars().collect::<String>();
12066 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12067
12068 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12069 RewrapBehavior::InComments => inside_comment,
12070 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12071 RewrapBehavior::Anywhere => true,
12072 };
12073
12074 let should_rewrap = options.override_language_settings
12075 || allow_rewrap_based_on_language
12076 || self.hard_wrap.is_some();
12077 if !should_rewrap {
12078 continue;
12079 }
12080
12081 if from_empty_selection {
12082 'expand_upwards: while start_row > 0 {
12083 let prev_row = start_row - 1;
12084 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12085 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12086 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12087 {
12088 start_row = prev_row;
12089 } else {
12090 break 'expand_upwards;
12091 }
12092 }
12093
12094 'expand_downwards: while end_row < buffer.max_point().row {
12095 let next_row = end_row + 1;
12096 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12097 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12098 && !buffer.is_line_blank(MultiBufferRow(next_row))
12099 {
12100 end_row = next_row;
12101 } else {
12102 break 'expand_downwards;
12103 }
12104 }
12105 }
12106
12107 let start = Point::new(start_row, 0);
12108 let start_offset = start.to_offset(&buffer);
12109 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12110 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12111 let mut first_line_delimiter = None;
12112 let mut last_line_delimiter = None;
12113 let Some(lines_without_prefixes) = selection_text
12114 .lines()
12115 .enumerate()
12116 .map(|(ix, line)| {
12117 let line_trimmed = line.trim_start();
12118 if rewrap_prefix.is_some() && ix > 0 {
12119 Ok(line_trimmed)
12120 } else if let Some(
12121 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12122 start,
12123 prefix,
12124 end,
12125 tab_size,
12126 })
12127 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12128 start,
12129 prefix,
12130 end,
12131 tab_size,
12132 }),
12133 ) = &comment_prefix
12134 {
12135 let line_trimmed = line_trimmed
12136 .strip_prefix(start.as_ref())
12137 .map(|s| {
12138 let mut indent_size = indent_size;
12139 indent_size.len -= tab_size;
12140 let indent_prefix: String = indent_size.chars().collect();
12141 first_line_delimiter = Some((indent_prefix, start));
12142 s.trim_start()
12143 })
12144 .unwrap_or(line_trimmed);
12145 let line_trimmed = line_trimmed
12146 .strip_suffix(end.as_ref())
12147 .map(|s| {
12148 last_line_delimiter = Some(end);
12149 s.trim_end()
12150 })
12151 .unwrap_or(line_trimmed);
12152 let line_trimmed = line_trimmed
12153 .strip_prefix(prefix.as_ref())
12154 .unwrap_or(line_trimmed);
12155 Ok(line_trimmed)
12156 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12157 line_trimmed.strip_prefix(prefix).with_context(|| {
12158 format!("line did not start with prefix {prefix:?}: {line:?}")
12159 })
12160 } else {
12161 line_trimmed
12162 .strip_prefix(&line_prefix.trim_start())
12163 .with_context(|| {
12164 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12165 })
12166 }
12167 })
12168 .collect::<Result<Vec<_>, _>>()
12169 .log_err()
12170 else {
12171 continue;
12172 };
12173
12174 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12175 buffer
12176 .language_settings_at(Point::new(start_row, 0), cx)
12177 .preferred_line_length as usize
12178 });
12179
12180 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12181 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12182 } else {
12183 line_prefix.clone()
12184 };
12185
12186 let wrapped_text = {
12187 let mut wrapped_text = wrap_with_prefix(
12188 line_prefix,
12189 subsequent_lines_prefix,
12190 lines_without_prefixes.join("\n"),
12191 wrap_column,
12192 tab_size,
12193 options.preserve_existing_whitespace,
12194 );
12195
12196 if let Some((indent, delimiter)) = first_line_delimiter {
12197 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12198 }
12199 if let Some(last_line) = last_line_delimiter {
12200 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12201 }
12202
12203 wrapped_text
12204 };
12205
12206 // TODO: should always use char-based diff while still supporting cursor behavior that
12207 // matches vim.
12208 let mut diff_options = DiffOptions::default();
12209 if options.override_language_settings {
12210 diff_options.max_word_diff_len = 0;
12211 diff_options.max_word_diff_line_count = 0;
12212 } else {
12213 diff_options.max_word_diff_len = usize::MAX;
12214 diff_options.max_word_diff_line_count = usize::MAX;
12215 }
12216
12217 for (old_range, new_text) in
12218 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12219 {
12220 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12221 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12222 edits.push((edit_start..edit_end, new_text));
12223 }
12224
12225 rewrapped_row_ranges.push(start_row..=end_row);
12226 }
12227
12228 self.buffer
12229 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12230 }
12231
12232 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
12233 let mut text = String::new();
12234 let buffer = self.buffer.read(cx).snapshot(cx);
12235 let mut selections = self.selections.all::<Point>(cx);
12236 let mut clipboard_selections = Vec::with_capacity(selections.len());
12237 {
12238 let max_point = buffer.max_point();
12239 let mut is_first = true;
12240 for selection in &mut selections {
12241 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12242 if is_entire_line {
12243 selection.start = Point::new(selection.start.row, 0);
12244 if !selection.is_empty() && selection.end.column == 0 {
12245 selection.end = cmp::min(max_point, selection.end);
12246 } else {
12247 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12248 }
12249 selection.goal = SelectionGoal::None;
12250 }
12251 if is_first {
12252 is_first = false;
12253 } else {
12254 text += "\n";
12255 }
12256 let mut len = 0;
12257 for chunk in buffer.text_for_range(selection.start..selection.end) {
12258 text.push_str(chunk);
12259 len += chunk.len();
12260 }
12261 clipboard_selections.push(ClipboardSelection {
12262 len,
12263 is_entire_line,
12264 first_line_indent: buffer
12265 .indent_size_for_line(MultiBufferRow(selection.start.row))
12266 .len,
12267 });
12268 }
12269 }
12270
12271 self.transact(window, cx, |this, window, cx| {
12272 this.change_selections(Default::default(), window, cx, |s| {
12273 s.select(selections);
12274 });
12275 this.insert("", window, cx);
12276 });
12277 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12278 }
12279
12280 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12282 let item = self.cut_common(window, cx);
12283 cx.write_to_clipboard(item);
12284 }
12285
12286 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12288 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12289 s.move_with(|snapshot, sel| {
12290 if sel.is_empty() {
12291 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
12292 }
12293 });
12294 });
12295 let item = self.cut_common(window, cx);
12296 cx.set_global(KillRing(item))
12297 }
12298
12299 pub fn kill_ring_yank(
12300 &mut self,
12301 _: &KillRingYank,
12302 window: &mut Window,
12303 cx: &mut Context<Self>,
12304 ) {
12305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12306 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12307 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12308 (kill_ring.text().to_string(), kill_ring.metadata_json())
12309 } else {
12310 return;
12311 }
12312 } else {
12313 return;
12314 };
12315 self.do_paste(&text, metadata, false, window, cx);
12316 }
12317
12318 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12319 self.do_copy(true, cx);
12320 }
12321
12322 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12323 self.do_copy(false, cx);
12324 }
12325
12326 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12327 let selections = self.selections.all::<Point>(cx);
12328 let buffer = self.buffer.read(cx).read(cx);
12329 let mut text = String::new();
12330
12331 let mut clipboard_selections = Vec::with_capacity(selections.len());
12332 {
12333 let max_point = buffer.max_point();
12334 let mut is_first = true;
12335 for selection in &selections {
12336 let mut start = selection.start;
12337 let mut end = selection.end;
12338 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12339 if is_entire_line {
12340 start = Point::new(start.row, 0);
12341 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12342 }
12343
12344 let mut trimmed_selections = Vec::new();
12345 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12346 let row = MultiBufferRow(start.row);
12347 let first_indent = buffer.indent_size_for_line(row);
12348 if first_indent.len == 0 || start.column > first_indent.len {
12349 trimmed_selections.push(start..end);
12350 } else {
12351 trimmed_selections.push(
12352 Point::new(row.0, first_indent.len)
12353 ..Point::new(row.0, buffer.line_len(row)),
12354 );
12355 for row in start.row + 1..=end.row {
12356 let mut line_len = buffer.line_len(MultiBufferRow(row));
12357 if row == end.row {
12358 line_len = end.column;
12359 }
12360 if line_len == 0 {
12361 trimmed_selections
12362 .push(Point::new(row, 0)..Point::new(row, line_len));
12363 continue;
12364 }
12365 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12366 if row_indent_size.len >= first_indent.len {
12367 trimmed_selections.push(
12368 Point::new(row, first_indent.len)..Point::new(row, line_len),
12369 );
12370 } else {
12371 trimmed_selections.clear();
12372 trimmed_selections.push(start..end);
12373 break;
12374 }
12375 }
12376 }
12377 } else {
12378 trimmed_selections.push(start..end);
12379 }
12380
12381 for trimmed_range in trimmed_selections {
12382 if is_first {
12383 is_first = false;
12384 } else {
12385 text += "\n";
12386 }
12387 let mut len = 0;
12388 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12389 text.push_str(chunk);
12390 len += chunk.len();
12391 }
12392 clipboard_selections.push(ClipboardSelection {
12393 len,
12394 is_entire_line,
12395 first_line_indent: buffer
12396 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12397 .len,
12398 });
12399 }
12400 }
12401 }
12402
12403 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12404 text,
12405 clipboard_selections,
12406 ));
12407 }
12408
12409 pub fn do_paste(
12410 &mut self,
12411 text: &String,
12412 clipboard_selections: Option<Vec<ClipboardSelection>>,
12413 handle_entire_lines: bool,
12414 window: &mut Window,
12415 cx: &mut Context<Self>,
12416 ) {
12417 if self.read_only(cx) {
12418 return;
12419 }
12420
12421 let clipboard_text = Cow::Borrowed(text);
12422
12423 self.transact(window, cx, |this, window, cx| {
12424 let had_active_edit_prediction = this.has_active_edit_prediction();
12425
12426 if let Some(mut clipboard_selections) = clipboard_selections {
12427 let old_selections = this.selections.all::<usize>(cx);
12428 let all_selections_were_entire_line =
12429 clipboard_selections.iter().all(|s| s.is_entire_line);
12430 let first_selection_indent_column =
12431 clipboard_selections.first().map(|s| s.first_line_indent);
12432 if clipboard_selections.len() != old_selections.len() {
12433 clipboard_selections.drain(..);
12434 }
12435 let cursor_offset = this.selections.last::<usize>(cx).head();
12436 let mut auto_indent_on_paste = true;
12437
12438 this.buffer.update(cx, |buffer, cx| {
12439 let snapshot = buffer.read(cx);
12440 auto_indent_on_paste = snapshot
12441 .language_settings_at(cursor_offset, cx)
12442 .auto_indent_on_paste;
12443
12444 let mut start_offset = 0;
12445 let mut edits = Vec::new();
12446 let mut original_indent_columns = Vec::new();
12447 for (ix, selection) in old_selections.iter().enumerate() {
12448 let to_insert;
12449 let entire_line;
12450 let original_indent_column;
12451 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12452 let end_offset = start_offset + clipboard_selection.len;
12453 to_insert = &clipboard_text[start_offset..end_offset];
12454 entire_line = clipboard_selection.is_entire_line;
12455 start_offset = end_offset + 1;
12456 original_indent_column = Some(clipboard_selection.first_line_indent);
12457 } else {
12458 to_insert = clipboard_text.as_str();
12459 entire_line = all_selections_were_entire_line;
12460 original_indent_column = first_selection_indent_column
12461 }
12462
12463 // If the corresponding selection was empty when this slice of the
12464 // clipboard text was written, then the entire line containing the
12465 // selection was copied. If this selection is also currently empty,
12466 // then paste the line before the current line of the buffer.
12467 let range = if selection.is_empty() && handle_entire_lines && entire_line {
12468 let column = selection.start.to_point(&snapshot).column as usize;
12469 let line_start = selection.start - column;
12470 line_start..line_start
12471 } else {
12472 selection.range()
12473 };
12474
12475 edits.push((range, to_insert));
12476 original_indent_columns.push(original_indent_column);
12477 }
12478 drop(snapshot);
12479
12480 buffer.edit(
12481 edits,
12482 if auto_indent_on_paste {
12483 Some(AutoindentMode::Block {
12484 original_indent_columns,
12485 })
12486 } else {
12487 None
12488 },
12489 cx,
12490 );
12491 });
12492
12493 let selections = this.selections.all::<usize>(cx);
12494 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12495 } else {
12496 this.insert(&clipboard_text, window, cx);
12497 }
12498
12499 let trigger_in_words =
12500 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12501
12502 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12503 });
12504 }
12505
12506 pub fn diff_clipboard_with_selection(
12507 &mut self,
12508 _: &DiffClipboardWithSelection,
12509 window: &mut Window,
12510 cx: &mut Context<Self>,
12511 ) {
12512 let selections = self.selections.all::<usize>(cx);
12513
12514 if selections.is_empty() {
12515 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12516 return;
12517 };
12518
12519 let clipboard_text = match cx.read_from_clipboard() {
12520 Some(item) => match item.entries().first() {
12521 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12522 _ => None,
12523 },
12524 None => None,
12525 };
12526
12527 let Some(clipboard_text) = clipboard_text else {
12528 log::warn!("Clipboard doesn't contain text.");
12529 return;
12530 };
12531
12532 window.dispatch_action(
12533 Box::new(DiffClipboardWithSelectionData {
12534 clipboard_text,
12535 editor: cx.entity(),
12536 }),
12537 cx,
12538 );
12539 }
12540
12541 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12542 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12543 if let Some(item) = cx.read_from_clipboard() {
12544 let entries = item.entries();
12545
12546 match entries.first() {
12547 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12548 // of all the pasted entries.
12549 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12550 .do_paste(
12551 clipboard_string.text(),
12552 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12553 true,
12554 window,
12555 cx,
12556 ),
12557 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12558 }
12559 }
12560 }
12561
12562 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12563 if self.read_only(cx) {
12564 return;
12565 }
12566
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12568
12569 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12570 if let Some((selections, _)) =
12571 self.selection_history.transaction(transaction_id).cloned()
12572 {
12573 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12574 s.select_anchors(selections.to_vec());
12575 });
12576 } else {
12577 log::error!(
12578 "No entry in selection_history found for undo. \
12579 This may correspond to a bug where undo does not update the selection. \
12580 If this is occurring, please add details to \
12581 https://github.com/zed-industries/zed/issues/22692"
12582 );
12583 }
12584 self.request_autoscroll(Autoscroll::fit(), cx);
12585 self.unmark_text(window, cx);
12586 self.refresh_edit_prediction(true, false, window, cx);
12587 cx.emit(EditorEvent::Edited { transaction_id });
12588 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12589 }
12590 }
12591
12592 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12593 if self.read_only(cx) {
12594 return;
12595 }
12596
12597 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12598
12599 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12600 if let Some((_, Some(selections))) =
12601 self.selection_history.transaction(transaction_id).cloned()
12602 {
12603 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12604 s.select_anchors(selections.to_vec());
12605 });
12606 } else {
12607 log::error!(
12608 "No entry in selection_history found for redo. \
12609 This may correspond to a bug where undo does not update the selection. \
12610 If this is occurring, please add details to \
12611 https://github.com/zed-industries/zed/issues/22692"
12612 );
12613 }
12614 self.request_autoscroll(Autoscroll::fit(), cx);
12615 self.unmark_text(window, cx);
12616 self.refresh_edit_prediction(true, false, window, cx);
12617 cx.emit(EditorEvent::Edited { transaction_id });
12618 }
12619 }
12620
12621 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12622 self.buffer
12623 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12624 }
12625
12626 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12627 self.buffer
12628 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12629 }
12630
12631 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12633 self.change_selections(Default::default(), window, cx, |s| {
12634 s.move_with(|map, selection| {
12635 let cursor = if selection.is_empty() {
12636 movement::left(map, selection.start)
12637 } else {
12638 selection.start
12639 };
12640 selection.collapse_to(cursor, SelectionGoal::None);
12641 });
12642 })
12643 }
12644
12645 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12646 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12647 self.change_selections(Default::default(), window, cx, |s| {
12648 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12649 })
12650 }
12651
12652 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12654 self.change_selections(Default::default(), window, cx, |s| {
12655 s.move_with(|map, selection| {
12656 let cursor = if selection.is_empty() {
12657 movement::right(map, selection.end)
12658 } else {
12659 selection.end
12660 };
12661 selection.collapse_to(cursor, SelectionGoal::None)
12662 });
12663 })
12664 }
12665
12666 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12668 self.change_selections(Default::default(), window, cx, |s| {
12669 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12670 })
12671 }
12672
12673 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12674 if self.take_rename(true, window, cx).is_some() {
12675 return;
12676 }
12677
12678 if self.mode.is_single_line() {
12679 cx.propagate();
12680 return;
12681 }
12682
12683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12684
12685 let text_layout_details = &self.text_layout_details(window);
12686 let selection_count = self.selections.count();
12687 let first_selection = self.selections.first_anchor();
12688
12689 self.change_selections(Default::default(), window, cx, |s| {
12690 s.move_with(|map, selection| {
12691 if !selection.is_empty() {
12692 selection.goal = SelectionGoal::None;
12693 }
12694 let (cursor, goal) = movement::up(
12695 map,
12696 selection.start,
12697 selection.goal,
12698 false,
12699 text_layout_details,
12700 );
12701 selection.collapse_to(cursor, goal);
12702 });
12703 });
12704
12705 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12706 {
12707 cx.propagate();
12708 }
12709 }
12710
12711 pub fn move_up_by_lines(
12712 &mut self,
12713 action: &MoveUpByLines,
12714 window: &mut Window,
12715 cx: &mut Context<Self>,
12716 ) {
12717 if self.take_rename(true, window, cx).is_some() {
12718 return;
12719 }
12720
12721 if self.mode.is_single_line() {
12722 cx.propagate();
12723 return;
12724 }
12725
12726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12727
12728 let text_layout_details = &self.text_layout_details(window);
12729
12730 self.change_selections(Default::default(), window, cx, |s| {
12731 s.move_with(|map, selection| {
12732 if !selection.is_empty() {
12733 selection.goal = SelectionGoal::None;
12734 }
12735 let (cursor, goal) = movement::up_by_rows(
12736 map,
12737 selection.start,
12738 action.lines,
12739 selection.goal,
12740 false,
12741 text_layout_details,
12742 );
12743 selection.collapse_to(cursor, goal);
12744 });
12745 })
12746 }
12747
12748 pub fn move_down_by_lines(
12749 &mut self,
12750 action: &MoveDownByLines,
12751 window: &mut Window,
12752 cx: &mut Context<Self>,
12753 ) {
12754 if self.take_rename(true, window, cx).is_some() {
12755 return;
12756 }
12757
12758 if self.mode.is_single_line() {
12759 cx.propagate();
12760 return;
12761 }
12762
12763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12764
12765 let text_layout_details = &self.text_layout_details(window);
12766
12767 self.change_selections(Default::default(), window, cx, |s| {
12768 s.move_with(|map, selection| {
12769 if !selection.is_empty() {
12770 selection.goal = SelectionGoal::None;
12771 }
12772 let (cursor, goal) = movement::down_by_rows(
12773 map,
12774 selection.start,
12775 action.lines,
12776 selection.goal,
12777 false,
12778 text_layout_details,
12779 );
12780 selection.collapse_to(cursor, goal);
12781 });
12782 })
12783 }
12784
12785 pub fn select_down_by_lines(
12786 &mut self,
12787 action: &SelectDownByLines,
12788 window: &mut Window,
12789 cx: &mut Context<Self>,
12790 ) {
12791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12792 let text_layout_details = &self.text_layout_details(window);
12793 self.change_selections(Default::default(), window, cx, |s| {
12794 s.move_heads_with(|map, head, goal| {
12795 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12796 })
12797 })
12798 }
12799
12800 pub fn select_up_by_lines(
12801 &mut self,
12802 action: &SelectUpByLines,
12803 window: &mut Window,
12804 cx: &mut Context<Self>,
12805 ) {
12806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12807 let text_layout_details = &self.text_layout_details(window);
12808 self.change_selections(Default::default(), window, cx, |s| {
12809 s.move_heads_with(|map, head, goal| {
12810 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12811 })
12812 })
12813 }
12814
12815 pub fn select_page_up(
12816 &mut self,
12817 _: &SelectPageUp,
12818 window: &mut Window,
12819 cx: &mut Context<Self>,
12820 ) {
12821 let Some(row_count) = self.visible_row_count() else {
12822 return;
12823 };
12824
12825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12826
12827 let text_layout_details = &self.text_layout_details(window);
12828
12829 self.change_selections(Default::default(), window, cx, |s| {
12830 s.move_heads_with(|map, head, goal| {
12831 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12832 })
12833 })
12834 }
12835
12836 pub fn move_page_up(
12837 &mut self,
12838 action: &MovePageUp,
12839 window: &mut Window,
12840 cx: &mut Context<Self>,
12841 ) {
12842 if self.take_rename(true, window, cx).is_some() {
12843 return;
12844 }
12845
12846 if self
12847 .context_menu
12848 .borrow_mut()
12849 .as_mut()
12850 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12851 .unwrap_or(false)
12852 {
12853 return;
12854 }
12855
12856 if matches!(self.mode, EditorMode::SingleLine) {
12857 cx.propagate();
12858 return;
12859 }
12860
12861 let Some(row_count) = self.visible_row_count() else {
12862 return;
12863 };
12864
12865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12866
12867 let effects = if action.center_cursor {
12868 SelectionEffects::scroll(Autoscroll::center())
12869 } else {
12870 SelectionEffects::default()
12871 };
12872
12873 let text_layout_details = &self.text_layout_details(window);
12874
12875 self.change_selections(effects, window, cx, |s| {
12876 s.move_with(|map, selection| {
12877 if !selection.is_empty() {
12878 selection.goal = SelectionGoal::None;
12879 }
12880 let (cursor, goal) = movement::up_by_rows(
12881 map,
12882 selection.end,
12883 row_count,
12884 selection.goal,
12885 false,
12886 text_layout_details,
12887 );
12888 selection.collapse_to(cursor, goal);
12889 });
12890 });
12891 }
12892
12893 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12895 let text_layout_details = &self.text_layout_details(window);
12896 self.change_selections(Default::default(), window, cx, |s| {
12897 s.move_heads_with(|map, head, goal| {
12898 movement::up(map, head, goal, false, text_layout_details)
12899 })
12900 })
12901 }
12902
12903 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12904 self.take_rename(true, window, cx);
12905
12906 if self.mode.is_single_line() {
12907 cx.propagate();
12908 return;
12909 }
12910
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912
12913 let text_layout_details = &self.text_layout_details(window);
12914 let selection_count = self.selections.count();
12915 let first_selection = self.selections.first_anchor();
12916
12917 self.change_selections(Default::default(), window, cx, |s| {
12918 s.move_with(|map, selection| {
12919 if !selection.is_empty() {
12920 selection.goal = SelectionGoal::None;
12921 }
12922 let (cursor, goal) = movement::down(
12923 map,
12924 selection.end,
12925 selection.goal,
12926 false,
12927 text_layout_details,
12928 );
12929 selection.collapse_to(cursor, goal);
12930 });
12931 });
12932
12933 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12934 {
12935 cx.propagate();
12936 }
12937 }
12938
12939 pub fn select_page_down(
12940 &mut self,
12941 _: &SelectPageDown,
12942 window: &mut Window,
12943 cx: &mut Context<Self>,
12944 ) {
12945 let Some(row_count) = self.visible_row_count() else {
12946 return;
12947 };
12948
12949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12950
12951 let text_layout_details = &self.text_layout_details(window);
12952
12953 self.change_selections(Default::default(), window, cx, |s| {
12954 s.move_heads_with(|map, head, goal| {
12955 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12956 })
12957 })
12958 }
12959
12960 pub fn move_page_down(
12961 &mut self,
12962 action: &MovePageDown,
12963 window: &mut Window,
12964 cx: &mut Context<Self>,
12965 ) {
12966 if self.take_rename(true, window, cx).is_some() {
12967 return;
12968 }
12969
12970 if self
12971 .context_menu
12972 .borrow_mut()
12973 .as_mut()
12974 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12975 .unwrap_or(false)
12976 {
12977 return;
12978 }
12979
12980 if matches!(self.mode, EditorMode::SingleLine) {
12981 cx.propagate();
12982 return;
12983 }
12984
12985 let Some(row_count) = self.visible_row_count() else {
12986 return;
12987 };
12988
12989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12990
12991 let effects = if action.center_cursor {
12992 SelectionEffects::scroll(Autoscroll::center())
12993 } else {
12994 SelectionEffects::default()
12995 };
12996
12997 let text_layout_details = &self.text_layout_details(window);
12998 self.change_selections(effects, window, cx, |s| {
12999 s.move_with(|map, selection| {
13000 if !selection.is_empty() {
13001 selection.goal = SelectionGoal::None;
13002 }
13003 let (cursor, goal) = movement::down_by_rows(
13004 map,
13005 selection.end,
13006 row_count,
13007 selection.goal,
13008 false,
13009 text_layout_details,
13010 );
13011 selection.collapse_to(cursor, goal);
13012 });
13013 });
13014 }
13015
13016 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13018 let text_layout_details = &self.text_layout_details(window);
13019 self.change_selections(Default::default(), window, cx, |s| {
13020 s.move_heads_with(|map, head, goal| {
13021 movement::down(map, head, goal, false, text_layout_details)
13022 })
13023 });
13024 }
13025
13026 pub fn context_menu_first(
13027 &mut self,
13028 _: &ContextMenuFirst,
13029 window: &mut Window,
13030 cx: &mut Context<Self>,
13031 ) {
13032 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13033 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13034 }
13035 }
13036
13037 pub fn context_menu_prev(
13038 &mut self,
13039 _: &ContextMenuPrevious,
13040 window: &mut Window,
13041 cx: &mut Context<Self>,
13042 ) {
13043 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13044 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13045 }
13046 }
13047
13048 pub fn context_menu_next(
13049 &mut self,
13050 _: &ContextMenuNext,
13051 window: &mut Window,
13052 cx: &mut Context<Self>,
13053 ) {
13054 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13055 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13056 }
13057 }
13058
13059 pub fn context_menu_last(
13060 &mut self,
13061 _: &ContextMenuLast,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13066 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13067 }
13068 }
13069
13070 pub fn signature_help_prev(
13071 &mut self,
13072 _: &SignatureHelpPrevious,
13073 _: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) {
13076 if let Some(popover) = self.signature_help_state.popover_mut() {
13077 if popover.current_signature == 0 {
13078 popover.current_signature = popover.signatures.len() - 1;
13079 } else {
13080 popover.current_signature -= 1;
13081 }
13082 cx.notify();
13083 }
13084 }
13085
13086 pub fn signature_help_next(
13087 &mut self,
13088 _: &SignatureHelpNext,
13089 _: &mut Window,
13090 cx: &mut Context<Self>,
13091 ) {
13092 if let Some(popover) = self.signature_help_state.popover_mut() {
13093 if popover.current_signature + 1 == popover.signatures.len() {
13094 popover.current_signature = 0;
13095 } else {
13096 popover.current_signature += 1;
13097 }
13098 cx.notify();
13099 }
13100 }
13101
13102 pub fn move_to_previous_word_start(
13103 &mut self,
13104 _: &MoveToPreviousWordStart,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13109 self.change_selections(Default::default(), window, cx, |s| {
13110 s.move_cursors_with(|map, head, _| {
13111 (
13112 movement::previous_word_start(map, head),
13113 SelectionGoal::None,
13114 )
13115 });
13116 })
13117 }
13118
13119 pub fn move_to_previous_subword_start(
13120 &mut self,
13121 _: &MoveToPreviousSubwordStart,
13122 window: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) {
13125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13126 self.change_selections(Default::default(), window, cx, |s| {
13127 s.move_cursors_with(|map, head, _| {
13128 (
13129 movement::previous_subword_start(map, head),
13130 SelectionGoal::None,
13131 )
13132 });
13133 })
13134 }
13135
13136 pub fn select_to_previous_word_start(
13137 &mut self,
13138 _: &SelectToPreviousWordStart,
13139 window: &mut Window,
13140 cx: &mut Context<Self>,
13141 ) {
13142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13143 self.change_selections(Default::default(), window, cx, |s| {
13144 s.move_heads_with(|map, head, _| {
13145 (
13146 movement::previous_word_start(map, head),
13147 SelectionGoal::None,
13148 )
13149 });
13150 })
13151 }
13152
13153 pub fn select_to_previous_subword_start(
13154 &mut self,
13155 _: &SelectToPreviousSubwordStart,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13160 self.change_selections(Default::default(), window, cx, |s| {
13161 s.move_heads_with(|map, head, _| {
13162 (
13163 movement::previous_subword_start(map, head),
13164 SelectionGoal::None,
13165 )
13166 });
13167 })
13168 }
13169
13170 pub fn delete_to_previous_word_start(
13171 &mut self,
13172 action: &DeleteToPreviousWordStart,
13173 window: &mut Window,
13174 cx: &mut Context<Self>,
13175 ) {
13176 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13177 self.transact(window, cx, |this, window, cx| {
13178 this.select_autoclose_pair(window, cx);
13179 this.change_selections(Default::default(), window, cx, |s| {
13180 s.move_with(|map, selection| {
13181 if selection.is_empty() {
13182 let mut cursor = if action.ignore_newlines {
13183 movement::previous_word_start(map, selection.head())
13184 } else {
13185 movement::previous_word_start_or_newline(map, selection.head())
13186 };
13187 cursor = movement::adjust_greedy_deletion(
13188 map,
13189 selection.head(),
13190 cursor,
13191 action.ignore_brackets,
13192 );
13193 selection.set_head(cursor, SelectionGoal::None);
13194 }
13195 });
13196 });
13197 this.insert("", window, cx);
13198 });
13199 }
13200
13201 pub fn delete_to_previous_subword_start(
13202 &mut self,
13203 _: &DeleteToPreviousSubwordStart,
13204 window: &mut Window,
13205 cx: &mut Context<Self>,
13206 ) {
13207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13208 self.transact(window, cx, |this, window, cx| {
13209 this.select_autoclose_pair(window, cx);
13210 this.change_selections(Default::default(), window, cx, |s| {
13211 s.move_with(|map, selection| {
13212 if selection.is_empty() {
13213 let mut cursor = movement::previous_subword_start(map, selection.head());
13214 cursor =
13215 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13216 selection.set_head(cursor, SelectionGoal::None);
13217 }
13218 });
13219 });
13220 this.insert("", window, cx);
13221 });
13222 }
13223
13224 pub fn move_to_next_word_end(
13225 &mut self,
13226 _: &MoveToNextWordEnd,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_cursors_with(|map, head, _| {
13233 (movement::next_word_end(map, head), SelectionGoal::None)
13234 });
13235 })
13236 }
13237
13238 pub fn move_to_next_subword_end(
13239 &mut self,
13240 _: &MoveToNextSubwordEnd,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) {
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245 self.change_selections(Default::default(), window, cx, |s| {
13246 s.move_cursors_with(|map, head, _| {
13247 (movement::next_subword_end(map, head), SelectionGoal::None)
13248 });
13249 })
13250 }
13251
13252 pub fn select_to_next_word_end(
13253 &mut self,
13254 _: &SelectToNextWordEnd,
13255 window: &mut Window,
13256 cx: &mut Context<Self>,
13257 ) {
13258 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13259 self.change_selections(Default::default(), window, cx, |s| {
13260 s.move_heads_with(|map, head, _| {
13261 (movement::next_word_end(map, head), SelectionGoal::None)
13262 });
13263 })
13264 }
13265
13266 pub fn select_to_next_subword_end(
13267 &mut self,
13268 _: &SelectToNextSubwordEnd,
13269 window: &mut Window,
13270 cx: &mut Context<Self>,
13271 ) {
13272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13273 self.change_selections(Default::default(), window, cx, |s| {
13274 s.move_heads_with(|map, head, _| {
13275 (movement::next_subword_end(map, head), SelectionGoal::None)
13276 });
13277 })
13278 }
13279
13280 pub fn delete_to_next_word_end(
13281 &mut self,
13282 action: &DeleteToNextWordEnd,
13283 window: &mut Window,
13284 cx: &mut Context<Self>,
13285 ) {
13286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13287 self.transact(window, cx, |this, window, cx| {
13288 this.change_selections(Default::default(), window, cx, |s| {
13289 s.move_with(|map, selection| {
13290 if selection.is_empty() {
13291 let mut cursor = if action.ignore_newlines {
13292 movement::next_word_end(map, selection.head())
13293 } else {
13294 movement::next_word_end_or_newline(map, selection.head())
13295 };
13296 cursor = movement::adjust_greedy_deletion(
13297 map,
13298 selection.head(),
13299 cursor,
13300 action.ignore_brackets,
13301 );
13302 selection.set_head(cursor, SelectionGoal::None);
13303 }
13304 });
13305 });
13306 this.insert("", window, cx);
13307 });
13308 }
13309
13310 pub fn delete_to_next_subword_end(
13311 &mut self,
13312 _: &DeleteToNextSubwordEnd,
13313 window: &mut Window,
13314 cx: &mut Context<Self>,
13315 ) {
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13317 self.transact(window, cx, |this, window, cx| {
13318 this.change_selections(Default::default(), window, cx, |s| {
13319 s.move_with(|map, selection| {
13320 if selection.is_empty() {
13321 let mut cursor = movement::next_subword_end(map, selection.head());
13322 cursor =
13323 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13324 selection.set_head(cursor, SelectionGoal::None);
13325 }
13326 });
13327 });
13328 this.insert("", window, cx);
13329 });
13330 }
13331
13332 pub fn move_to_beginning_of_line(
13333 &mut self,
13334 action: &MoveToBeginningOfLine,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13339 self.change_selections(Default::default(), window, cx, |s| {
13340 s.move_cursors_with(|map, head, _| {
13341 (
13342 movement::indented_line_beginning(
13343 map,
13344 head,
13345 action.stop_at_soft_wraps,
13346 action.stop_at_indent,
13347 ),
13348 SelectionGoal::None,
13349 )
13350 });
13351 })
13352 }
13353
13354 pub fn select_to_beginning_of_line(
13355 &mut self,
13356 action: &SelectToBeginningOfLine,
13357 window: &mut Window,
13358 cx: &mut Context<Self>,
13359 ) {
13360 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13361 self.change_selections(Default::default(), window, cx, |s| {
13362 s.move_heads_with(|map, head, _| {
13363 (
13364 movement::indented_line_beginning(
13365 map,
13366 head,
13367 action.stop_at_soft_wraps,
13368 action.stop_at_indent,
13369 ),
13370 SelectionGoal::None,
13371 )
13372 });
13373 });
13374 }
13375
13376 pub fn delete_to_beginning_of_line(
13377 &mut self,
13378 action: &DeleteToBeginningOfLine,
13379 window: &mut Window,
13380 cx: &mut Context<Self>,
13381 ) {
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13383 self.transact(window, cx, |this, window, cx| {
13384 this.change_selections(Default::default(), window, cx, |s| {
13385 s.move_with(|_, selection| {
13386 selection.reversed = true;
13387 });
13388 });
13389
13390 this.select_to_beginning_of_line(
13391 &SelectToBeginningOfLine {
13392 stop_at_soft_wraps: false,
13393 stop_at_indent: action.stop_at_indent,
13394 },
13395 window,
13396 cx,
13397 );
13398 this.backspace(&Backspace, window, cx);
13399 });
13400 }
13401
13402 pub fn move_to_end_of_line(
13403 &mut self,
13404 action: &MoveToEndOfLine,
13405 window: &mut Window,
13406 cx: &mut Context<Self>,
13407 ) {
13408 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13409 self.change_selections(Default::default(), window, cx, |s| {
13410 s.move_cursors_with(|map, head, _| {
13411 (
13412 movement::line_end(map, head, action.stop_at_soft_wraps),
13413 SelectionGoal::None,
13414 )
13415 });
13416 })
13417 }
13418
13419 pub fn select_to_end_of_line(
13420 &mut self,
13421 action: &SelectToEndOfLine,
13422 window: &mut Window,
13423 cx: &mut Context<Self>,
13424 ) {
13425 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13426 self.change_selections(Default::default(), window, cx, |s| {
13427 s.move_heads_with(|map, head, _| {
13428 (
13429 movement::line_end(map, head, action.stop_at_soft_wraps),
13430 SelectionGoal::None,
13431 )
13432 });
13433 })
13434 }
13435
13436 pub fn delete_to_end_of_line(
13437 &mut self,
13438 _: &DeleteToEndOfLine,
13439 window: &mut Window,
13440 cx: &mut Context<Self>,
13441 ) {
13442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13443 self.transact(window, cx, |this, window, cx| {
13444 this.select_to_end_of_line(
13445 &SelectToEndOfLine {
13446 stop_at_soft_wraps: false,
13447 },
13448 window,
13449 cx,
13450 );
13451 this.delete(&Delete, window, cx);
13452 });
13453 }
13454
13455 pub fn cut_to_end_of_line(
13456 &mut self,
13457 _: &CutToEndOfLine,
13458 window: &mut Window,
13459 cx: &mut Context<Self>,
13460 ) {
13461 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13462 self.transact(window, cx, |this, window, cx| {
13463 this.select_to_end_of_line(
13464 &SelectToEndOfLine {
13465 stop_at_soft_wraps: false,
13466 },
13467 window,
13468 cx,
13469 );
13470 this.cut(&Cut, window, cx);
13471 });
13472 }
13473
13474 pub fn move_to_start_of_paragraph(
13475 &mut self,
13476 _: &MoveToStartOfParagraph,
13477 window: &mut Window,
13478 cx: &mut Context<Self>,
13479 ) {
13480 if matches!(self.mode, EditorMode::SingleLine) {
13481 cx.propagate();
13482 return;
13483 }
13484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13485 self.change_selections(Default::default(), window, cx, |s| {
13486 s.move_with(|map, selection| {
13487 selection.collapse_to(
13488 movement::start_of_paragraph(map, selection.head(), 1),
13489 SelectionGoal::None,
13490 )
13491 });
13492 })
13493 }
13494
13495 pub fn move_to_end_of_paragraph(
13496 &mut self,
13497 _: &MoveToEndOfParagraph,
13498 window: &mut Window,
13499 cx: &mut Context<Self>,
13500 ) {
13501 if matches!(self.mode, EditorMode::SingleLine) {
13502 cx.propagate();
13503 return;
13504 }
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.move_with(|map, selection| {
13508 selection.collapse_to(
13509 movement::end_of_paragraph(map, selection.head(), 1),
13510 SelectionGoal::None,
13511 )
13512 });
13513 })
13514 }
13515
13516 pub fn select_to_start_of_paragraph(
13517 &mut self,
13518 _: &SelectToStartOfParagraph,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 if matches!(self.mode, EditorMode::SingleLine) {
13523 cx.propagate();
13524 return;
13525 }
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_heads_with(|map, head, _| {
13529 (
13530 movement::start_of_paragraph(map, head, 1),
13531 SelectionGoal::None,
13532 )
13533 });
13534 })
13535 }
13536
13537 pub fn select_to_end_of_paragraph(
13538 &mut self,
13539 _: &SelectToEndOfParagraph,
13540 window: &mut Window,
13541 cx: &mut Context<Self>,
13542 ) {
13543 if matches!(self.mode, EditorMode::SingleLine) {
13544 cx.propagate();
13545 return;
13546 }
13547 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.move_heads_with(|map, head, _| {
13550 (
13551 movement::end_of_paragraph(map, head, 1),
13552 SelectionGoal::None,
13553 )
13554 });
13555 })
13556 }
13557
13558 pub fn move_to_start_of_excerpt(
13559 &mut self,
13560 _: &MoveToStartOfExcerpt,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 if matches!(self.mode, EditorMode::SingleLine) {
13565 cx.propagate();
13566 return;
13567 }
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_with(|map, selection| {
13571 selection.collapse_to(
13572 movement::start_of_excerpt(
13573 map,
13574 selection.head(),
13575 workspace::searchable::Direction::Prev,
13576 ),
13577 SelectionGoal::None,
13578 )
13579 });
13580 })
13581 }
13582
13583 pub fn move_to_start_of_next_excerpt(
13584 &mut self,
13585 _: &MoveToStartOfNextExcerpt,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 if matches!(self.mode, EditorMode::SingleLine) {
13590 cx.propagate();
13591 return;
13592 }
13593
13594 self.change_selections(Default::default(), window, cx, |s| {
13595 s.move_with(|map, selection| {
13596 selection.collapse_to(
13597 movement::start_of_excerpt(
13598 map,
13599 selection.head(),
13600 workspace::searchable::Direction::Next,
13601 ),
13602 SelectionGoal::None,
13603 )
13604 });
13605 })
13606 }
13607
13608 pub fn move_to_end_of_excerpt(
13609 &mut self,
13610 _: &MoveToEndOfExcerpt,
13611 window: &mut Window,
13612 cx: &mut Context<Self>,
13613 ) {
13614 if matches!(self.mode, EditorMode::SingleLine) {
13615 cx.propagate();
13616 return;
13617 }
13618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13619 self.change_selections(Default::default(), window, cx, |s| {
13620 s.move_with(|map, selection| {
13621 selection.collapse_to(
13622 movement::end_of_excerpt(
13623 map,
13624 selection.head(),
13625 workspace::searchable::Direction::Next,
13626 ),
13627 SelectionGoal::None,
13628 )
13629 });
13630 })
13631 }
13632
13633 pub fn move_to_end_of_previous_excerpt(
13634 &mut self,
13635 _: &MoveToEndOfPreviousExcerpt,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 if matches!(self.mode, EditorMode::SingleLine) {
13640 cx.propagate();
13641 return;
13642 }
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13644 self.change_selections(Default::default(), window, cx, |s| {
13645 s.move_with(|map, selection| {
13646 selection.collapse_to(
13647 movement::end_of_excerpt(
13648 map,
13649 selection.head(),
13650 workspace::searchable::Direction::Prev,
13651 ),
13652 SelectionGoal::None,
13653 )
13654 });
13655 })
13656 }
13657
13658 pub fn select_to_start_of_excerpt(
13659 &mut self,
13660 _: &SelectToStartOfExcerpt,
13661 window: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 if matches!(self.mode, EditorMode::SingleLine) {
13665 cx.propagate();
13666 return;
13667 }
13668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13669 self.change_selections(Default::default(), window, cx, |s| {
13670 s.move_heads_with(|map, head, _| {
13671 (
13672 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13673 SelectionGoal::None,
13674 )
13675 });
13676 })
13677 }
13678
13679 pub fn select_to_start_of_next_excerpt(
13680 &mut self,
13681 _: &SelectToStartOfNextExcerpt,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 if matches!(self.mode, EditorMode::SingleLine) {
13686 cx.propagate();
13687 return;
13688 }
13689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13690 self.change_selections(Default::default(), window, cx, |s| {
13691 s.move_heads_with(|map, head, _| {
13692 (
13693 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13694 SelectionGoal::None,
13695 )
13696 });
13697 })
13698 }
13699
13700 pub fn select_to_end_of_excerpt(
13701 &mut self,
13702 _: &SelectToEndOfExcerpt,
13703 window: &mut Window,
13704 cx: &mut Context<Self>,
13705 ) {
13706 if matches!(self.mode, EditorMode::SingleLine) {
13707 cx.propagate();
13708 return;
13709 }
13710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13711 self.change_selections(Default::default(), window, cx, |s| {
13712 s.move_heads_with(|map, head, _| {
13713 (
13714 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13715 SelectionGoal::None,
13716 )
13717 });
13718 })
13719 }
13720
13721 pub fn select_to_end_of_previous_excerpt(
13722 &mut self,
13723 _: &SelectToEndOfPreviousExcerpt,
13724 window: &mut Window,
13725 cx: &mut Context<Self>,
13726 ) {
13727 if matches!(self.mode, EditorMode::SingleLine) {
13728 cx.propagate();
13729 return;
13730 }
13731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13732 self.change_selections(Default::default(), window, cx, |s| {
13733 s.move_heads_with(|map, head, _| {
13734 (
13735 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13736 SelectionGoal::None,
13737 )
13738 });
13739 })
13740 }
13741
13742 pub fn move_to_beginning(
13743 &mut self,
13744 _: &MoveToBeginning,
13745 window: &mut Window,
13746 cx: &mut Context<Self>,
13747 ) {
13748 if matches!(self.mode, EditorMode::SingleLine) {
13749 cx.propagate();
13750 return;
13751 }
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.select_ranges(vec![0..0]);
13755 });
13756 }
13757
13758 pub fn select_to_beginning(
13759 &mut self,
13760 _: &SelectToBeginning,
13761 window: &mut Window,
13762 cx: &mut Context<Self>,
13763 ) {
13764 let mut selection = self.selections.last::<Point>(cx);
13765 selection.set_head(Point::zero(), SelectionGoal::None);
13766 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13767 self.change_selections(Default::default(), window, cx, |s| {
13768 s.select(vec![selection]);
13769 });
13770 }
13771
13772 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13773 if matches!(self.mode, EditorMode::SingleLine) {
13774 cx.propagate();
13775 return;
13776 }
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778 let cursor = self.buffer.read(cx).read(cx).len();
13779 self.change_selections(Default::default(), window, cx, |s| {
13780 s.select_ranges(vec![cursor..cursor])
13781 });
13782 }
13783
13784 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13785 self.nav_history = nav_history;
13786 }
13787
13788 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13789 self.nav_history.as_ref()
13790 }
13791
13792 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13793 self.push_to_nav_history(
13794 self.selections.newest_anchor().head(),
13795 None,
13796 false,
13797 true,
13798 cx,
13799 );
13800 }
13801
13802 fn push_to_nav_history(
13803 &mut self,
13804 cursor_anchor: Anchor,
13805 new_position: Option<Point>,
13806 is_deactivate: bool,
13807 always: bool,
13808 cx: &mut Context<Self>,
13809 ) {
13810 if let Some(nav_history) = self.nav_history.as_mut() {
13811 let buffer = self.buffer.read(cx).read(cx);
13812 let cursor_position = cursor_anchor.to_point(&buffer);
13813 let scroll_state = self.scroll_manager.anchor();
13814 let scroll_top_row = scroll_state.top_row(&buffer);
13815 drop(buffer);
13816
13817 if let Some(new_position) = new_position {
13818 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13819 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13820 return;
13821 }
13822 }
13823
13824 nav_history.push(
13825 Some(NavigationData {
13826 cursor_anchor,
13827 cursor_position,
13828 scroll_anchor: scroll_state,
13829 scroll_top_row,
13830 }),
13831 cx,
13832 );
13833 cx.emit(EditorEvent::PushedToNavHistory {
13834 anchor: cursor_anchor,
13835 is_deactivate,
13836 })
13837 }
13838 }
13839
13840 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13842 let buffer = self.buffer.read(cx).snapshot(cx);
13843 let mut selection = self.selections.first::<usize>(cx);
13844 selection.set_head(buffer.len(), SelectionGoal::None);
13845 self.change_selections(Default::default(), window, cx, |s| {
13846 s.select(vec![selection]);
13847 });
13848 }
13849
13850 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13852 let end = self.buffer.read(cx).read(cx).len();
13853 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13854 s.select_ranges(vec![0..end]);
13855 });
13856 }
13857
13858 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13860 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13861 let mut selections = self.selections.all::<Point>(cx);
13862 let max_point = display_map.buffer_snapshot.max_point();
13863 for selection in &mut selections {
13864 let rows = selection.spanned_rows(true, &display_map);
13865 selection.start = Point::new(rows.start.0, 0);
13866 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13867 selection.reversed = false;
13868 }
13869 self.change_selections(Default::default(), window, cx, |s| {
13870 s.select(selections);
13871 });
13872 }
13873
13874 pub fn split_selection_into_lines(
13875 &mut self,
13876 action: &SplitSelectionIntoLines,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 let selections = self
13881 .selections
13882 .all::<Point>(cx)
13883 .into_iter()
13884 .map(|selection| selection.start..selection.end)
13885 .collect::<Vec<_>>();
13886 self.unfold_ranges(&selections, true, true, cx);
13887
13888 let mut new_selection_ranges = Vec::new();
13889 {
13890 let buffer = self.buffer.read(cx).read(cx);
13891 for selection in selections {
13892 for row in selection.start.row..selection.end.row {
13893 let line_start = Point::new(row, 0);
13894 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13895
13896 if action.keep_selections {
13897 // Keep the selection range for each line
13898 let selection_start = if row == selection.start.row {
13899 selection.start
13900 } else {
13901 line_start
13902 };
13903 new_selection_ranges.push(selection_start..line_end);
13904 } else {
13905 // Collapse to cursor at end of line
13906 new_selection_ranges.push(line_end..line_end);
13907 }
13908 }
13909
13910 let is_multiline_selection = selection.start.row != selection.end.row;
13911 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13912 // so this action feels more ergonomic when paired with other selection operations
13913 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13914 if !should_skip_last {
13915 if action.keep_selections {
13916 if is_multiline_selection {
13917 let line_start = Point::new(selection.end.row, 0);
13918 new_selection_ranges.push(line_start..selection.end);
13919 } else {
13920 new_selection_ranges.push(selection.start..selection.end);
13921 }
13922 } else {
13923 new_selection_ranges.push(selection.end..selection.end);
13924 }
13925 }
13926 }
13927 }
13928 self.change_selections(Default::default(), window, cx, |s| {
13929 s.select_ranges(new_selection_ranges);
13930 });
13931 }
13932
13933 pub fn add_selection_above(
13934 &mut self,
13935 _: &AddSelectionAbove,
13936 window: &mut Window,
13937 cx: &mut Context<Self>,
13938 ) {
13939 self.add_selection(true, window, cx);
13940 }
13941
13942 pub fn add_selection_below(
13943 &mut self,
13944 _: &AddSelectionBelow,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) {
13948 self.add_selection(false, window, cx);
13949 }
13950
13951 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13952 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13953
13954 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13955 let all_selections = self.selections.all::<Point>(cx);
13956 let text_layout_details = self.text_layout_details(window);
13957
13958 let (mut columnar_selections, new_selections_to_columnarize) = {
13959 if let Some(state) = self.add_selections_state.as_ref() {
13960 let columnar_selection_ids: HashSet<_> = state
13961 .groups
13962 .iter()
13963 .flat_map(|group| group.stack.iter())
13964 .copied()
13965 .collect();
13966
13967 all_selections
13968 .into_iter()
13969 .partition(|s| columnar_selection_ids.contains(&s.id))
13970 } else {
13971 (Vec::new(), all_selections)
13972 }
13973 };
13974
13975 let mut state = self
13976 .add_selections_state
13977 .take()
13978 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13979
13980 for selection in new_selections_to_columnarize {
13981 let range = selection.display_range(&display_map).sorted();
13982 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13983 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13984 let positions = start_x.min(end_x)..start_x.max(end_x);
13985 let mut stack = Vec::new();
13986 for row in range.start.row().0..=range.end.row().0 {
13987 if let Some(selection) = self.selections.build_columnar_selection(
13988 &display_map,
13989 DisplayRow(row),
13990 &positions,
13991 selection.reversed,
13992 &text_layout_details,
13993 ) {
13994 stack.push(selection.id);
13995 columnar_selections.push(selection);
13996 }
13997 }
13998 if !stack.is_empty() {
13999 if above {
14000 stack.reverse();
14001 }
14002 state.groups.push(AddSelectionsGroup { above, stack });
14003 }
14004 }
14005
14006 let mut final_selections = Vec::new();
14007 let end_row = if above {
14008 DisplayRow(0)
14009 } else {
14010 display_map.max_point().row()
14011 };
14012
14013 let mut last_added_item_per_group = HashMap::default();
14014 for group in state.groups.iter_mut() {
14015 if let Some(last_id) = group.stack.last() {
14016 last_added_item_per_group.insert(*last_id, group);
14017 }
14018 }
14019
14020 for selection in columnar_selections {
14021 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14022 if above == group.above {
14023 let range = selection.display_range(&display_map).sorted();
14024 debug_assert_eq!(range.start.row(), range.end.row());
14025 let mut row = range.start.row();
14026 let positions =
14027 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14028 px(start)..px(end)
14029 } else {
14030 let start_x =
14031 display_map.x_for_display_point(range.start, &text_layout_details);
14032 let end_x =
14033 display_map.x_for_display_point(range.end, &text_layout_details);
14034 start_x.min(end_x)..start_x.max(end_x)
14035 };
14036
14037 let mut maybe_new_selection = None;
14038 while row != end_row {
14039 if above {
14040 row.0 -= 1;
14041 } else {
14042 row.0 += 1;
14043 }
14044 if let Some(new_selection) = self.selections.build_columnar_selection(
14045 &display_map,
14046 row,
14047 &positions,
14048 selection.reversed,
14049 &text_layout_details,
14050 ) {
14051 maybe_new_selection = Some(new_selection);
14052 break;
14053 }
14054 }
14055
14056 if let Some(new_selection) = maybe_new_selection {
14057 group.stack.push(new_selection.id);
14058 if above {
14059 final_selections.push(new_selection);
14060 final_selections.push(selection);
14061 } else {
14062 final_selections.push(selection);
14063 final_selections.push(new_selection);
14064 }
14065 } else {
14066 final_selections.push(selection);
14067 }
14068 } else {
14069 group.stack.pop();
14070 }
14071 } else {
14072 final_selections.push(selection);
14073 }
14074 }
14075
14076 self.change_selections(Default::default(), window, cx, |s| {
14077 s.select(final_selections);
14078 });
14079
14080 let final_selection_ids: HashSet<_> = self
14081 .selections
14082 .all::<Point>(cx)
14083 .iter()
14084 .map(|s| s.id)
14085 .collect();
14086 state.groups.retain_mut(|group| {
14087 // selections might get merged above so we remove invalid items from stacks
14088 group.stack.retain(|id| final_selection_ids.contains(id));
14089
14090 // single selection in stack can be treated as initial state
14091 group.stack.len() > 1
14092 });
14093
14094 if !state.groups.is_empty() {
14095 self.add_selections_state = Some(state);
14096 }
14097 }
14098
14099 fn select_match_ranges(
14100 &mut self,
14101 range: Range<usize>,
14102 reversed: bool,
14103 replace_newest: bool,
14104 auto_scroll: Option<Autoscroll>,
14105 window: &mut Window,
14106 cx: &mut Context<Editor>,
14107 ) {
14108 self.unfold_ranges(
14109 std::slice::from_ref(&range),
14110 false,
14111 auto_scroll.is_some(),
14112 cx,
14113 );
14114 let effects = if let Some(scroll) = auto_scroll {
14115 SelectionEffects::scroll(scroll)
14116 } else {
14117 SelectionEffects::no_scroll()
14118 };
14119 self.change_selections(effects, window, cx, |s| {
14120 if replace_newest {
14121 s.delete(s.newest_anchor().id);
14122 }
14123 if reversed {
14124 s.insert_range(range.end..range.start);
14125 } else {
14126 s.insert_range(range);
14127 }
14128 });
14129 }
14130
14131 pub fn select_next_match_internal(
14132 &mut self,
14133 display_map: &DisplaySnapshot,
14134 replace_newest: bool,
14135 autoscroll: Option<Autoscroll>,
14136 window: &mut Window,
14137 cx: &mut Context<Self>,
14138 ) -> Result<()> {
14139 let buffer = &display_map.buffer_snapshot;
14140 let mut selections = self.selections.all::<usize>(cx);
14141 if let Some(mut select_next_state) = self.select_next_state.take() {
14142 let query = &select_next_state.query;
14143 if !select_next_state.done {
14144 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14145 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14146 let mut next_selected_range = None;
14147
14148 let bytes_after_last_selection =
14149 buffer.bytes_in_range(last_selection.end..buffer.len());
14150 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14151 let query_matches = query
14152 .stream_find_iter(bytes_after_last_selection)
14153 .map(|result| (last_selection.end, result))
14154 .chain(
14155 query
14156 .stream_find_iter(bytes_before_first_selection)
14157 .map(|result| (0, result)),
14158 );
14159
14160 for (start_offset, query_match) in query_matches {
14161 let query_match = query_match.unwrap(); // can only fail due to I/O
14162 let offset_range =
14163 start_offset + query_match.start()..start_offset + query_match.end();
14164
14165 if !select_next_state.wordwise
14166 || (!buffer.is_inside_word(offset_range.start, false)
14167 && !buffer.is_inside_word(offset_range.end, false))
14168 {
14169 // TODO: This is n^2, because we might check all the selections
14170 if !selections
14171 .iter()
14172 .any(|selection| selection.range().overlaps(&offset_range))
14173 {
14174 next_selected_range = Some(offset_range);
14175 break;
14176 }
14177 }
14178 }
14179
14180 if let Some(next_selected_range) = next_selected_range {
14181 self.select_match_ranges(
14182 next_selected_range,
14183 last_selection.reversed,
14184 replace_newest,
14185 autoscroll,
14186 window,
14187 cx,
14188 );
14189 } else {
14190 select_next_state.done = true;
14191 }
14192 }
14193
14194 self.select_next_state = Some(select_next_state);
14195 } else {
14196 let mut only_carets = true;
14197 let mut same_text_selected = true;
14198 let mut selected_text = None;
14199
14200 let mut selections_iter = selections.iter().peekable();
14201 while let Some(selection) = selections_iter.next() {
14202 if selection.start != selection.end {
14203 only_carets = false;
14204 }
14205
14206 if same_text_selected {
14207 if selected_text.is_none() {
14208 selected_text =
14209 Some(buffer.text_for_range(selection.range()).collect::<String>());
14210 }
14211
14212 if let Some(next_selection) = selections_iter.peek() {
14213 if next_selection.range().len() == selection.range().len() {
14214 let next_selected_text = buffer
14215 .text_for_range(next_selection.range())
14216 .collect::<String>();
14217 if Some(next_selected_text) != selected_text {
14218 same_text_selected = false;
14219 selected_text = None;
14220 }
14221 } else {
14222 same_text_selected = false;
14223 selected_text = None;
14224 }
14225 }
14226 }
14227 }
14228
14229 if only_carets {
14230 for selection in &mut selections {
14231 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14232 selection.start = word_range.start;
14233 selection.end = word_range.end;
14234 selection.goal = SelectionGoal::None;
14235 selection.reversed = false;
14236 self.select_match_ranges(
14237 selection.start..selection.end,
14238 selection.reversed,
14239 replace_newest,
14240 autoscroll,
14241 window,
14242 cx,
14243 );
14244 }
14245
14246 if selections.len() == 1 {
14247 let selection = selections
14248 .last()
14249 .expect("ensured that there's only one selection");
14250 let query = buffer
14251 .text_for_range(selection.start..selection.end)
14252 .collect::<String>();
14253 let is_empty = query.is_empty();
14254 let select_state = SelectNextState {
14255 query: AhoCorasick::new(&[query])?,
14256 wordwise: true,
14257 done: is_empty,
14258 };
14259 self.select_next_state = Some(select_state);
14260 } else {
14261 self.select_next_state = None;
14262 }
14263 } else if let Some(selected_text) = selected_text {
14264 self.select_next_state = Some(SelectNextState {
14265 query: AhoCorasick::new(&[selected_text])?,
14266 wordwise: false,
14267 done: false,
14268 });
14269 self.select_next_match_internal(
14270 display_map,
14271 replace_newest,
14272 autoscroll,
14273 window,
14274 cx,
14275 )?;
14276 }
14277 }
14278 Ok(())
14279 }
14280
14281 pub fn select_all_matches(
14282 &mut self,
14283 _action: &SelectAllMatches,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) -> Result<()> {
14287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14288
14289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14290
14291 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14292 let Some(select_next_state) = self.select_next_state.as_mut() else {
14293 return Ok(());
14294 };
14295 if select_next_state.done {
14296 return Ok(());
14297 }
14298
14299 let mut new_selections = Vec::new();
14300
14301 let reversed = self.selections.oldest::<usize>(cx).reversed;
14302 let buffer = &display_map.buffer_snapshot;
14303 let query_matches = select_next_state
14304 .query
14305 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14306
14307 for query_match in query_matches.into_iter() {
14308 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14309 let offset_range = if reversed {
14310 query_match.end()..query_match.start()
14311 } else {
14312 query_match.start()..query_match.end()
14313 };
14314
14315 if !select_next_state.wordwise
14316 || (!buffer.is_inside_word(offset_range.start, false)
14317 && !buffer.is_inside_word(offset_range.end, false))
14318 {
14319 new_selections.push(offset_range.start..offset_range.end);
14320 }
14321 }
14322
14323 select_next_state.done = true;
14324
14325 if new_selections.is_empty() {
14326 log::error!("bug: new_selections is empty in select_all_matches");
14327 return Ok(());
14328 }
14329
14330 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14331 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14332 selections.select_ranges(new_selections)
14333 });
14334
14335 Ok(())
14336 }
14337
14338 pub fn select_next(
14339 &mut self,
14340 action: &SelectNext,
14341 window: &mut Window,
14342 cx: &mut Context<Self>,
14343 ) -> Result<()> {
14344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14346 self.select_next_match_internal(
14347 &display_map,
14348 action.replace_newest,
14349 Some(Autoscroll::newest()),
14350 window,
14351 cx,
14352 )?;
14353 Ok(())
14354 }
14355
14356 pub fn select_previous(
14357 &mut self,
14358 action: &SelectPrevious,
14359 window: &mut Window,
14360 cx: &mut Context<Self>,
14361 ) -> Result<()> {
14362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14363 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14364 let buffer = &display_map.buffer_snapshot;
14365 let mut selections = self.selections.all::<usize>(cx);
14366 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14367 let query = &select_prev_state.query;
14368 if !select_prev_state.done {
14369 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14370 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14371 let mut next_selected_range = None;
14372 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14373 let bytes_before_last_selection =
14374 buffer.reversed_bytes_in_range(0..last_selection.start);
14375 let bytes_after_first_selection =
14376 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14377 let query_matches = query
14378 .stream_find_iter(bytes_before_last_selection)
14379 .map(|result| (last_selection.start, result))
14380 .chain(
14381 query
14382 .stream_find_iter(bytes_after_first_selection)
14383 .map(|result| (buffer.len(), result)),
14384 );
14385 for (end_offset, query_match) in query_matches {
14386 let query_match = query_match.unwrap(); // can only fail due to I/O
14387 let offset_range =
14388 end_offset - query_match.end()..end_offset - query_match.start();
14389
14390 if !select_prev_state.wordwise
14391 || (!buffer.is_inside_word(offset_range.start, false)
14392 && !buffer.is_inside_word(offset_range.end, false))
14393 {
14394 next_selected_range = Some(offset_range);
14395 break;
14396 }
14397 }
14398
14399 if let Some(next_selected_range) = next_selected_range {
14400 self.select_match_ranges(
14401 next_selected_range,
14402 last_selection.reversed,
14403 action.replace_newest,
14404 Some(Autoscroll::newest()),
14405 window,
14406 cx,
14407 );
14408 } else {
14409 select_prev_state.done = true;
14410 }
14411 }
14412
14413 self.select_prev_state = Some(select_prev_state);
14414 } else {
14415 let mut only_carets = true;
14416 let mut same_text_selected = true;
14417 let mut selected_text = None;
14418
14419 let mut selections_iter = selections.iter().peekable();
14420 while let Some(selection) = selections_iter.next() {
14421 if selection.start != selection.end {
14422 only_carets = false;
14423 }
14424
14425 if same_text_selected {
14426 if selected_text.is_none() {
14427 selected_text =
14428 Some(buffer.text_for_range(selection.range()).collect::<String>());
14429 }
14430
14431 if let Some(next_selection) = selections_iter.peek() {
14432 if next_selection.range().len() == selection.range().len() {
14433 let next_selected_text = buffer
14434 .text_for_range(next_selection.range())
14435 .collect::<String>();
14436 if Some(next_selected_text) != selected_text {
14437 same_text_selected = false;
14438 selected_text = None;
14439 }
14440 } else {
14441 same_text_selected = false;
14442 selected_text = None;
14443 }
14444 }
14445 }
14446 }
14447
14448 if only_carets {
14449 for selection in &mut selections {
14450 let (word_range, _) = buffer.surrounding_word(selection.start, false);
14451 selection.start = word_range.start;
14452 selection.end = word_range.end;
14453 selection.goal = SelectionGoal::None;
14454 selection.reversed = false;
14455 self.select_match_ranges(
14456 selection.start..selection.end,
14457 selection.reversed,
14458 action.replace_newest,
14459 Some(Autoscroll::newest()),
14460 window,
14461 cx,
14462 );
14463 }
14464 if selections.len() == 1 {
14465 let selection = selections
14466 .last()
14467 .expect("ensured that there's only one selection");
14468 let query = buffer
14469 .text_for_range(selection.start..selection.end)
14470 .collect::<String>();
14471 let is_empty = query.is_empty();
14472 let select_state = SelectNextState {
14473 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14474 wordwise: true,
14475 done: is_empty,
14476 };
14477 self.select_prev_state = Some(select_state);
14478 } else {
14479 self.select_prev_state = None;
14480 }
14481 } else if let Some(selected_text) = selected_text {
14482 self.select_prev_state = Some(SelectNextState {
14483 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14484 wordwise: false,
14485 done: false,
14486 });
14487 self.select_previous(action, window, cx)?;
14488 }
14489 }
14490 Ok(())
14491 }
14492
14493 pub fn find_next_match(
14494 &mut self,
14495 _: &FindNextMatch,
14496 window: &mut Window,
14497 cx: &mut Context<Self>,
14498 ) -> Result<()> {
14499 let selections = self.selections.disjoint_anchors();
14500 match selections.first() {
14501 Some(first) if selections.len() >= 2 => {
14502 self.change_selections(Default::default(), window, cx, |s| {
14503 s.select_ranges([first.range()]);
14504 });
14505 }
14506 _ => self.select_next(
14507 &SelectNext {
14508 replace_newest: true,
14509 },
14510 window,
14511 cx,
14512 )?,
14513 }
14514 Ok(())
14515 }
14516
14517 pub fn find_previous_match(
14518 &mut self,
14519 _: &FindPreviousMatch,
14520 window: &mut Window,
14521 cx: &mut Context<Self>,
14522 ) -> Result<()> {
14523 let selections = self.selections.disjoint_anchors();
14524 match selections.last() {
14525 Some(last) if selections.len() >= 2 => {
14526 self.change_selections(Default::default(), window, cx, |s| {
14527 s.select_ranges([last.range()]);
14528 });
14529 }
14530 _ => self.select_previous(
14531 &SelectPrevious {
14532 replace_newest: true,
14533 },
14534 window,
14535 cx,
14536 )?,
14537 }
14538 Ok(())
14539 }
14540
14541 pub fn toggle_comments(
14542 &mut self,
14543 action: &ToggleComments,
14544 window: &mut Window,
14545 cx: &mut Context<Self>,
14546 ) {
14547 if self.read_only(cx) {
14548 return;
14549 }
14550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14551 let text_layout_details = &self.text_layout_details(window);
14552 self.transact(window, cx, |this, window, cx| {
14553 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14554 let mut edits = Vec::new();
14555 let mut selection_edit_ranges = Vec::new();
14556 let mut last_toggled_row = None;
14557 let snapshot = this.buffer.read(cx).read(cx);
14558 let empty_str: Arc<str> = Arc::default();
14559 let mut suffixes_inserted = Vec::new();
14560 let ignore_indent = action.ignore_indent;
14561
14562 fn comment_prefix_range(
14563 snapshot: &MultiBufferSnapshot,
14564 row: MultiBufferRow,
14565 comment_prefix: &str,
14566 comment_prefix_whitespace: &str,
14567 ignore_indent: bool,
14568 ) -> Range<Point> {
14569 let indent_size = if ignore_indent {
14570 0
14571 } else {
14572 snapshot.indent_size_for_line(row).len
14573 };
14574
14575 let start = Point::new(row.0, indent_size);
14576
14577 let mut line_bytes = snapshot
14578 .bytes_in_range(start..snapshot.max_point())
14579 .flatten()
14580 .copied();
14581
14582 // If this line currently begins with the line comment prefix, then record
14583 // the range containing the prefix.
14584 if line_bytes
14585 .by_ref()
14586 .take(comment_prefix.len())
14587 .eq(comment_prefix.bytes())
14588 {
14589 // Include any whitespace that matches the comment prefix.
14590 let matching_whitespace_len = line_bytes
14591 .zip(comment_prefix_whitespace.bytes())
14592 .take_while(|(a, b)| a == b)
14593 .count() as u32;
14594 let end = Point::new(
14595 start.row,
14596 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14597 );
14598 start..end
14599 } else {
14600 start..start
14601 }
14602 }
14603
14604 fn comment_suffix_range(
14605 snapshot: &MultiBufferSnapshot,
14606 row: MultiBufferRow,
14607 comment_suffix: &str,
14608 comment_suffix_has_leading_space: bool,
14609 ) -> Range<Point> {
14610 let end = Point::new(row.0, snapshot.line_len(row));
14611 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14612
14613 let mut line_end_bytes = snapshot
14614 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14615 .flatten()
14616 .copied();
14617
14618 let leading_space_len = if suffix_start_column > 0
14619 && line_end_bytes.next() == Some(b' ')
14620 && comment_suffix_has_leading_space
14621 {
14622 1
14623 } else {
14624 0
14625 };
14626
14627 // If this line currently begins with the line comment prefix, then record
14628 // the range containing the prefix.
14629 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14630 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14631 start..end
14632 } else {
14633 end..end
14634 }
14635 }
14636
14637 // TODO: Handle selections that cross excerpts
14638 for selection in &mut selections {
14639 let start_column = snapshot
14640 .indent_size_for_line(MultiBufferRow(selection.start.row))
14641 .len;
14642 let language = if let Some(language) =
14643 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14644 {
14645 language
14646 } else {
14647 continue;
14648 };
14649
14650 selection_edit_ranges.clear();
14651
14652 // If multiple selections contain a given row, avoid processing that
14653 // row more than once.
14654 let mut start_row = MultiBufferRow(selection.start.row);
14655 if last_toggled_row == Some(start_row) {
14656 start_row = start_row.next_row();
14657 }
14658 let end_row =
14659 if selection.end.row > selection.start.row && selection.end.column == 0 {
14660 MultiBufferRow(selection.end.row - 1)
14661 } else {
14662 MultiBufferRow(selection.end.row)
14663 };
14664 last_toggled_row = Some(end_row);
14665
14666 if start_row > end_row {
14667 continue;
14668 }
14669
14670 // If the language has line comments, toggle those.
14671 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14672
14673 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14674 if ignore_indent {
14675 full_comment_prefixes = full_comment_prefixes
14676 .into_iter()
14677 .map(|s| Arc::from(s.trim_end()))
14678 .collect();
14679 }
14680
14681 if !full_comment_prefixes.is_empty() {
14682 let first_prefix = full_comment_prefixes
14683 .first()
14684 .expect("prefixes is non-empty");
14685 let prefix_trimmed_lengths = full_comment_prefixes
14686 .iter()
14687 .map(|p| p.trim_end_matches(' ').len())
14688 .collect::<SmallVec<[usize; 4]>>();
14689
14690 let mut all_selection_lines_are_comments = true;
14691
14692 for row in start_row.0..=end_row.0 {
14693 let row = MultiBufferRow(row);
14694 if start_row < end_row && snapshot.is_line_blank(row) {
14695 continue;
14696 }
14697
14698 let prefix_range = full_comment_prefixes
14699 .iter()
14700 .zip(prefix_trimmed_lengths.iter().copied())
14701 .map(|(prefix, trimmed_prefix_len)| {
14702 comment_prefix_range(
14703 snapshot.deref(),
14704 row,
14705 &prefix[..trimmed_prefix_len],
14706 &prefix[trimmed_prefix_len..],
14707 ignore_indent,
14708 )
14709 })
14710 .max_by_key(|range| range.end.column - range.start.column)
14711 .expect("prefixes is non-empty");
14712
14713 if prefix_range.is_empty() {
14714 all_selection_lines_are_comments = false;
14715 }
14716
14717 selection_edit_ranges.push(prefix_range);
14718 }
14719
14720 if all_selection_lines_are_comments {
14721 edits.extend(
14722 selection_edit_ranges
14723 .iter()
14724 .cloned()
14725 .map(|range| (range, empty_str.clone())),
14726 );
14727 } else {
14728 let min_column = selection_edit_ranges
14729 .iter()
14730 .map(|range| range.start.column)
14731 .min()
14732 .unwrap_or(0);
14733 edits.extend(selection_edit_ranges.iter().map(|range| {
14734 let position = Point::new(range.start.row, min_column);
14735 (position..position, first_prefix.clone())
14736 }));
14737 }
14738 } else if let Some(BlockCommentConfig {
14739 start: full_comment_prefix,
14740 end: comment_suffix,
14741 ..
14742 }) = language.block_comment()
14743 {
14744 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14745 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14746 let prefix_range = comment_prefix_range(
14747 snapshot.deref(),
14748 start_row,
14749 comment_prefix,
14750 comment_prefix_whitespace,
14751 ignore_indent,
14752 );
14753 let suffix_range = comment_suffix_range(
14754 snapshot.deref(),
14755 end_row,
14756 comment_suffix.trim_start_matches(' '),
14757 comment_suffix.starts_with(' '),
14758 );
14759
14760 if prefix_range.is_empty() || suffix_range.is_empty() {
14761 edits.push((
14762 prefix_range.start..prefix_range.start,
14763 full_comment_prefix.clone(),
14764 ));
14765 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14766 suffixes_inserted.push((end_row, comment_suffix.len()));
14767 } else {
14768 edits.push((prefix_range, empty_str.clone()));
14769 edits.push((suffix_range, empty_str.clone()));
14770 }
14771 } else {
14772 continue;
14773 }
14774 }
14775
14776 drop(snapshot);
14777 this.buffer.update(cx, |buffer, cx| {
14778 buffer.edit(edits, None, cx);
14779 });
14780
14781 // Adjust selections so that they end before any comment suffixes that
14782 // were inserted.
14783 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14784 let mut selections = this.selections.all::<Point>(cx);
14785 let snapshot = this.buffer.read(cx).read(cx);
14786 for selection in &mut selections {
14787 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14788 match row.cmp(&MultiBufferRow(selection.end.row)) {
14789 Ordering::Less => {
14790 suffixes_inserted.next();
14791 continue;
14792 }
14793 Ordering::Greater => break,
14794 Ordering::Equal => {
14795 if selection.end.column == snapshot.line_len(row) {
14796 if selection.is_empty() {
14797 selection.start.column -= suffix_len as u32;
14798 }
14799 selection.end.column -= suffix_len as u32;
14800 }
14801 break;
14802 }
14803 }
14804 }
14805 }
14806
14807 drop(snapshot);
14808 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14809
14810 let selections = this.selections.all::<Point>(cx);
14811 let selections_on_single_row = selections.windows(2).all(|selections| {
14812 selections[0].start.row == selections[1].start.row
14813 && selections[0].end.row == selections[1].end.row
14814 && selections[0].start.row == selections[0].end.row
14815 });
14816 let selections_selecting = selections
14817 .iter()
14818 .any(|selection| selection.start != selection.end);
14819 let advance_downwards = action.advance_downwards
14820 && selections_on_single_row
14821 && !selections_selecting
14822 && !matches!(this.mode, EditorMode::SingleLine);
14823
14824 if advance_downwards {
14825 let snapshot = this.buffer.read(cx).snapshot(cx);
14826
14827 this.change_selections(Default::default(), window, cx, |s| {
14828 s.move_cursors_with(|display_snapshot, display_point, _| {
14829 let mut point = display_point.to_point(display_snapshot);
14830 point.row += 1;
14831 point = snapshot.clip_point(point, Bias::Left);
14832 let display_point = point.to_display_point(display_snapshot);
14833 let goal = SelectionGoal::HorizontalPosition(
14834 display_snapshot
14835 .x_for_display_point(display_point, text_layout_details)
14836 .into(),
14837 );
14838 (display_point, goal)
14839 })
14840 });
14841 }
14842 });
14843 }
14844
14845 pub fn select_enclosing_symbol(
14846 &mut self,
14847 _: &SelectEnclosingSymbol,
14848 window: &mut Window,
14849 cx: &mut Context<Self>,
14850 ) {
14851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14852
14853 let buffer = self.buffer.read(cx).snapshot(cx);
14854 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14855
14856 fn update_selection(
14857 selection: &Selection<usize>,
14858 buffer_snap: &MultiBufferSnapshot,
14859 ) -> Option<Selection<usize>> {
14860 let cursor = selection.head();
14861 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14862 for symbol in symbols.iter().rev() {
14863 let start = symbol.range.start.to_offset(buffer_snap);
14864 let end = symbol.range.end.to_offset(buffer_snap);
14865 let new_range = start..end;
14866 if start < selection.start || end > selection.end {
14867 return Some(Selection {
14868 id: selection.id,
14869 start: new_range.start,
14870 end: new_range.end,
14871 goal: SelectionGoal::None,
14872 reversed: selection.reversed,
14873 });
14874 }
14875 }
14876 None
14877 }
14878
14879 let mut selected_larger_symbol = false;
14880 let new_selections = old_selections
14881 .iter()
14882 .map(|selection| match update_selection(selection, &buffer) {
14883 Some(new_selection) => {
14884 if new_selection.range() != selection.range() {
14885 selected_larger_symbol = true;
14886 }
14887 new_selection
14888 }
14889 None => selection.clone(),
14890 })
14891 .collect::<Vec<_>>();
14892
14893 if selected_larger_symbol {
14894 self.change_selections(Default::default(), window, cx, |s| {
14895 s.select(new_selections);
14896 });
14897 }
14898 }
14899
14900 pub fn select_larger_syntax_node(
14901 &mut self,
14902 _: &SelectLargerSyntaxNode,
14903 window: &mut Window,
14904 cx: &mut Context<Self>,
14905 ) {
14906 let Some(visible_row_count) = self.visible_row_count() else {
14907 return;
14908 };
14909 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14910 if old_selections.is_empty() {
14911 return;
14912 }
14913
14914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14915
14916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14917 let buffer = self.buffer.read(cx).snapshot(cx);
14918
14919 let mut selected_larger_node = false;
14920 let mut new_selections = old_selections
14921 .iter()
14922 .map(|selection| {
14923 let old_range = selection.start..selection.end;
14924
14925 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14926 // manually select word at selection
14927 if ["string_content", "inline"].contains(&node.kind()) {
14928 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14929 // ignore if word is already selected
14930 if !word_range.is_empty() && old_range != word_range {
14931 let (last_word_range, _) =
14932 buffer.surrounding_word(old_range.end, false);
14933 // only select word if start and end point belongs to same word
14934 if word_range == last_word_range {
14935 selected_larger_node = true;
14936 return Selection {
14937 id: selection.id,
14938 start: word_range.start,
14939 end: word_range.end,
14940 goal: SelectionGoal::None,
14941 reversed: selection.reversed,
14942 };
14943 }
14944 }
14945 }
14946 }
14947
14948 let mut new_range = old_range.clone();
14949 while let Some((_node, containing_range)) =
14950 buffer.syntax_ancestor(new_range.clone())
14951 {
14952 new_range = match containing_range {
14953 MultiOrSingleBufferOffsetRange::Single(_) => break,
14954 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14955 };
14956 if !display_map.intersects_fold(new_range.start)
14957 && !display_map.intersects_fold(new_range.end)
14958 {
14959 break;
14960 }
14961 }
14962
14963 selected_larger_node |= new_range != old_range;
14964 Selection {
14965 id: selection.id,
14966 start: new_range.start,
14967 end: new_range.end,
14968 goal: SelectionGoal::None,
14969 reversed: selection.reversed,
14970 }
14971 })
14972 .collect::<Vec<_>>();
14973
14974 if !selected_larger_node {
14975 return; // don't put this call in the history
14976 }
14977
14978 // scroll based on transformation done to the last selection created by the user
14979 let (last_old, last_new) = old_selections
14980 .last()
14981 .zip(new_selections.last().cloned())
14982 .expect("old_selections isn't empty");
14983
14984 // revert selection
14985 let is_selection_reversed = {
14986 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14987 new_selections.last_mut().expect("checked above").reversed =
14988 should_newest_selection_be_reversed;
14989 should_newest_selection_be_reversed
14990 };
14991
14992 if selected_larger_node {
14993 self.select_syntax_node_history.disable_clearing = true;
14994 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14995 s.select(new_selections.clone());
14996 });
14997 self.select_syntax_node_history.disable_clearing = false;
14998 }
14999
15000 let start_row = last_new.start.to_display_point(&display_map).row().0;
15001 let end_row = last_new.end.to_display_point(&display_map).row().0;
15002 let selection_height = end_row - start_row + 1;
15003 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15004
15005 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15006 let scroll_behavior = if fits_on_the_screen {
15007 self.request_autoscroll(Autoscroll::fit(), cx);
15008 SelectSyntaxNodeScrollBehavior::FitSelection
15009 } else if is_selection_reversed {
15010 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15011 SelectSyntaxNodeScrollBehavior::CursorTop
15012 } else {
15013 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15014 SelectSyntaxNodeScrollBehavior::CursorBottom
15015 };
15016
15017 self.select_syntax_node_history.push((
15018 old_selections,
15019 scroll_behavior,
15020 is_selection_reversed,
15021 ));
15022 }
15023
15024 pub fn select_smaller_syntax_node(
15025 &mut self,
15026 _: &SelectSmallerSyntaxNode,
15027 window: &mut Window,
15028 cx: &mut Context<Self>,
15029 ) {
15030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15031
15032 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15033 self.select_syntax_node_history.pop()
15034 {
15035 if let Some(selection) = selections.last_mut() {
15036 selection.reversed = is_selection_reversed;
15037 }
15038
15039 self.select_syntax_node_history.disable_clearing = true;
15040 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15041 s.select(selections.to_vec());
15042 });
15043 self.select_syntax_node_history.disable_clearing = false;
15044
15045 match scroll_behavior {
15046 SelectSyntaxNodeScrollBehavior::CursorTop => {
15047 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15048 }
15049 SelectSyntaxNodeScrollBehavior::FitSelection => {
15050 self.request_autoscroll(Autoscroll::fit(), cx);
15051 }
15052 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15053 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15054 }
15055 }
15056 }
15057 }
15058
15059 pub fn unwrap_syntax_node(
15060 &mut self,
15061 _: &UnwrapSyntaxNode,
15062 window: &mut Window,
15063 cx: &mut Context<Self>,
15064 ) {
15065 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15066
15067 let buffer = self.buffer.read(cx).snapshot(cx);
15068 let selections = self
15069 .selections
15070 .all::<usize>(cx)
15071 .into_iter()
15072 // subtracting the offset requires sorting
15073 .sorted_by_key(|i| i.start);
15074
15075 let full_edits = selections
15076 .into_iter()
15077 .filter_map(|selection| {
15078 // Only requires two branches once if-let-chains stabilize (#53667)
15079 let child = if !selection.is_empty() {
15080 selection.range()
15081 } else if let Some((_, ancestor_range)) =
15082 buffer.syntax_ancestor(selection.start..selection.end)
15083 {
15084 match ancestor_range {
15085 MultiOrSingleBufferOffsetRange::Single(range) => range,
15086 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15087 }
15088 } else {
15089 selection.range()
15090 };
15091
15092 let mut parent = child.clone();
15093 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15094 parent = match ancestor_range {
15095 MultiOrSingleBufferOffsetRange::Single(range) => range,
15096 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15097 };
15098 if parent.start < child.start || parent.end > child.end {
15099 break;
15100 }
15101 }
15102
15103 if parent == child {
15104 return None;
15105 }
15106 let text = buffer.text_for_range(child).collect::<String>();
15107 Some((selection.id, parent, text))
15108 })
15109 .collect::<Vec<_>>();
15110
15111 self.transact(window, cx, |this, window, cx| {
15112 this.buffer.update(cx, |buffer, cx| {
15113 buffer.edit(
15114 full_edits
15115 .iter()
15116 .map(|(_, p, t)| (p.clone(), t.clone()))
15117 .collect::<Vec<_>>(),
15118 None,
15119 cx,
15120 );
15121 });
15122 this.change_selections(Default::default(), window, cx, |s| {
15123 let mut offset = 0;
15124 let mut selections = vec![];
15125 for (id, parent, text) in full_edits {
15126 let start = parent.start - offset;
15127 offset += parent.len() - text.len();
15128 selections.push(Selection {
15129 id,
15130 start,
15131 end: start + text.len(),
15132 reversed: false,
15133 goal: Default::default(),
15134 });
15135 }
15136 s.select(selections);
15137 });
15138 });
15139 }
15140
15141 pub fn select_next_syntax_node(
15142 &mut self,
15143 _: &SelectNextSyntaxNode,
15144 window: &mut Window,
15145 cx: &mut Context<Self>,
15146 ) {
15147 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15148 if old_selections.is_empty() {
15149 return;
15150 }
15151
15152 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15153
15154 let buffer = self.buffer.read(cx).snapshot(cx);
15155 let mut selected_sibling = false;
15156
15157 let new_selections = old_selections
15158 .iter()
15159 .map(|selection| {
15160 let old_range = selection.start..selection.end;
15161
15162 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15163 let new_range = node.byte_range();
15164 selected_sibling = true;
15165 Selection {
15166 id: selection.id,
15167 start: new_range.start,
15168 end: new_range.end,
15169 goal: SelectionGoal::None,
15170 reversed: selection.reversed,
15171 }
15172 } else {
15173 selection.clone()
15174 }
15175 })
15176 .collect::<Vec<_>>();
15177
15178 if selected_sibling {
15179 self.change_selections(
15180 SelectionEffects::scroll(Autoscroll::fit()),
15181 window,
15182 cx,
15183 |s| {
15184 s.select(new_selections);
15185 },
15186 );
15187 }
15188 }
15189
15190 pub fn select_prev_syntax_node(
15191 &mut self,
15192 _: &SelectPreviousSyntaxNode,
15193 window: &mut Window,
15194 cx: &mut Context<Self>,
15195 ) {
15196 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15197 if old_selections.is_empty() {
15198 return;
15199 }
15200
15201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15202
15203 let buffer = self.buffer.read(cx).snapshot(cx);
15204 let mut selected_sibling = false;
15205
15206 let new_selections = old_selections
15207 .iter()
15208 .map(|selection| {
15209 let old_range = selection.start..selection.end;
15210
15211 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15212 let new_range = node.byte_range();
15213 selected_sibling = true;
15214 Selection {
15215 id: selection.id,
15216 start: new_range.start,
15217 end: new_range.end,
15218 goal: SelectionGoal::None,
15219 reversed: selection.reversed,
15220 }
15221 } else {
15222 selection.clone()
15223 }
15224 })
15225 .collect::<Vec<_>>();
15226
15227 if selected_sibling {
15228 self.change_selections(
15229 SelectionEffects::scroll(Autoscroll::fit()),
15230 window,
15231 cx,
15232 |s| {
15233 s.select(new_selections);
15234 },
15235 );
15236 }
15237 }
15238
15239 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15240 if !EditorSettings::get_global(cx).gutter.runnables {
15241 self.clear_tasks();
15242 return Task::ready(());
15243 }
15244 let project = self.project().map(Entity::downgrade);
15245 let task_sources = self.lsp_task_sources(cx);
15246 let multi_buffer = self.buffer.downgrade();
15247 cx.spawn_in(window, async move |editor, cx| {
15248 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15249 let Some(project) = project.and_then(|p| p.upgrade()) else {
15250 return;
15251 };
15252 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15253 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15254 }) else {
15255 return;
15256 };
15257
15258 let hide_runnables = project
15259 .update(cx, |project, _| project.is_via_collab())
15260 .unwrap_or(true);
15261 if hide_runnables {
15262 return;
15263 }
15264 let new_rows =
15265 cx.background_spawn({
15266 let snapshot = display_snapshot.clone();
15267 async move {
15268 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15269 }
15270 })
15271 .await;
15272 let Ok(lsp_tasks) =
15273 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15274 else {
15275 return;
15276 };
15277 let lsp_tasks = lsp_tasks.await;
15278
15279 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15280 lsp_tasks
15281 .into_iter()
15282 .flat_map(|(kind, tasks)| {
15283 tasks.into_iter().filter_map(move |(location, task)| {
15284 Some((kind.clone(), location?, task))
15285 })
15286 })
15287 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15288 let buffer = location.target.buffer;
15289 let buffer_snapshot = buffer.read(cx).snapshot();
15290 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15291 |(excerpt_id, snapshot, _)| {
15292 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15293 display_snapshot
15294 .buffer_snapshot
15295 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15296 } else {
15297 None
15298 }
15299 },
15300 );
15301 if let Some(offset) = offset {
15302 let task_buffer_range =
15303 location.target.range.to_point(&buffer_snapshot);
15304 let context_buffer_range =
15305 task_buffer_range.to_offset(&buffer_snapshot);
15306 let context_range = BufferOffset(context_buffer_range.start)
15307 ..BufferOffset(context_buffer_range.end);
15308
15309 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15310 .or_insert_with(|| RunnableTasks {
15311 templates: Vec::new(),
15312 offset,
15313 column: task_buffer_range.start.column,
15314 extra_variables: HashMap::default(),
15315 context_range,
15316 })
15317 .templates
15318 .push((kind, task.original_task().clone()));
15319 }
15320
15321 acc
15322 })
15323 }) else {
15324 return;
15325 };
15326
15327 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15328 buffer.language_settings(cx).tasks.prefer_lsp
15329 }) else {
15330 return;
15331 };
15332
15333 let rows = Self::runnable_rows(
15334 project,
15335 display_snapshot,
15336 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15337 new_rows,
15338 cx.clone(),
15339 )
15340 .await;
15341 editor
15342 .update(cx, |editor, _| {
15343 editor.clear_tasks();
15344 for (key, mut value) in rows {
15345 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15346 value.templates.extend(lsp_tasks.templates);
15347 }
15348
15349 editor.insert_tasks(key, value);
15350 }
15351 for (key, value) in lsp_tasks_by_rows {
15352 editor.insert_tasks(key, value);
15353 }
15354 })
15355 .ok();
15356 })
15357 }
15358 fn fetch_runnable_ranges(
15359 snapshot: &DisplaySnapshot,
15360 range: Range<Anchor>,
15361 ) -> Vec<language::RunnableRange> {
15362 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15363 }
15364
15365 fn runnable_rows(
15366 project: Entity<Project>,
15367 snapshot: DisplaySnapshot,
15368 prefer_lsp: bool,
15369 runnable_ranges: Vec<RunnableRange>,
15370 cx: AsyncWindowContext,
15371 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15372 cx.spawn(async move |cx| {
15373 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15374 for mut runnable in runnable_ranges {
15375 let Some(tasks) = cx
15376 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15377 .ok()
15378 else {
15379 continue;
15380 };
15381 let mut tasks = tasks.await;
15382
15383 if prefer_lsp {
15384 tasks.retain(|(task_kind, _)| {
15385 !matches!(task_kind, TaskSourceKind::Language { .. })
15386 });
15387 }
15388 if tasks.is_empty() {
15389 continue;
15390 }
15391
15392 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15393 let Some(row) = snapshot
15394 .buffer_snapshot
15395 .buffer_line_for_row(MultiBufferRow(point.row))
15396 .map(|(_, range)| range.start.row)
15397 else {
15398 continue;
15399 };
15400
15401 let context_range =
15402 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15403 runnable_rows.push((
15404 (runnable.buffer_id, row),
15405 RunnableTasks {
15406 templates: tasks,
15407 offset: snapshot
15408 .buffer_snapshot
15409 .anchor_before(runnable.run_range.start),
15410 context_range,
15411 column: point.column,
15412 extra_variables: runnable.extra_captures,
15413 },
15414 ));
15415 }
15416 runnable_rows
15417 })
15418 }
15419
15420 fn templates_with_tags(
15421 project: &Entity<Project>,
15422 runnable: &mut Runnable,
15423 cx: &mut App,
15424 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15425 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15426 let (worktree_id, file) = project
15427 .buffer_for_id(runnable.buffer, cx)
15428 .and_then(|buffer| buffer.read(cx).file())
15429 .map(|file| (file.worktree_id(cx), file.clone()))
15430 .unzip();
15431
15432 (
15433 project.task_store().read(cx).task_inventory().cloned(),
15434 worktree_id,
15435 file,
15436 )
15437 });
15438
15439 let tags = mem::take(&mut runnable.tags);
15440 let language = runnable.language.clone();
15441 cx.spawn(async move |cx| {
15442 let mut templates_with_tags = Vec::new();
15443 if let Some(inventory) = inventory {
15444 for RunnableTag(tag) in tags {
15445 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15446 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15447 }) else {
15448 return templates_with_tags;
15449 };
15450 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15451 move |(_, template)| {
15452 template.tags.iter().any(|source_tag| source_tag == &tag)
15453 },
15454 ));
15455 }
15456 }
15457 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15458
15459 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15460 // Strongest source wins; if we have worktree tag binding, prefer that to
15461 // global and language bindings;
15462 // if we have a global binding, prefer that to language binding.
15463 let first_mismatch = templates_with_tags
15464 .iter()
15465 .position(|(tag_source, _)| tag_source != leading_tag_source);
15466 if let Some(index) = first_mismatch {
15467 templates_with_tags.truncate(index);
15468 }
15469 }
15470
15471 templates_with_tags
15472 })
15473 }
15474
15475 pub fn move_to_enclosing_bracket(
15476 &mut self,
15477 _: &MoveToEnclosingBracket,
15478 window: &mut Window,
15479 cx: &mut Context<Self>,
15480 ) {
15481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15482 self.change_selections(Default::default(), window, cx, |s| {
15483 s.move_offsets_with(|snapshot, selection| {
15484 let Some(enclosing_bracket_ranges) =
15485 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15486 else {
15487 return;
15488 };
15489
15490 let mut best_length = usize::MAX;
15491 let mut best_inside = false;
15492 let mut best_in_bracket_range = false;
15493 let mut best_destination = None;
15494 for (open, close) in enclosing_bracket_ranges {
15495 let close = close.to_inclusive();
15496 let length = close.end() - open.start;
15497 let inside = selection.start >= open.end && selection.end <= *close.start();
15498 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15499 || close.contains(&selection.head());
15500
15501 // If best is next to a bracket and current isn't, skip
15502 if !in_bracket_range && best_in_bracket_range {
15503 continue;
15504 }
15505
15506 // Prefer smaller lengths unless best is inside and current isn't
15507 if length > best_length && (best_inside || !inside) {
15508 continue;
15509 }
15510
15511 best_length = length;
15512 best_inside = inside;
15513 best_in_bracket_range = in_bracket_range;
15514 best_destination = Some(
15515 if close.contains(&selection.start) && close.contains(&selection.end) {
15516 if inside { open.end } else { open.start }
15517 } else if inside {
15518 *close.start()
15519 } else {
15520 *close.end()
15521 },
15522 );
15523 }
15524
15525 if let Some(destination) = best_destination {
15526 selection.collapse_to(destination, SelectionGoal::None);
15527 }
15528 })
15529 });
15530 }
15531
15532 pub fn undo_selection(
15533 &mut self,
15534 _: &UndoSelection,
15535 window: &mut Window,
15536 cx: &mut Context<Self>,
15537 ) {
15538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15539 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15540 self.selection_history.mode = SelectionHistoryMode::Undoing;
15541 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15542 this.end_selection(window, cx);
15543 this.change_selections(
15544 SelectionEffects::scroll(Autoscroll::newest()),
15545 window,
15546 cx,
15547 |s| s.select_anchors(entry.selections.to_vec()),
15548 );
15549 });
15550 self.selection_history.mode = SelectionHistoryMode::Normal;
15551
15552 self.select_next_state = entry.select_next_state;
15553 self.select_prev_state = entry.select_prev_state;
15554 self.add_selections_state = entry.add_selections_state;
15555 }
15556 }
15557
15558 pub fn redo_selection(
15559 &mut self,
15560 _: &RedoSelection,
15561 window: &mut Window,
15562 cx: &mut Context<Self>,
15563 ) {
15564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15565 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15566 self.selection_history.mode = SelectionHistoryMode::Redoing;
15567 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15568 this.end_selection(window, cx);
15569 this.change_selections(
15570 SelectionEffects::scroll(Autoscroll::newest()),
15571 window,
15572 cx,
15573 |s| s.select_anchors(entry.selections.to_vec()),
15574 );
15575 });
15576 self.selection_history.mode = SelectionHistoryMode::Normal;
15577
15578 self.select_next_state = entry.select_next_state;
15579 self.select_prev_state = entry.select_prev_state;
15580 self.add_selections_state = entry.add_selections_state;
15581 }
15582 }
15583
15584 pub fn expand_excerpts(
15585 &mut self,
15586 action: &ExpandExcerpts,
15587 _: &mut Window,
15588 cx: &mut Context<Self>,
15589 ) {
15590 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15591 }
15592
15593 pub fn expand_excerpts_down(
15594 &mut self,
15595 action: &ExpandExcerptsDown,
15596 _: &mut Window,
15597 cx: &mut Context<Self>,
15598 ) {
15599 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15600 }
15601
15602 pub fn expand_excerpts_up(
15603 &mut self,
15604 action: &ExpandExcerptsUp,
15605 _: &mut Window,
15606 cx: &mut Context<Self>,
15607 ) {
15608 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15609 }
15610
15611 pub fn expand_excerpts_for_direction(
15612 &mut self,
15613 lines: u32,
15614 direction: ExpandExcerptDirection,
15615
15616 cx: &mut Context<Self>,
15617 ) {
15618 let selections = self.selections.disjoint_anchors();
15619
15620 let lines = if lines == 0 {
15621 EditorSettings::get_global(cx).expand_excerpt_lines
15622 } else {
15623 lines
15624 };
15625
15626 self.buffer.update(cx, |buffer, cx| {
15627 let snapshot = buffer.snapshot(cx);
15628 let mut excerpt_ids = selections
15629 .iter()
15630 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15631 .collect::<Vec<_>>();
15632 excerpt_ids.sort();
15633 excerpt_ids.dedup();
15634 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15635 })
15636 }
15637
15638 pub fn expand_excerpt(
15639 &mut self,
15640 excerpt: ExcerptId,
15641 direction: ExpandExcerptDirection,
15642 window: &mut Window,
15643 cx: &mut Context<Self>,
15644 ) {
15645 let current_scroll_position = self.scroll_position(cx);
15646 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15647 let mut should_scroll_up = false;
15648
15649 if direction == ExpandExcerptDirection::Down {
15650 let multi_buffer = self.buffer.read(cx);
15651 let snapshot = multi_buffer.snapshot(cx);
15652 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15653 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15654 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15655 {
15656 let buffer_snapshot = buffer.read(cx).snapshot();
15657 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15658 let last_row = buffer_snapshot.max_point().row;
15659 let lines_below = last_row.saturating_sub(excerpt_end_row);
15660 should_scroll_up = lines_below >= lines_to_expand;
15661 }
15662 }
15663
15664 self.buffer.update(cx, |buffer, cx| {
15665 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15666 });
15667
15668 if should_scroll_up {
15669 let new_scroll_position =
15670 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15671 self.set_scroll_position(new_scroll_position, window, cx);
15672 }
15673 }
15674
15675 pub fn go_to_singleton_buffer_point(
15676 &mut self,
15677 point: Point,
15678 window: &mut Window,
15679 cx: &mut Context<Self>,
15680 ) {
15681 self.go_to_singleton_buffer_range(point..point, window, cx);
15682 }
15683
15684 pub fn go_to_singleton_buffer_range(
15685 &mut self,
15686 range: Range<Point>,
15687 window: &mut Window,
15688 cx: &mut Context<Self>,
15689 ) {
15690 let multibuffer = self.buffer().read(cx);
15691 let Some(buffer) = multibuffer.as_singleton() else {
15692 return;
15693 };
15694 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15695 return;
15696 };
15697 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15698 return;
15699 };
15700 self.change_selections(
15701 SelectionEffects::default().nav_history(true),
15702 window,
15703 cx,
15704 |s| s.select_anchor_ranges([start..end]),
15705 );
15706 }
15707
15708 pub fn go_to_diagnostic(
15709 &mut self,
15710 action: &GoToDiagnostic,
15711 window: &mut Window,
15712 cx: &mut Context<Self>,
15713 ) {
15714 if !self.diagnostics_enabled() {
15715 return;
15716 }
15717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15718 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15719 }
15720
15721 pub fn go_to_prev_diagnostic(
15722 &mut self,
15723 action: &GoToPreviousDiagnostic,
15724 window: &mut Window,
15725 cx: &mut Context<Self>,
15726 ) {
15727 if !self.diagnostics_enabled() {
15728 return;
15729 }
15730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15731 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15732 }
15733
15734 pub fn go_to_diagnostic_impl(
15735 &mut self,
15736 direction: Direction,
15737 severity: GoToDiagnosticSeverityFilter,
15738 window: &mut Window,
15739 cx: &mut Context<Self>,
15740 ) {
15741 let buffer = self.buffer.read(cx).snapshot(cx);
15742 let selection = self.selections.newest::<usize>(cx);
15743
15744 let mut active_group_id = None;
15745 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15746 && active_group.active_range.start.to_offset(&buffer) == selection.start
15747 {
15748 active_group_id = Some(active_group.group_id);
15749 }
15750
15751 fn filtered(
15752 snapshot: EditorSnapshot,
15753 severity: GoToDiagnosticSeverityFilter,
15754 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15755 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15756 diagnostics
15757 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15758 .filter(|entry| entry.range.start != entry.range.end)
15759 .filter(|entry| !entry.diagnostic.is_unnecessary)
15760 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15761 }
15762
15763 let snapshot = self.snapshot(window, cx);
15764 let before = filtered(
15765 snapshot.clone(),
15766 severity,
15767 buffer
15768 .diagnostics_in_range(0..selection.start)
15769 .filter(|entry| entry.range.start <= selection.start),
15770 );
15771 let after = filtered(
15772 snapshot,
15773 severity,
15774 buffer
15775 .diagnostics_in_range(selection.start..buffer.len())
15776 .filter(|entry| entry.range.start >= selection.start),
15777 );
15778
15779 let mut found: Option<DiagnosticEntry<usize>> = None;
15780 if direction == Direction::Prev {
15781 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15782 {
15783 for diagnostic in prev_diagnostics.into_iter().rev() {
15784 if diagnostic.range.start != selection.start
15785 || active_group_id
15786 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15787 {
15788 found = Some(diagnostic);
15789 break 'outer;
15790 }
15791 }
15792 }
15793 } else {
15794 for diagnostic in after.chain(before) {
15795 if diagnostic.range.start != selection.start
15796 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15797 {
15798 found = Some(diagnostic);
15799 break;
15800 }
15801 }
15802 }
15803 let Some(next_diagnostic) = found else {
15804 return;
15805 };
15806
15807 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15808 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15809 return;
15810 };
15811 self.change_selections(Default::default(), window, cx, |s| {
15812 s.select_ranges(vec![
15813 next_diagnostic.range.start..next_diagnostic.range.start,
15814 ])
15815 });
15816 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15817 self.refresh_edit_prediction(false, true, window, cx);
15818 }
15819
15820 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15821 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15822 let snapshot = self.snapshot(window, cx);
15823 let selection = self.selections.newest::<Point>(cx);
15824 self.go_to_hunk_before_or_after_position(
15825 &snapshot,
15826 selection.head(),
15827 Direction::Next,
15828 window,
15829 cx,
15830 );
15831 }
15832
15833 pub fn go_to_hunk_before_or_after_position(
15834 &mut self,
15835 snapshot: &EditorSnapshot,
15836 position: Point,
15837 direction: Direction,
15838 window: &mut Window,
15839 cx: &mut Context<Editor>,
15840 ) {
15841 let row = if direction == Direction::Next {
15842 self.hunk_after_position(snapshot, position)
15843 .map(|hunk| hunk.row_range.start)
15844 } else {
15845 self.hunk_before_position(snapshot, position)
15846 };
15847
15848 if let Some(row) = row {
15849 let destination = Point::new(row.0, 0);
15850 let autoscroll = Autoscroll::center();
15851
15852 self.unfold_ranges(&[destination..destination], false, false, cx);
15853 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15854 s.select_ranges([destination..destination]);
15855 });
15856 }
15857 }
15858
15859 fn hunk_after_position(
15860 &mut self,
15861 snapshot: &EditorSnapshot,
15862 position: Point,
15863 ) -> Option<MultiBufferDiffHunk> {
15864 snapshot
15865 .buffer_snapshot
15866 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15867 .find(|hunk| hunk.row_range.start.0 > position.row)
15868 .or_else(|| {
15869 snapshot
15870 .buffer_snapshot
15871 .diff_hunks_in_range(Point::zero()..position)
15872 .find(|hunk| hunk.row_range.end.0 < position.row)
15873 })
15874 }
15875
15876 fn go_to_prev_hunk(
15877 &mut self,
15878 _: &GoToPreviousHunk,
15879 window: &mut Window,
15880 cx: &mut Context<Self>,
15881 ) {
15882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15883 let snapshot = self.snapshot(window, cx);
15884 let selection = self.selections.newest::<Point>(cx);
15885 self.go_to_hunk_before_or_after_position(
15886 &snapshot,
15887 selection.head(),
15888 Direction::Prev,
15889 window,
15890 cx,
15891 );
15892 }
15893
15894 fn hunk_before_position(
15895 &mut self,
15896 snapshot: &EditorSnapshot,
15897 position: Point,
15898 ) -> Option<MultiBufferRow> {
15899 snapshot
15900 .buffer_snapshot
15901 .diff_hunk_before(position)
15902 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15903 }
15904
15905 fn go_to_next_change(
15906 &mut self,
15907 _: &GoToNextChange,
15908 window: &mut Window,
15909 cx: &mut Context<Self>,
15910 ) {
15911 if let Some(selections) = self
15912 .change_list
15913 .next_change(1, Direction::Next)
15914 .map(|s| s.to_vec())
15915 {
15916 self.change_selections(Default::default(), window, cx, |s| {
15917 let map = s.display_map();
15918 s.select_display_ranges(selections.iter().map(|a| {
15919 let point = a.to_display_point(&map);
15920 point..point
15921 }))
15922 })
15923 }
15924 }
15925
15926 fn go_to_previous_change(
15927 &mut self,
15928 _: &GoToPreviousChange,
15929 window: &mut Window,
15930 cx: &mut Context<Self>,
15931 ) {
15932 if let Some(selections) = self
15933 .change_list
15934 .next_change(1, Direction::Prev)
15935 .map(|s| s.to_vec())
15936 {
15937 self.change_selections(Default::default(), window, cx, |s| {
15938 let map = s.display_map();
15939 s.select_display_ranges(selections.iter().map(|a| {
15940 let point = a.to_display_point(&map);
15941 point..point
15942 }))
15943 })
15944 }
15945 }
15946
15947 fn go_to_line<T: 'static>(
15948 &mut self,
15949 position: Anchor,
15950 highlight_color: Option<Hsla>,
15951 window: &mut Window,
15952 cx: &mut Context<Self>,
15953 ) {
15954 let snapshot = self.snapshot(window, cx).display_snapshot;
15955 let position = position.to_point(&snapshot.buffer_snapshot);
15956 let start = snapshot
15957 .buffer_snapshot
15958 .clip_point(Point::new(position.row, 0), Bias::Left);
15959 let end = start + Point::new(1, 0);
15960 let start = snapshot.buffer_snapshot.anchor_before(start);
15961 let end = snapshot.buffer_snapshot.anchor_before(end);
15962
15963 self.highlight_rows::<T>(
15964 start..end,
15965 highlight_color
15966 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15967 Default::default(),
15968 cx,
15969 );
15970
15971 if self.buffer.read(cx).is_singleton() {
15972 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15973 }
15974 }
15975
15976 pub fn go_to_definition(
15977 &mut self,
15978 _: &GoToDefinition,
15979 window: &mut Window,
15980 cx: &mut Context<Self>,
15981 ) -> Task<Result<Navigated>> {
15982 let definition =
15983 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15984 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15985 cx.spawn_in(window, async move |editor, cx| {
15986 if definition.await? == Navigated::Yes {
15987 return Ok(Navigated::Yes);
15988 }
15989 match fallback_strategy {
15990 GoToDefinitionFallback::None => Ok(Navigated::No),
15991 GoToDefinitionFallback::FindAllReferences => {
15992 match editor.update_in(cx, |editor, window, cx| {
15993 editor.find_all_references(&FindAllReferences, window, cx)
15994 })? {
15995 Some(references) => references.await,
15996 None => Ok(Navigated::No),
15997 }
15998 }
15999 }
16000 })
16001 }
16002
16003 pub fn go_to_declaration(
16004 &mut self,
16005 _: &GoToDeclaration,
16006 window: &mut Window,
16007 cx: &mut Context<Self>,
16008 ) -> Task<Result<Navigated>> {
16009 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16010 }
16011
16012 pub fn go_to_declaration_split(
16013 &mut self,
16014 _: &GoToDeclaration,
16015 window: &mut Window,
16016 cx: &mut Context<Self>,
16017 ) -> Task<Result<Navigated>> {
16018 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16019 }
16020
16021 pub fn go_to_implementation(
16022 &mut self,
16023 _: &GoToImplementation,
16024 window: &mut Window,
16025 cx: &mut Context<Self>,
16026 ) -> Task<Result<Navigated>> {
16027 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16028 }
16029
16030 pub fn go_to_implementation_split(
16031 &mut self,
16032 _: &GoToImplementationSplit,
16033 window: &mut Window,
16034 cx: &mut Context<Self>,
16035 ) -> Task<Result<Navigated>> {
16036 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16037 }
16038
16039 pub fn go_to_type_definition(
16040 &mut self,
16041 _: &GoToTypeDefinition,
16042 window: &mut Window,
16043 cx: &mut Context<Self>,
16044 ) -> Task<Result<Navigated>> {
16045 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16046 }
16047
16048 pub fn go_to_definition_split(
16049 &mut self,
16050 _: &GoToDefinitionSplit,
16051 window: &mut Window,
16052 cx: &mut Context<Self>,
16053 ) -> Task<Result<Navigated>> {
16054 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16055 }
16056
16057 pub fn go_to_type_definition_split(
16058 &mut self,
16059 _: &GoToTypeDefinitionSplit,
16060 window: &mut Window,
16061 cx: &mut Context<Self>,
16062 ) -> Task<Result<Navigated>> {
16063 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16064 }
16065
16066 fn go_to_definition_of_kind(
16067 &mut self,
16068 kind: GotoDefinitionKind,
16069 split: bool,
16070 window: &mut Window,
16071 cx: &mut Context<Self>,
16072 ) -> Task<Result<Navigated>> {
16073 let Some(provider) = self.semantics_provider.clone() else {
16074 return Task::ready(Ok(Navigated::No));
16075 };
16076 let head = self.selections.newest::<usize>(cx).head();
16077 let buffer = self.buffer.read(cx);
16078 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16079 return Task::ready(Ok(Navigated::No));
16080 };
16081 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16082 return Task::ready(Ok(Navigated::No));
16083 };
16084
16085 cx.spawn_in(window, async move |editor, cx| {
16086 let Some(definitions) = definitions.await? else {
16087 return Ok(Navigated::No);
16088 };
16089 let navigated = editor
16090 .update_in(cx, |editor, window, cx| {
16091 editor.navigate_to_hover_links(
16092 Some(kind),
16093 definitions
16094 .into_iter()
16095 .filter(|location| {
16096 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16097 })
16098 .map(HoverLink::Text)
16099 .collect::<Vec<_>>(),
16100 split,
16101 window,
16102 cx,
16103 )
16104 })?
16105 .await?;
16106 anyhow::Ok(navigated)
16107 })
16108 }
16109
16110 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16111 let selection = self.selections.newest_anchor();
16112 let head = selection.head();
16113 let tail = selection.tail();
16114
16115 let Some((buffer, start_position)) =
16116 self.buffer.read(cx).text_anchor_for_position(head, cx)
16117 else {
16118 return;
16119 };
16120
16121 let end_position = if head != tail {
16122 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16123 return;
16124 };
16125 Some(pos)
16126 } else {
16127 None
16128 };
16129
16130 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16131 let url = if let Some(end_pos) = end_position {
16132 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16133 } else {
16134 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16135 };
16136
16137 if let Some(url) = url {
16138 editor.update(cx, |_, cx| {
16139 cx.open_url(&url);
16140 })
16141 } else {
16142 Ok(())
16143 }
16144 });
16145
16146 url_finder.detach();
16147 }
16148
16149 pub fn open_selected_filename(
16150 &mut self,
16151 _: &OpenSelectedFilename,
16152 window: &mut Window,
16153 cx: &mut Context<Self>,
16154 ) {
16155 let Some(workspace) = self.workspace() else {
16156 return;
16157 };
16158
16159 let position = self.selections.newest_anchor().head();
16160
16161 let Some((buffer, buffer_position)) =
16162 self.buffer.read(cx).text_anchor_for_position(position, cx)
16163 else {
16164 return;
16165 };
16166
16167 let project = self.project.clone();
16168
16169 cx.spawn_in(window, async move |_, cx| {
16170 let result = find_file(&buffer, project, buffer_position, cx).await;
16171
16172 if let Some((_, path)) = result {
16173 workspace
16174 .update_in(cx, |workspace, window, cx| {
16175 workspace.open_resolved_path(path, window, cx)
16176 })?
16177 .await?;
16178 }
16179 anyhow::Ok(())
16180 })
16181 .detach();
16182 }
16183
16184 pub(crate) fn navigate_to_hover_links(
16185 &mut self,
16186 kind: Option<GotoDefinitionKind>,
16187 definitions: Vec<HoverLink>,
16188 split: bool,
16189 window: &mut Window,
16190 cx: &mut Context<Editor>,
16191 ) -> Task<Result<Navigated>> {
16192 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16193 let mut first_url_or_file = None;
16194 let definitions: Vec<_> = definitions
16195 .into_iter()
16196 .filter_map(|def| match def {
16197 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16198 HoverLink::InlayHint(lsp_location, server_id) => {
16199 let computation =
16200 self.compute_target_location(lsp_location, server_id, window, cx);
16201 Some(cx.background_spawn(computation))
16202 }
16203 HoverLink::Url(url) => {
16204 first_url_or_file = Some(Either::Left(url));
16205 None
16206 }
16207 HoverLink::File(path) => {
16208 first_url_or_file = Some(Either::Right(path));
16209 None
16210 }
16211 })
16212 .collect();
16213
16214 let workspace = self.workspace();
16215
16216 cx.spawn_in(window, async move |editor, acx| {
16217 let mut locations: Vec<Location> = future::join_all(definitions)
16218 .await
16219 .into_iter()
16220 .filter_map(|location| location.transpose())
16221 .collect::<Result<_>>()
16222 .context("location tasks")?;
16223
16224 if locations.len() > 1 {
16225 let Some(workspace) = workspace else {
16226 return Ok(Navigated::No);
16227 };
16228
16229 let tab_kind = match kind {
16230 Some(GotoDefinitionKind::Implementation) => "Implementations",
16231 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16232 Some(GotoDefinitionKind::Declaration) => "Declarations",
16233 Some(GotoDefinitionKind::Type) => "Types",
16234 };
16235 let title = editor
16236 .update_in(acx, |_, _, cx| {
16237 let target = locations
16238 .iter()
16239 .map(|location| {
16240 location
16241 .buffer
16242 .read(cx)
16243 .text_for_range(location.range.clone())
16244 .collect::<String>()
16245 })
16246 .filter(|text| !text.contains('\n'))
16247 .unique()
16248 .take(3)
16249 .join(", ");
16250 if target.is_empty() {
16251 tab_kind.to_owned()
16252 } else {
16253 format!("{tab_kind} for {target}")
16254 }
16255 })
16256 .context("buffer title")?;
16257
16258 let opened = workspace
16259 .update_in(acx, |workspace, window, cx| {
16260 Self::open_locations_in_multibuffer(
16261 workspace,
16262 locations,
16263 title,
16264 split,
16265 MultibufferSelectionMode::First,
16266 window,
16267 cx,
16268 )
16269 })
16270 .is_ok();
16271
16272 anyhow::Ok(Navigated::from_bool(opened))
16273 } else if locations.is_empty() {
16274 // If there is one definition, just open it directly
16275 match first_url_or_file {
16276 Some(Either::Left(url)) => {
16277 acx.update(|_, cx| cx.open_url(&url))?;
16278 Ok(Navigated::Yes)
16279 }
16280 Some(Either::Right(path)) => {
16281 let Some(workspace) = workspace else {
16282 return Ok(Navigated::No);
16283 };
16284
16285 workspace
16286 .update_in(acx, |workspace, window, cx| {
16287 workspace.open_resolved_path(path, window, cx)
16288 })?
16289 .await?;
16290 Ok(Navigated::Yes)
16291 }
16292 None => Ok(Navigated::No),
16293 }
16294 } else {
16295 let Some(workspace) = workspace else {
16296 return Ok(Navigated::No);
16297 };
16298
16299 let target = locations.pop().unwrap();
16300 editor.update_in(acx, |editor, window, cx| {
16301 let pane = workspace.read(cx).active_pane().clone();
16302
16303 let range = target.range.to_point(target.buffer.read(cx));
16304 let range = editor.range_for_match(&range);
16305 let range = collapse_multiline_range(range);
16306
16307 if !split
16308 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16309 {
16310 editor.go_to_singleton_buffer_range(range, window, cx);
16311 } else {
16312 window.defer(cx, move |window, cx| {
16313 let target_editor: Entity<Self> =
16314 workspace.update(cx, |workspace, cx| {
16315 let pane = if split {
16316 workspace.adjacent_pane(window, cx)
16317 } else {
16318 workspace.active_pane().clone()
16319 };
16320
16321 workspace.open_project_item(
16322 pane,
16323 target.buffer.clone(),
16324 true,
16325 true,
16326 window,
16327 cx,
16328 )
16329 });
16330 target_editor.update(cx, |target_editor, cx| {
16331 // When selecting a definition in a different buffer, disable the nav history
16332 // to avoid creating a history entry at the previous cursor location.
16333 pane.update(cx, |pane, _| pane.disable_history());
16334 target_editor.go_to_singleton_buffer_range(range, window, cx);
16335 pane.update(cx, |pane, _| pane.enable_history());
16336 });
16337 });
16338 }
16339 Navigated::Yes
16340 })
16341 }
16342 })
16343 }
16344
16345 fn compute_target_location(
16346 &self,
16347 lsp_location: lsp::Location,
16348 server_id: LanguageServerId,
16349 window: &mut Window,
16350 cx: &mut Context<Self>,
16351 ) -> Task<anyhow::Result<Option<Location>>> {
16352 let Some(project) = self.project.clone() else {
16353 return Task::ready(Ok(None));
16354 };
16355
16356 cx.spawn_in(window, async move |editor, cx| {
16357 let location_task = editor.update(cx, |_, cx| {
16358 project.update(cx, |project, cx| {
16359 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16360 })
16361 })?;
16362 let location = Some({
16363 let target_buffer_handle = location_task.await.context("open local buffer")?;
16364 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16365 let target_start = target_buffer
16366 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16367 let target_end = target_buffer
16368 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16369 target_buffer.anchor_after(target_start)
16370 ..target_buffer.anchor_before(target_end)
16371 })?;
16372 Location {
16373 buffer: target_buffer_handle,
16374 range,
16375 }
16376 });
16377 Ok(location)
16378 })
16379 }
16380
16381 pub fn find_all_references(
16382 &mut self,
16383 _: &FindAllReferences,
16384 window: &mut Window,
16385 cx: &mut Context<Self>,
16386 ) -> Option<Task<Result<Navigated>>> {
16387 let selection = self.selections.newest::<usize>(cx);
16388 let multi_buffer = self.buffer.read(cx);
16389 let head = selection.head();
16390
16391 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16392 let head_anchor = multi_buffer_snapshot.anchor_at(
16393 head,
16394 if head < selection.tail() {
16395 Bias::Right
16396 } else {
16397 Bias::Left
16398 },
16399 );
16400
16401 match self
16402 .find_all_references_task_sources
16403 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16404 {
16405 Ok(_) => {
16406 log::info!(
16407 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16408 );
16409 return None;
16410 }
16411 Err(i) => {
16412 self.find_all_references_task_sources.insert(i, head_anchor);
16413 }
16414 }
16415
16416 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16417 let workspace = self.workspace()?;
16418 let project = workspace.read(cx).project().clone();
16419 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16420 Some(cx.spawn_in(window, async move |editor, cx| {
16421 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16422 if let Ok(i) = editor
16423 .find_all_references_task_sources
16424 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16425 {
16426 editor.find_all_references_task_sources.remove(i);
16427 }
16428 });
16429
16430 let Some(locations) = references.await? else {
16431 return anyhow::Ok(Navigated::No);
16432 };
16433 if locations.is_empty() {
16434 return anyhow::Ok(Navigated::No);
16435 }
16436
16437 workspace.update_in(cx, |workspace, window, cx| {
16438 let target = locations
16439 .iter()
16440 .map(|location| {
16441 location
16442 .buffer
16443 .read(cx)
16444 .text_for_range(location.range.clone())
16445 .collect::<String>()
16446 })
16447 .filter(|text| !text.contains('\n'))
16448 .unique()
16449 .take(3)
16450 .join(", ");
16451 let title = if target.is_empty() {
16452 "References".to_owned()
16453 } else {
16454 format!("References to {target}")
16455 };
16456 Self::open_locations_in_multibuffer(
16457 workspace,
16458 locations,
16459 title,
16460 false,
16461 MultibufferSelectionMode::First,
16462 window,
16463 cx,
16464 );
16465 Navigated::Yes
16466 })
16467 }))
16468 }
16469
16470 /// Opens a multibuffer with the given project locations in it
16471 pub fn open_locations_in_multibuffer(
16472 workspace: &mut Workspace,
16473 mut locations: Vec<Location>,
16474 title: String,
16475 split: bool,
16476 multibuffer_selection_mode: MultibufferSelectionMode,
16477 window: &mut Window,
16478 cx: &mut Context<Workspace>,
16479 ) {
16480 if locations.is_empty() {
16481 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16482 return;
16483 }
16484
16485 // If there are multiple definitions, open them in a multibuffer
16486 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
16487 let mut locations = locations.into_iter().peekable();
16488 let mut ranges: Vec<Range<Anchor>> = Vec::new();
16489 let capability = workspace.project().read(cx).capability();
16490
16491 let excerpt_buffer = cx.new(|cx| {
16492 let mut multibuffer = MultiBuffer::new(capability);
16493 while let Some(location) = locations.next() {
16494 let buffer = location.buffer.read(cx);
16495 let mut ranges_for_buffer = Vec::new();
16496 let range = location.range.to_point(buffer);
16497 ranges_for_buffer.push(range.clone());
16498
16499 while let Some(next_location) = locations.peek() {
16500 if next_location.buffer == location.buffer {
16501 ranges_for_buffer.push(next_location.range.to_point(buffer));
16502 locations.next();
16503 } else {
16504 break;
16505 }
16506 }
16507
16508 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16509 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16510 PathKey::for_buffer(&location.buffer, cx),
16511 location.buffer.clone(),
16512 ranges_for_buffer,
16513 multibuffer_context_lines(cx),
16514 cx,
16515 );
16516 ranges.extend(new_ranges)
16517 }
16518
16519 multibuffer.with_title(title)
16520 });
16521
16522 let editor = cx.new(|cx| {
16523 Editor::for_multibuffer(
16524 excerpt_buffer,
16525 Some(workspace.project().clone()),
16526 window,
16527 cx,
16528 )
16529 });
16530 editor.update(cx, |editor, cx| {
16531 match multibuffer_selection_mode {
16532 MultibufferSelectionMode::First => {
16533 if let Some(first_range) = ranges.first() {
16534 editor.change_selections(
16535 SelectionEffects::no_scroll(),
16536 window,
16537 cx,
16538 |selections| {
16539 selections.clear_disjoint();
16540 selections
16541 .select_anchor_ranges(std::iter::once(first_range.clone()));
16542 },
16543 );
16544 }
16545 editor.highlight_background::<Self>(
16546 &ranges,
16547 |theme| theme.colors().editor_highlighted_line_background,
16548 cx,
16549 );
16550 }
16551 MultibufferSelectionMode::All => {
16552 editor.change_selections(
16553 SelectionEffects::no_scroll(),
16554 window,
16555 cx,
16556 |selections| {
16557 selections.clear_disjoint();
16558 selections.select_anchor_ranges(ranges);
16559 },
16560 );
16561 }
16562 }
16563 editor.register_buffers_with_language_servers(cx);
16564 });
16565
16566 let item = Box::new(editor);
16567 let item_id = item.item_id();
16568
16569 if split {
16570 workspace.split_item(SplitDirection::Right, item, window, cx);
16571 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16572 let (preview_item_id, preview_item_idx) =
16573 workspace.active_pane().read_with(cx, |pane, _| {
16574 (pane.preview_item_id(), pane.preview_item_idx())
16575 });
16576
16577 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16578
16579 if let Some(preview_item_id) = preview_item_id {
16580 workspace.active_pane().update(cx, |pane, cx| {
16581 pane.remove_item(preview_item_id, false, false, window, cx);
16582 });
16583 }
16584 } else {
16585 workspace.add_item_to_active_pane(item, None, true, window, cx);
16586 }
16587 workspace.active_pane().update(cx, |pane, cx| {
16588 pane.set_preview_item_id(Some(item_id), cx);
16589 });
16590 }
16591
16592 pub fn rename(
16593 &mut self,
16594 _: &Rename,
16595 window: &mut Window,
16596 cx: &mut Context<Self>,
16597 ) -> Option<Task<Result<()>>> {
16598 use language::ToOffset as _;
16599
16600 let provider = self.semantics_provider.clone()?;
16601 let selection = self.selections.newest_anchor().clone();
16602 let (cursor_buffer, cursor_buffer_position) = self
16603 .buffer
16604 .read(cx)
16605 .text_anchor_for_position(selection.head(), cx)?;
16606 let (tail_buffer, cursor_buffer_position_end) = self
16607 .buffer
16608 .read(cx)
16609 .text_anchor_for_position(selection.tail(), cx)?;
16610 if tail_buffer != cursor_buffer {
16611 return None;
16612 }
16613
16614 let snapshot = cursor_buffer.read(cx).snapshot();
16615 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16616 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16617 let prepare_rename = provider
16618 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16619 .unwrap_or_else(|| Task::ready(Ok(None)));
16620 drop(snapshot);
16621
16622 Some(cx.spawn_in(window, async move |this, cx| {
16623 let rename_range = if let Some(range) = prepare_rename.await? {
16624 Some(range)
16625 } else {
16626 this.update(cx, |this, cx| {
16627 let buffer = this.buffer.read(cx).snapshot(cx);
16628 let mut buffer_highlights = this
16629 .document_highlights_for_position(selection.head(), &buffer)
16630 .filter(|highlight| {
16631 highlight.start.excerpt_id == selection.head().excerpt_id
16632 && highlight.end.excerpt_id == selection.head().excerpt_id
16633 });
16634 buffer_highlights
16635 .next()
16636 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16637 })?
16638 };
16639 if let Some(rename_range) = rename_range {
16640 this.update_in(cx, |this, window, cx| {
16641 let snapshot = cursor_buffer.read(cx).snapshot();
16642 let rename_buffer_range = rename_range.to_offset(&snapshot);
16643 let cursor_offset_in_rename_range =
16644 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16645 let cursor_offset_in_rename_range_end =
16646 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16647
16648 this.take_rename(false, window, cx);
16649 let buffer = this.buffer.read(cx).read(cx);
16650 let cursor_offset = selection.head().to_offset(&buffer);
16651 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16652 let rename_end = rename_start + rename_buffer_range.len();
16653 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16654 let mut old_highlight_id = None;
16655 let old_name: Arc<str> = buffer
16656 .chunks(rename_start..rename_end, true)
16657 .map(|chunk| {
16658 if old_highlight_id.is_none() {
16659 old_highlight_id = chunk.syntax_highlight_id;
16660 }
16661 chunk.text
16662 })
16663 .collect::<String>()
16664 .into();
16665
16666 drop(buffer);
16667
16668 // Position the selection in the rename editor so that it matches the current selection.
16669 this.show_local_selections = false;
16670 let rename_editor = cx.new(|cx| {
16671 let mut editor = Editor::single_line(window, cx);
16672 editor.buffer.update(cx, |buffer, cx| {
16673 buffer.edit([(0..0, old_name.clone())], None, cx)
16674 });
16675 let rename_selection_range = match cursor_offset_in_rename_range
16676 .cmp(&cursor_offset_in_rename_range_end)
16677 {
16678 Ordering::Equal => {
16679 editor.select_all(&SelectAll, window, cx);
16680 return editor;
16681 }
16682 Ordering::Less => {
16683 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16684 }
16685 Ordering::Greater => {
16686 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16687 }
16688 };
16689 if rename_selection_range.end > old_name.len() {
16690 editor.select_all(&SelectAll, window, cx);
16691 } else {
16692 editor.change_selections(Default::default(), window, cx, |s| {
16693 s.select_ranges([rename_selection_range]);
16694 });
16695 }
16696 editor
16697 });
16698 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16699 if e == &EditorEvent::Focused {
16700 cx.emit(EditorEvent::FocusedIn)
16701 }
16702 })
16703 .detach();
16704
16705 let write_highlights =
16706 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16707 let read_highlights =
16708 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16709 let ranges = write_highlights
16710 .iter()
16711 .flat_map(|(_, ranges)| ranges.iter())
16712 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16713 .cloned()
16714 .collect();
16715
16716 this.highlight_text::<Rename>(
16717 ranges,
16718 HighlightStyle {
16719 fade_out: Some(0.6),
16720 ..Default::default()
16721 },
16722 cx,
16723 );
16724 let rename_focus_handle = rename_editor.focus_handle(cx);
16725 window.focus(&rename_focus_handle);
16726 let block_id = this.insert_blocks(
16727 [BlockProperties {
16728 style: BlockStyle::Flex,
16729 placement: BlockPlacement::Below(range.start),
16730 height: Some(1),
16731 render: Arc::new({
16732 let rename_editor = rename_editor.clone();
16733 move |cx: &mut BlockContext| {
16734 let mut text_style = cx.editor_style.text.clone();
16735 if let Some(highlight_style) = old_highlight_id
16736 .and_then(|h| h.style(&cx.editor_style.syntax))
16737 {
16738 text_style = text_style.highlight(highlight_style);
16739 }
16740 div()
16741 .block_mouse_except_scroll()
16742 .pl(cx.anchor_x)
16743 .child(EditorElement::new(
16744 &rename_editor,
16745 EditorStyle {
16746 background: cx.theme().system().transparent,
16747 local_player: cx.editor_style.local_player,
16748 text: text_style,
16749 scrollbar_width: cx.editor_style.scrollbar_width,
16750 syntax: cx.editor_style.syntax.clone(),
16751 status: cx.editor_style.status.clone(),
16752 inlay_hints_style: HighlightStyle {
16753 font_weight: Some(FontWeight::BOLD),
16754 ..make_inlay_hints_style(cx.app)
16755 },
16756 edit_prediction_styles: make_suggestion_styles(
16757 cx.app,
16758 ),
16759 ..EditorStyle::default()
16760 },
16761 ))
16762 .into_any_element()
16763 }
16764 }),
16765 priority: 0,
16766 }],
16767 Some(Autoscroll::fit()),
16768 cx,
16769 )[0];
16770 this.pending_rename = Some(RenameState {
16771 range,
16772 old_name,
16773 editor: rename_editor,
16774 block_id,
16775 });
16776 })?;
16777 }
16778
16779 Ok(())
16780 }))
16781 }
16782
16783 pub fn confirm_rename(
16784 &mut self,
16785 _: &ConfirmRename,
16786 window: &mut Window,
16787 cx: &mut Context<Self>,
16788 ) -> Option<Task<Result<()>>> {
16789 let rename = self.take_rename(false, window, cx)?;
16790 let workspace = self.workspace()?.downgrade();
16791 let (buffer, start) = self
16792 .buffer
16793 .read(cx)
16794 .text_anchor_for_position(rename.range.start, cx)?;
16795 let (end_buffer, _) = self
16796 .buffer
16797 .read(cx)
16798 .text_anchor_for_position(rename.range.end, cx)?;
16799 if buffer != end_buffer {
16800 return None;
16801 }
16802
16803 let old_name = rename.old_name;
16804 let new_name = rename.editor.read(cx).text(cx);
16805
16806 let rename = self.semantics_provider.as_ref()?.perform_rename(
16807 &buffer,
16808 start,
16809 new_name.clone(),
16810 cx,
16811 )?;
16812
16813 Some(cx.spawn_in(window, async move |editor, cx| {
16814 let project_transaction = rename.await?;
16815 Self::open_project_transaction(
16816 &editor,
16817 workspace,
16818 project_transaction,
16819 format!("Rename: {} → {}", old_name, new_name),
16820 cx,
16821 )
16822 .await?;
16823
16824 editor.update(cx, |editor, cx| {
16825 editor.refresh_document_highlights(cx);
16826 })?;
16827 Ok(())
16828 }))
16829 }
16830
16831 fn take_rename(
16832 &mut self,
16833 moving_cursor: bool,
16834 window: &mut Window,
16835 cx: &mut Context<Self>,
16836 ) -> Option<RenameState> {
16837 let rename = self.pending_rename.take()?;
16838 if rename.editor.focus_handle(cx).is_focused(window) {
16839 window.focus(&self.focus_handle);
16840 }
16841
16842 self.remove_blocks(
16843 [rename.block_id].into_iter().collect(),
16844 Some(Autoscroll::fit()),
16845 cx,
16846 );
16847 self.clear_highlights::<Rename>(cx);
16848 self.show_local_selections = true;
16849
16850 if moving_cursor {
16851 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16852 editor.selections.newest::<usize>(cx).head()
16853 });
16854
16855 // Update the selection to match the position of the selection inside
16856 // the rename editor.
16857 let snapshot = self.buffer.read(cx).read(cx);
16858 let rename_range = rename.range.to_offset(&snapshot);
16859 let cursor_in_editor = snapshot
16860 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16861 .min(rename_range.end);
16862 drop(snapshot);
16863
16864 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16865 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16866 });
16867 } else {
16868 self.refresh_document_highlights(cx);
16869 }
16870
16871 Some(rename)
16872 }
16873
16874 pub fn pending_rename(&self) -> Option<&RenameState> {
16875 self.pending_rename.as_ref()
16876 }
16877
16878 fn format(
16879 &mut self,
16880 _: &Format,
16881 window: &mut Window,
16882 cx: &mut Context<Self>,
16883 ) -> Option<Task<Result<()>>> {
16884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16885
16886 let project = match &self.project {
16887 Some(project) => project.clone(),
16888 None => return None,
16889 };
16890
16891 Some(self.perform_format(
16892 project,
16893 FormatTrigger::Manual,
16894 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16895 window,
16896 cx,
16897 ))
16898 }
16899
16900 fn format_selections(
16901 &mut self,
16902 _: &FormatSelections,
16903 window: &mut Window,
16904 cx: &mut Context<Self>,
16905 ) -> Option<Task<Result<()>>> {
16906 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16907
16908 let project = match &self.project {
16909 Some(project) => project.clone(),
16910 None => return None,
16911 };
16912
16913 let ranges = self
16914 .selections
16915 .all_adjusted(cx)
16916 .into_iter()
16917 .map(|selection| selection.range())
16918 .collect_vec();
16919
16920 Some(self.perform_format(
16921 project,
16922 FormatTrigger::Manual,
16923 FormatTarget::Ranges(ranges),
16924 window,
16925 cx,
16926 ))
16927 }
16928
16929 fn perform_format(
16930 &mut self,
16931 project: Entity<Project>,
16932 trigger: FormatTrigger,
16933 target: FormatTarget,
16934 window: &mut Window,
16935 cx: &mut Context<Self>,
16936 ) -> Task<Result<()>> {
16937 let buffer = self.buffer.clone();
16938 let (buffers, target) = match target {
16939 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16940 FormatTarget::Ranges(selection_ranges) => {
16941 let multi_buffer = buffer.read(cx);
16942 let snapshot = multi_buffer.read(cx);
16943 let mut buffers = HashSet::default();
16944 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16945 BTreeMap::new();
16946 for selection_range in selection_ranges {
16947 for (buffer, buffer_range, _) in
16948 snapshot.range_to_buffer_ranges(selection_range)
16949 {
16950 let buffer_id = buffer.remote_id();
16951 let start = buffer.anchor_before(buffer_range.start);
16952 let end = buffer.anchor_after(buffer_range.end);
16953 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16954 buffer_id_to_ranges
16955 .entry(buffer_id)
16956 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16957 .or_insert_with(|| vec![start..end]);
16958 }
16959 }
16960 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16961 }
16962 };
16963
16964 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16965 let selections_prev = transaction_id_prev
16966 .and_then(|transaction_id_prev| {
16967 // default to selections as they were after the last edit, if we have them,
16968 // instead of how they are now.
16969 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16970 // will take you back to where you made the last edit, instead of staying where you scrolled
16971 self.selection_history
16972 .transaction(transaction_id_prev)
16973 .map(|t| t.0.clone())
16974 })
16975 .unwrap_or_else(|| self.selections.disjoint_anchors());
16976
16977 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16978 let format = project.update(cx, |project, cx| {
16979 project.format(buffers, target, true, trigger, cx)
16980 });
16981
16982 cx.spawn_in(window, async move |editor, cx| {
16983 let transaction = futures::select_biased! {
16984 transaction = format.log_err().fuse() => transaction,
16985 () = timeout => {
16986 log::warn!("timed out waiting for formatting");
16987 None
16988 }
16989 };
16990
16991 buffer
16992 .update(cx, |buffer, cx| {
16993 if let Some(transaction) = transaction
16994 && !buffer.is_singleton()
16995 {
16996 buffer.push_transaction(&transaction.0, cx);
16997 }
16998 cx.notify();
16999 })
17000 .ok();
17001
17002 if let Some(transaction_id_now) =
17003 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17004 {
17005 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17006 if has_new_transaction {
17007 _ = editor.update(cx, |editor, _| {
17008 editor
17009 .selection_history
17010 .insert_transaction(transaction_id_now, selections_prev);
17011 });
17012 }
17013 }
17014
17015 Ok(())
17016 })
17017 }
17018
17019 fn organize_imports(
17020 &mut self,
17021 _: &OrganizeImports,
17022 window: &mut Window,
17023 cx: &mut Context<Self>,
17024 ) -> Option<Task<Result<()>>> {
17025 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17026 let project = match &self.project {
17027 Some(project) => project.clone(),
17028 None => return None,
17029 };
17030 Some(self.perform_code_action_kind(
17031 project,
17032 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17033 window,
17034 cx,
17035 ))
17036 }
17037
17038 fn perform_code_action_kind(
17039 &mut self,
17040 project: Entity<Project>,
17041 kind: CodeActionKind,
17042 window: &mut Window,
17043 cx: &mut Context<Self>,
17044 ) -> Task<Result<()>> {
17045 let buffer = self.buffer.clone();
17046 let buffers = buffer.read(cx).all_buffers();
17047 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17048 let apply_action = project.update(cx, |project, cx| {
17049 project.apply_code_action_kind(buffers, kind, true, cx)
17050 });
17051 cx.spawn_in(window, async move |_, cx| {
17052 let transaction = futures::select_biased! {
17053 () = timeout => {
17054 log::warn!("timed out waiting for executing code action");
17055 None
17056 }
17057 transaction = apply_action.log_err().fuse() => transaction,
17058 };
17059 buffer
17060 .update(cx, |buffer, cx| {
17061 // check if we need this
17062 if let Some(transaction) = transaction
17063 && !buffer.is_singleton()
17064 {
17065 buffer.push_transaction(&transaction.0, cx);
17066 }
17067 cx.notify();
17068 })
17069 .ok();
17070 Ok(())
17071 })
17072 }
17073
17074 pub fn restart_language_server(
17075 &mut self,
17076 _: &RestartLanguageServer,
17077 _: &mut Window,
17078 cx: &mut Context<Self>,
17079 ) {
17080 if let Some(project) = self.project.clone() {
17081 self.buffer.update(cx, |multi_buffer, cx| {
17082 project.update(cx, |project, cx| {
17083 project.restart_language_servers_for_buffers(
17084 multi_buffer.all_buffers().into_iter().collect(),
17085 HashSet::default(),
17086 cx,
17087 );
17088 });
17089 })
17090 }
17091 }
17092
17093 pub fn stop_language_server(
17094 &mut self,
17095 _: &StopLanguageServer,
17096 _: &mut Window,
17097 cx: &mut Context<Self>,
17098 ) {
17099 if let Some(project) = self.project.clone() {
17100 self.buffer.update(cx, |multi_buffer, cx| {
17101 project.update(cx, |project, cx| {
17102 project.stop_language_servers_for_buffers(
17103 multi_buffer.all_buffers().into_iter().collect(),
17104 HashSet::default(),
17105 cx,
17106 );
17107 cx.emit(project::Event::RefreshInlayHints);
17108 });
17109 });
17110 }
17111 }
17112
17113 fn cancel_language_server_work(
17114 workspace: &mut Workspace,
17115 _: &actions::CancelLanguageServerWork,
17116 _: &mut Window,
17117 cx: &mut Context<Workspace>,
17118 ) {
17119 let project = workspace.project();
17120 let buffers = workspace
17121 .active_item(cx)
17122 .and_then(|item| item.act_as::<Editor>(cx))
17123 .map_or(HashSet::default(), |editor| {
17124 editor.read(cx).buffer.read(cx).all_buffers()
17125 });
17126 project.update(cx, |project, cx| {
17127 project.cancel_language_server_work_for_buffers(buffers, cx);
17128 });
17129 }
17130
17131 fn show_character_palette(
17132 &mut self,
17133 _: &ShowCharacterPalette,
17134 window: &mut Window,
17135 _: &mut Context<Self>,
17136 ) {
17137 window.show_character_palette();
17138 }
17139
17140 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17141 if !self.diagnostics_enabled() {
17142 return;
17143 }
17144
17145 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17146 let buffer = self.buffer.read(cx).snapshot(cx);
17147 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17148 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17149 let is_valid = buffer
17150 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17151 .any(|entry| {
17152 entry.diagnostic.is_primary
17153 && !entry.range.is_empty()
17154 && entry.range.start == primary_range_start
17155 && entry.diagnostic.message == active_diagnostics.active_message
17156 });
17157
17158 if !is_valid {
17159 self.dismiss_diagnostics(cx);
17160 }
17161 }
17162 }
17163
17164 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17165 match &self.active_diagnostics {
17166 ActiveDiagnostic::Group(group) => Some(group),
17167 _ => None,
17168 }
17169 }
17170
17171 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17172 if !self.diagnostics_enabled() {
17173 return;
17174 }
17175 self.dismiss_diagnostics(cx);
17176 self.active_diagnostics = ActiveDiagnostic::All;
17177 }
17178
17179 fn activate_diagnostics(
17180 &mut self,
17181 buffer_id: BufferId,
17182 diagnostic: DiagnosticEntry<usize>,
17183 window: &mut Window,
17184 cx: &mut Context<Self>,
17185 ) {
17186 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17187 return;
17188 }
17189 self.dismiss_diagnostics(cx);
17190 let snapshot = self.snapshot(window, cx);
17191 let buffer = self.buffer.read(cx).snapshot(cx);
17192 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17193 return;
17194 };
17195
17196 let diagnostic_group = buffer
17197 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17198 .collect::<Vec<_>>();
17199
17200 let blocks =
17201 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17202
17203 let blocks = self.display_map.update(cx, |display_map, cx| {
17204 display_map.insert_blocks(blocks, cx).into_iter().collect()
17205 });
17206 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17207 active_range: buffer.anchor_before(diagnostic.range.start)
17208 ..buffer.anchor_after(diagnostic.range.end),
17209 active_message: diagnostic.diagnostic.message.clone(),
17210 group_id: diagnostic.diagnostic.group_id,
17211 blocks,
17212 });
17213 cx.notify();
17214 }
17215
17216 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17217 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17218 return;
17219 };
17220
17221 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17222 if let ActiveDiagnostic::Group(group) = prev {
17223 self.display_map.update(cx, |display_map, cx| {
17224 display_map.remove_blocks(group.blocks, cx);
17225 });
17226 cx.notify();
17227 }
17228 }
17229
17230 /// Disable inline diagnostics rendering for this editor.
17231 pub fn disable_inline_diagnostics(&mut self) {
17232 self.inline_diagnostics_enabled = false;
17233 self.inline_diagnostics_update = Task::ready(());
17234 self.inline_diagnostics.clear();
17235 }
17236
17237 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17238 self.diagnostics_enabled = false;
17239 self.dismiss_diagnostics(cx);
17240 self.inline_diagnostics_update = Task::ready(());
17241 self.inline_diagnostics.clear();
17242 }
17243
17244 pub fn disable_word_completions(&mut self) {
17245 self.word_completions_enabled = false;
17246 }
17247
17248 pub fn diagnostics_enabled(&self) -> bool {
17249 self.diagnostics_enabled && self.mode.is_full()
17250 }
17251
17252 pub fn inline_diagnostics_enabled(&self) -> bool {
17253 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17254 }
17255
17256 pub fn show_inline_diagnostics(&self) -> bool {
17257 self.show_inline_diagnostics
17258 }
17259
17260 pub fn toggle_inline_diagnostics(
17261 &mut self,
17262 _: &ToggleInlineDiagnostics,
17263 window: &mut Window,
17264 cx: &mut Context<Editor>,
17265 ) {
17266 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17267 self.refresh_inline_diagnostics(false, window, cx);
17268 }
17269
17270 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17271 self.diagnostics_max_severity = severity;
17272 self.display_map.update(cx, |display_map, _| {
17273 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17274 });
17275 }
17276
17277 pub fn toggle_diagnostics(
17278 &mut self,
17279 _: &ToggleDiagnostics,
17280 window: &mut Window,
17281 cx: &mut Context<Editor>,
17282 ) {
17283 if !self.diagnostics_enabled() {
17284 return;
17285 }
17286
17287 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17288 EditorSettings::get_global(cx)
17289 .diagnostics_max_severity
17290 .filter(|severity| severity != &DiagnosticSeverity::Off)
17291 .unwrap_or(DiagnosticSeverity::Hint)
17292 } else {
17293 DiagnosticSeverity::Off
17294 };
17295 self.set_max_diagnostics_severity(new_severity, cx);
17296 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17297 self.active_diagnostics = ActiveDiagnostic::None;
17298 self.inline_diagnostics_update = Task::ready(());
17299 self.inline_diagnostics.clear();
17300 } else {
17301 self.refresh_inline_diagnostics(false, window, cx);
17302 }
17303
17304 cx.notify();
17305 }
17306
17307 pub fn toggle_minimap(
17308 &mut self,
17309 _: &ToggleMinimap,
17310 window: &mut Window,
17311 cx: &mut Context<Editor>,
17312 ) {
17313 if self.supports_minimap(cx) {
17314 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17315 }
17316 }
17317
17318 fn refresh_inline_diagnostics(
17319 &mut self,
17320 debounce: bool,
17321 window: &mut Window,
17322 cx: &mut Context<Self>,
17323 ) {
17324 let max_severity = ProjectSettings::get_global(cx)
17325 .diagnostics
17326 .inline
17327 .max_severity
17328 .unwrap_or(self.diagnostics_max_severity);
17329
17330 if !self.inline_diagnostics_enabled()
17331 || !self.show_inline_diagnostics
17332 || max_severity == DiagnosticSeverity::Off
17333 {
17334 self.inline_diagnostics_update = Task::ready(());
17335 self.inline_diagnostics.clear();
17336 return;
17337 }
17338
17339 let debounce_ms = ProjectSettings::get_global(cx)
17340 .diagnostics
17341 .inline
17342 .update_debounce_ms;
17343 let debounce = if debounce && debounce_ms > 0 {
17344 Some(Duration::from_millis(debounce_ms))
17345 } else {
17346 None
17347 };
17348 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17349 if let Some(debounce) = debounce {
17350 cx.background_executor().timer(debounce).await;
17351 }
17352 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17353 editor
17354 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17355 .ok()
17356 }) else {
17357 return;
17358 };
17359
17360 let new_inline_diagnostics = cx
17361 .background_spawn(async move {
17362 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17363 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17364 let message = diagnostic_entry
17365 .diagnostic
17366 .message
17367 .split_once('\n')
17368 .map(|(line, _)| line)
17369 .map(SharedString::new)
17370 .unwrap_or_else(|| {
17371 SharedString::from(diagnostic_entry.diagnostic.message)
17372 });
17373 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17374 let (Ok(i) | Err(i)) = inline_diagnostics
17375 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17376 inline_diagnostics.insert(
17377 i,
17378 (
17379 start_anchor,
17380 InlineDiagnostic {
17381 message,
17382 group_id: diagnostic_entry.diagnostic.group_id,
17383 start: diagnostic_entry.range.start.to_point(&snapshot),
17384 is_primary: diagnostic_entry.diagnostic.is_primary,
17385 severity: diagnostic_entry.diagnostic.severity,
17386 },
17387 ),
17388 );
17389 }
17390 inline_diagnostics
17391 })
17392 .await;
17393
17394 editor
17395 .update(cx, |editor, cx| {
17396 editor.inline_diagnostics = new_inline_diagnostics;
17397 cx.notify();
17398 })
17399 .ok();
17400 });
17401 }
17402
17403 fn pull_diagnostics(
17404 &mut self,
17405 buffer_id: Option<BufferId>,
17406 window: &Window,
17407 cx: &mut Context<Self>,
17408 ) -> Option<()> {
17409 if !self.mode().is_full() {
17410 return None;
17411 }
17412 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17413 .diagnostics
17414 .lsp_pull_diagnostics;
17415 if !pull_diagnostics_settings.enabled {
17416 return None;
17417 }
17418 let project = self.project()?.downgrade();
17419 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17420 let mut buffers = self.buffer.read(cx).all_buffers();
17421 if let Some(buffer_id) = buffer_id {
17422 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17423 }
17424
17425 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17426 cx.background_executor().timer(debounce).await;
17427
17428 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17429 buffers
17430 .into_iter()
17431 .filter_map(|buffer| {
17432 project
17433 .update(cx, |project, cx| {
17434 project.lsp_store().update(cx, |lsp_store, cx| {
17435 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17436 })
17437 })
17438 .ok()
17439 })
17440 .collect::<FuturesUnordered<_>>()
17441 }) else {
17442 return;
17443 };
17444
17445 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17446 match pull_task {
17447 Ok(()) => {
17448 if editor
17449 .update_in(cx, |editor, window, cx| {
17450 editor.update_diagnostics_state(window, cx);
17451 })
17452 .is_err()
17453 {
17454 return;
17455 }
17456 }
17457 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17458 }
17459 }
17460 });
17461
17462 Some(())
17463 }
17464
17465 pub fn set_selections_from_remote(
17466 &mut self,
17467 selections: Vec<Selection<Anchor>>,
17468 pending_selection: Option<Selection<Anchor>>,
17469 window: &mut Window,
17470 cx: &mut Context<Self>,
17471 ) {
17472 let old_cursor_position = self.selections.newest_anchor().head();
17473 self.selections.change_with(cx, |s| {
17474 s.select_anchors(selections);
17475 if let Some(pending_selection) = pending_selection {
17476 s.set_pending(pending_selection, SelectMode::Character);
17477 } else {
17478 s.clear_pending();
17479 }
17480 });
17481 self.selections_did_change(
17482 false,
17483 &old_cursor_position,
17484 SelectionEffects::default(),
17485 window,
17486 cx,
17487 );
17488 }
17489
17490 pub fn transact(
17491 &mut self,
17492 window: &mut Window,
17493 cx: &mut Context<Self>,
17494 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17495 ) -> Option<TransactionId> {
17496 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17497 this.start_transaction_at(Instant::now(), window, cx);
17498 update(this, window, cx);
17499 this.end_transaction_at(Instant::now(), cx)
17500 })
17501 }
17502
17503 pub fn start_transaction_at(
17504 &mut self,
17505 now: Instant,
17506 window: &mut Window,
17507 cx: &mut Context<Self>,
17508 ) -> Option<TransactionId> {
17509 self.end_selection(window, cx);
17510 if let Some(tx_id) = self
17511 .buffer
17512 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17513 {
17514 self.selection_history
17515 .insert_transaction(tx_id, self.selections.disjoint_anchors());
17516 cx.emit(EditorEvent::TransactionBegun {
17517 transaction_id: tx_id,
17518 });
17519 Some(tx_id)
17520 } else {
17521 None
17522 }
17523 }
17524
17525 pub fn end_transaction_at(
17526 &mut self,
17527 now: Instant,
17528 cx: &mut Context<Self>,
17529 ) -> Option<TransactionId> {
17530 if let Some(transaction_id) = self
17531 .buffer
17532 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17533 {
17534 if let Some((_, end_selections)) =
17535 self.selection_history.transaction_mut(transaction_id)
17536 {
17537 *end_selections = Some(self.selections.disjoint_anchors());
17538 } else {
17539 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17540 }
17541
17542 cx.emit(EditorEvent::Edited { transaction_id });
17543 Some(transaction_id)
17544 } else {
17545 None
17546 }
17547 }
17548
17549 pub fn modify_transaction_selection_history(
17550 &mut self,
17551 transaction_id: TransactionId,
17552 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17553 ) -> bool {
17554 self.selection_history
17555 .transaction_mut(transaction_id)
17556 .map(modify)
17557 .is_some()
17558 }
17559
17560 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17561 if self.selection_mark_mode {
17562 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17563 s.move_with(|_, sel| {
17564 sel.collapse_to(sel.head(), SelectionGoal::None);
17565 });
17566 })
17567 }
17568 self.selection_mark_mode = true;
17569 cx.notify();
17570 }
17571
17572 pub fn swap_selection_ends(
17573 &mut self,
17574 _: &actions::SwapSelectionEnds,
17575 window: &mut Window,
17576 cx: &mut Context<Self>,
17577 ) {
17578 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17579 s.move_with(|_, sel| {
17580 if sel.start != sel.end {
17581 sel.reversed = !sel.reversed
17582 }
17583 });
17584 });
17585 self.request_autoscroll(Autoscroll::newest(), cx);
17586 cx.notify();
17587 }
17588
17589 pub fn toggle_focus(
17590 workspace: &mut Workspace,
17591 _: &actions::ToggleFocus,
17592 window: &mut Window,
17593 cx: &mut Context<Workspace>,
17594 ) {
17595 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17596 return;
17597 };
17598 workspace.activate_item(&item, true, true, window, cx);
17599 }
17600
17601 pub fn toggle_fold(
17602 &mut self,
17603 _: &actions::ToggleFold,
17604 window: &mut Window,
17605 cx: &mut Context<Self>,
17606 ) {
17607 if self.is_singleton(cx) {
17608 let selection = self.selections.newest::<Point>(cx);
17609
17610 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17611 let range = if selection.is_empty() {
17612 let point = selection.head().to_display_point(&display_map);
17613 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17614 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17615 .to_point(&display_map);
17616 start..end
17617 } else {
17618 selection.range()
17619 };
17620 if display_map.folds_in_range(range).next().is_some() {
17621 self.unfold_lines(&Default::default(), window, cx)
17622 } else {
17623 self.fold(&Default::default(), window, cx)
17624 }
17625 } else {
17626 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17627 let buffer_ids: HashSet<_> = self
17628 .selections
17629 .disjoint_anchor_ranges()
17630 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17631 .collect();
17632
17633 let should_unfold = buffer_ids
17634 .iter()
17635 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17636
17637 for buffer_id in buffer_ids {
17638 if should_unfold {
17639 self.unfold_buffer(buffer_id, cx);
17640 } else {
17641 self.fold_buffer(buffer_id, cx);
17642 }
17643 }
17644 }
17645 }
17646
17647 pub fn toggle_fold_recursive(
17648 &mut self,
17649 _: &actions::ToggleFoldRecursive,
17650 window: &mut Window,
17651 cx: &mut Context<Self>,
17652 ) {
17653 let selection = self.selections.newest::<Point>(cx);
17654
17655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17656 let range = if selection.is_empty() {
17657 let point = selection.head().to_display_point(&display_map);
17658 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17659 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17660 .to_point(&display_map);
17661 start..end
17662 } else {
17663 selection.range()
17664 };
17665 if display_map.folds_in_range(range).next().is_some() {
17666 self.unfold_recursive(&Default::default(), window, cx)
17667 } else {
17668 self.fold_recursive(&Default::default(), window, cx)
17669 }
17670 }
17671
17672 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17673 if self.is_singleton(cx) {
17674 let mut to_fold = Vec::new();
17675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17676 let selections = self.selections.all_adjusted(cx);
17677
17678 for selection in selections {
17679 let range = selection.range().sorted();
17680 let buffer_start_row = range.start.row;
17681
17682 if range.start.row != range.end.row {
17683 let mut found = false;
17684 let mut row = range.start.row;
17685 while row <= range.end.row {
17686 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17687 {
17688 found = true;
17689 row = crease.range().end.row + 1;
17690 to_fold.push(crease);
17691 } else {
17692 row += 1
17693 }
17694 }
17695 if found {
17696 continue;
17697 }
17698 }
17699
17700 for row in (0..=range.start.row).rev() {
17701 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17702 && crease.range().end.row >= buffer_start_row
17703 {
17704 to_fold.push(crease);
17705 if row <= range.start.row {
17706 break;
17707 }
17708 }
17709 }
17710 }
17711
17712 self.fold_creases(to_fold, true, window, cx);
17713 } else {
17714 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17715 let buffer_ids = self
17716 .selections
17717 .disjoint_anchor_ranges()
17718 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17719 .collect::<HashSet<_>>();
17720 for buffer_id in buffer_ids {
17721 self.fold_buffer(buffer_id, cx);
17722 }
17723 }
17724 }
17725
17726 pub fn toggle_fold_all(
17727 &mut self,
17728 _: &actions::ToggleFoldAll,
17729 window: &mut Window,
17730 cx: &mut Context<Self>,
17731 ) {
17732 if self.buffer.read(cx).is_singleton() {
17733 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17734 let has_folds = display_map
17735 .folds_in_range(0..display_map.buffer_snapshot.len())
17736 .next()
17737 .is_some();
17738
17739 if has_folds {
17740 self.unfold_all(&actions::UnfoldAll, window, cx);
17741 } else {
17742 self.fold_all(&actions::FoldAll, window, cx);
17743 }
17744 } else {
17745 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17746 let should_unfold = buffer_ids
17747 .iter()
17748 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17749
17750 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17751 editor
17752 .update_in(cx, |editor, _, cx| {
17753 for buffer_id in buffer_ids {
17754 if should_unfold {
17755 editor.unfold_buffer(buffer_id, cx);
17756 } else {
17757 editor.fold_buffer(buffer_id, cx);
17758 }
17759 }
17760 })
17761 .ok();
17762 });
17763 }
17764 }
17765
17766 fn fold_at_level(
17767 &mut self,
17768 fold_at: &FoldAtLevel,
17769 window: &mut Window,
17770 cx: &mut Context<Self>,
17771 ) {
17772 if !self.buffer.read(cx).is_singleton() {
17773 return;
17774 }
17775
17776 let fold_at_level = fold_at.0;
17777 let snapshot = self.buffer.read(cx).snapshot(cx);
17778 let mut to_fold = Vec::new();
17779 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17780
17781 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17782 while start_row < end_row {
17783 match self
17784 .snapshot(window, cx)
17785 .crease_for_buffer_row(MultiBufferRow(start_row))
17786 {
17787 Some(crease) => {
17788 let nested_start_row = crease.range().start.row + 1;
17789 let nested_end_row = crease.range().end.row;
17790
17791 if current_level < fold_at_level {
17792 stack.push((nested_start_row, nested_end_row, current_level + 1));
17793 } else if current_level == fold_at_level {
17794 to_fold.push(crease);
17795 }
17796
17797 start_row = nested_end_row + 1;
17798 }
17799 None => start_row += 1,
17800 }
17801 }
17802 }
17803
17804 self.fold_creases(to_fold, true, window, cx);
17805 }
17806
17807 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
17808 if self.buffer.read(cx).is_singleton() {
17809 let mut fold_ranges = Vec::new();
17810 let snapshot = self.buffer.read(cx).snapshot(cx);
17811
17812 for row in 0..snapshot.max_row().0 {
17813 if let Some(foldable_range) = self
17814 .snapshot(window, cx)
17815 .crease_for_buffer_row(MultiBufferRow(row))
17816 {
17817 fold_ranges.push(foldable_range);
17818 }
17819 }
17820
17821 self.fold_creases(fold_ranges, true, window, cx);
17822 } else {
17823 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17824 editor
17825 .update_in(cx, |editor, _, cx| {
17826 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17827 editor.fold_buffer(buffer_id, cx);
17828 }
17829 })
17830 .ok();
17831 });
17832 }
17833 }
17834
17835 pub fn fold_function_bodies(
17836 &mut self,
17837 _: &actions::FoldFunctionBodies,
17838 window: &mut Window,
17839 cx: &mut Context<Self>,
17840 ) {
17841 let snapshot = self.buffer.read(cx).snapshot(cx);
17842
17843 let ranges = snapshot
17844 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17845 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17846 .collect::<Vec<_>>();
17847
17848 let creases = ranges
17849 .into_iter()
17850 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17851 .collect();
17852
17853 self.fold_creases(creases, true, window, cx);
17854 }
17855
17856 pub fn fold_recursive(
17857 &mut self,
17858 _: &actions::FoldRecursive,
17859 window: &mut Window,
17860 cx: &mut Context<Self>,
17861 ) {
17862 let mut to_fold = Vec::new();
17863 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17864 let selections = self.selections.all_adjusted(cx);
17865
17866 for selection in selections {
17867 let range = selection.range().sorted();
17868 let buffer_start_row = range.start.row;
17869
17870 if range.start.row != range.end.row {
17871 let mut found = false;
17872 for row in range.start.row..=range.end.row {
17873 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17874 found = true;
17875 to_fold.push(crease);
17876 }
17877 }
17878 if found {
17879 continue;
17880 }
17881 }
17882
17883 for row in (0..=range.start.row).rev() {
17884 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17885 if crease.range().end.row >= buffer_start_row {
17886 to_fold.push(crease);
17887 } else {
17888 break;
17889 }
17890 }
17891 }
17892 }
17893
17894 self.fold_creases(to_fold, true, window, cx);
17895 }
17896
17897 pub fn fold_at(
17898 &mut self,
17899 buffer_row: MultiBufferRow,
17900 window: &mut Window,
17901 cx: &mut Context<Self>,
17902 ) {
17903 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17904
17905 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17906 let autoscroll = self
17907 .selections
17908 .all::<Point>(cx)
17909 .iter()
17910 .any(|selection| crease.range().overlaps(&selection.range()));
17911
17912 self.fold_creases(vec![crease], autoscroll, window, cx);
17913 }
17914 }
17915
17916 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17917 if self.is_singleton(cx) {
17918 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17919 let buffer = &display_map.buffer_snapshot;
17920 let selections = self.selections.all::<Point>(cx);
17921 let ranges = selections
17922 .iter()
17923 .map(|s| {
17924 let range = s.display_range(&display_map).sorted();
17925 let mut start = range.start.to_point(&display_map);
17926 let mut end = range.end.to_point(&display_map);
17927 start.column = 0;
17928 end.column = buffer.line_len(MultiBufferRow(end.row));
17929 start..end
17930 })
17931 .collect::<Vec<_>>();
17932
17933 self.unfold_ranges(&ranges, true, true, cx);
17934 } else {
17935 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17936 let buffer_ids = self
17937 .selections
17938 .disjoint_anchor_ranges()
17939 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17940 .collect::<HashSet<_>>();
17941 for buffer_id in buffer_ids {
17942 self.unfold_buffer(buffer_id, cx);
17943 }
17944 }
17945 }
17946
17947 pub fn unfold_recursive(
17948 &mut self,
17949 _: &UnfoldRecursive,
17950 _window: &mut Window,
17951 cx: &mut Context<Self>,
17952 ) {
17953 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17954 let selections = self.selections.all::<Point>(cx);
17955 let ranges = selections
17956 .iter()
17957 .map(|s| {
17958 let mut range = s.display_range(&display_map).sorted();
17959 *range.start.column_mut() = 0;
17960 *range.end.column_mut() = display_map.line_len(range.end.row());
17961 let start = range.start.to_point(&display_map);
17962 let end = range.end.to_point(&display_map);
17963 start..end
17964 })
17965 .collect::<Vec<_>>();
17966
17967 self.unfold_ranges(&ranges, true, true, cx);
17968 }
17969
17970 pub fn unfold_at(
17971 &mut self,
17972 buffer_row: MultiBufferRow,
17973 _window: &mut Window,
17974 cx: &mut Context<Self>,
17975 ) {
17976 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17977
17978 let intersection_range = Point::new(buffer_row.0, 0)
17979 ..Point::new(
17980 buffer_row.0,
17981 display_map.buffer_snapshot.line_len(buffer_row),
17982 );
17983
17984 let autoscroll = self
17985 .selections
17986 .all::<Point>(cx)
17987 .iter()
17988 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17989
17990 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17991 }
17992
17993 pub fn unfold_all(
17994 &mut self,
17995 _: &actions::UnfoldAll,
17996 _window: &mut Window,
17997 cx: &mut Context<Self>,
17998 ) {
17999 if self.buffer.read(cx).is_singleton() {
18000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18001 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18002 } else {
18003 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18004 editor
18005 .update(cx, |editor, cx| {
18006 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18007 editor.unfold_buffer(buffer_id, cx);
18008 }
18009 })
18010 .ok();
18011 });
18012 }
18013 }
18014
18015 pub fn fold_selected_ranges(
18016 &mut self,
18017 _: &FoldSelectedRanges,
18018 window: &mut Window,
18019 cx: &mut Context<Self>,
18020 ) {
18021 let selections = self.selections.all_adjusted(cx);
18022 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18023 let ranges = selections
18024 .into_iter()
18025 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18026 .collect::<Vec<_>>();
18027 self.fold_creases(ranges, true, window, cx);
18028 }
18029
18030 pub fn fold_ranges<T: ToOffset + Clone>(
18031 &mut self,
18032 ranges: Vec<Range<T>>,
18033 auto_scroll: bool,
18034 window: &mut Window,
18035 cx: &mut Context<Self>,
18036 ) {
18037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18038 let ranges = ranges
18039 .into_iter()
18040 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18041 .collect::<Vec<_>>();
18042 self.fold_creases(ranges, auto_scroll, window, cx);
18043 }
18044
18045 pub fn fold_creases<T: ToOffset + Clone>(
18046 &mut self,
18047 creases: Vec<Crease<T>>,
18048 auto_scroll: bool,
18049 _window: &mut Window,
18050 cx: &mut Context<Self>,
18051 ) {
18052 if creases.is_empty() {
18053 return;
18054 }
18055
18056 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18057
18058 if auto_scroll {
18059 self.request_autoscroll(Autoscroll::fit(), cx);
18060 }
18061
18062 cx.notify();
18063
18064 self.scrollbar_marker_state.dirty = true;
18065 self.folds_did_change(cx);
18066 }
18067
18068 /// Removes any folds whose ranges intersect any of the given ranges.
18069 pub fn unfold_ranges<T: ToOffset + Clone>(
18070 &mut self,
18071 ranges: &[Range<T>],
18072 inclusive: bool,
18073 auto_scroll: bool,
18074 cx: &mut Context<Self>,
18075 ) {
18076 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18077 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18078 });
18079 self.folds_did_change(cx);
18080 }
18081
18082 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18083 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18084 return;
18085 }
18086 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18087 self.display_map.update(cx, |display_map, cx| {
18088 display_map.fold_buffers([buffer_id], cx)
18089 });
18090 cx.emit(EditorEvent::BufferFoldToggled {
18091 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18092 folded: true,
18093 });
18094 cx.notify();
18095 }
18096
18097 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18098 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18099 return;
18100 }
18101 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18102 self.display_map.update(cx, |display_map, cx| {
18103 display_map.unfold_buffers([buffer_id], cx);
18104 });
18105 cx.emit(EditorEvent::BufferFoldToggled {
18106 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18107 folded: false,
18108 });
18109 cx.notify();
18110 }
18111
18112 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18113 self.display_map.read(cx).is_buffer_folded(buffer)
18114 }
18115
18116 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18117 self.display_map.read(cx).folded_buffers()
18118 }
18119
18120 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18121 self.display_map.update(cx, |display_map, cx| {
18122 display_map.disable_header_for_buffer(buffer_id, cx);
18123 });
18124 cx.notify();
18125 }
18126
18127 /// Removes any folds with the given ranges.
18128 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18129 &mut self,
18130 ranges: &[Range<T>],
18131 type_id: TypeId,
18132 auto_scroll: bool,
18133 cx: &mut Context<Self>,
18134 ) {
18135 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18136 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18137 });
18138 self.folds_did_change(cx);
18139 }
18140
18141 fn remove_folds_with<T: ToOffset + Clone>(
18142 &mut self,
18143 ranges: &[Range<T>],
18144 auto_scroll: bool,
18145 cx: &mut Context<Self>,
18146 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18147 ) {
18148 if ranges.is_empty() {
18149 return;
18150 }
18151
18152 let mut buffers_affected = HashSet::default();
18153 let multi_buffer = self.buffer().read(cx);
18154 for range in ranges {
18155 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18156 buffers_affected.insert(buffer.read(cx).remote_id());
18157 };
18158 }
18159
18160 self.display_map.update(cx, update);
18161
18162 if auto_scroll {
18163 self.request_autoscroll(Autoscroll::fit(), cx);
18164 }
18165
18166 cx.notify();
18167 self.scrollbar_marker_state.dirty = true;
18168 self.active_indent_guides_state.dirty = true;
18169 }
18170
18171 pub fn update_renderer_widths(
18172 &mut self,
18173 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18174 cx: &mut Context<Self>,
18175 ) -> bool {
18176 self.display_map
18177 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18178 }
18179
18180 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18181 self.display_map.read(cx).fold_placeholder.clone()
18182 }
18183
18184 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18185 self.buffer.update(cx, |buffer, cx| {
18186 buffer.set_all_diff_hunks_expanded(cx);
18187 });
18188 }
18189
18190 pub fn expand_all_diff_hunks(
18191 &mut self,
18192 _: &ExpandAllDiffHunks,
18193 _window: &mut Window,
18194 cx: &mut Context<Self>,
18195 ) {
18196 self.buffer.update(cx, |buffer, cx| {
18197 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18198 });
18199 }
18200
18201 pub fn toggle_selected_diff_hunks(
18202 &mut self,
18203 _: &ToggleSelectedDiffHunks,
18204 _window: &mut Window,
18205 cx: &mut Context<Self>,
18206 ) {
18207 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18208 self.toggle_diff_hunks_in_ranges(ranges, cx);
18209 }
18210
18211 pub fn diff_hunks_in_ranges<'a>(
18212 &'a self,
18213 ranges: &'a [Range<Anchor>],
18214 buffer: &'a MultiBufferSnapshot,
18215 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18216 ranges.iter().flat_map(move |range| {
18217 let end_excerpt_id = range.end.excerpt_id;
18218 let range = range.to_point(buffer);
18219 let mut peek_end = range.end;
18220 if range.end.row < buffer.max_row().0 {
18221 peek_end = Point::new(range.end.row + 1, 0);
18222 }
18223 buffer
18224 .diff_hunks_in_range(range.start..peek_end)
18225 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18226 })
18227 }
18228
18229 pub fn has_stageable_diff_hunks_in_ranges(
18230 &self,
18231 ranges: &[Range<Anchor>],
18232 snapshot: &MultiBufferSnapshot,
18233 ) -> bool {
18234 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18235 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18236 }
18237
18238 pub fn toggle_staged_selected_diff_hunks(
18239 &mut self,
18240 _: &::git::ToggleStaged,
18241 _: &mut Window,
18242 cx: &mut Context<Self>,
18243 ) {
18244 let snapshot = self.buffer.read(cx).snapshot(cx);
18245 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18246 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18247 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18248 }
18249
18250 pub fn set_render_diff_hunk_controls(
18251 &mut self,
18252 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18253 cx: &mut Context<Self>,
18254 ) {
18255 self.render_diff_hunk_controls = render_diff_hunk_controls;
18256 cx.notify();
18257 }
18258
18259 pub fn stage_and_next(
18260 &mut self,
18261 _: &::git::StageAndNext,
18262 window: &mut Window,
18263 cx: &mut Context<Self>,
18264 ) {
18265 self.do_stage_or_unstage_and_next(true, window, cx);
18266 }
18267
18268 pub fn unstage_and_next(
18269 &mut self,
18270 _: &::git::UnstageAndNext,
18271 window: &mut Window,
18272 cx: &mut Context<Self>,
18273 ) {
18274 self.do_stage_or_unstage_and_next(false, window, cx);
18275 }
18276
18277 pub fn stage_or_unstage_diff_hunks(
18278 &mut self,
18279 stage: bool,
18280 ranges: Vec<Range<Anchor>>,
18281 cx: &mut Context<Self>,
18282 ) {
18283 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18284 cx.spawn(async move |this, cx| {
18285 task.await?;
18286 this.update(cx, |this, cx| {
18287 let snapshot = this.buffer.read(cx).snapshot(cx);
18288 let chunk_by = this
18289 .diff_hunks_in_ranges(&ranges, &snapshot)
18290 .chunk_by(|hunk| hunk.buffer_id);
18291 for (buffer_id, hunks) in &chunk_by {
18292 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18293 }
18294 })
18295 })
18296 .detach_and_log_err(cx);
18297 }
18298
18299 fn save_buffers_for_ranges_if_needed(
18300 &mut self,
18301 ranges: &[Range<Anchor>],
18302 cx: &mut Context<Editor>,
18303 ) -> Task<Result<()>> {
18304 let multibuffer = self.buffer.read(cx);
18305 let snapshot = multibuffer.read(cx);
18306 let buffer_ids: HashSet<_> = ranges
18307 .iter()
18308 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18309 .collect();
18310 drop(snapshot);
18311
18312 let mut buffers = HashSet::default();
18313 for buffer_id in buffer_ids {
18314 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18315 let buffer = buffer_entity.read(cx);
18316 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18317 {
18318 buffers.insert(buffer_entity);
18319 }
18320 }
18321 }
18322
18323 if let Some(project) = &self.project {
18324 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18325 } else {
18326 Task::ready(Ok(()))
18327 }
18328 }
18329
18330 fn do_stage_or_unstage_and_next(
18331 &mut self,
18332 stage: bool,
18333 window: &mut Window,
18334 cx: &mut Context<Self>,
18335 ) {
18336 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18337
18338 if ranges.iter().any(|range| range.start != range.end) {
18339 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18340 return;
18341 }
18342
18343 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18344 let snapshot = self.snapshot(window, cx);
18345 let position = self.selections.newest::<Point>(cx).head();
18346 let mut row = snapshot
18347 .buffer_snapshot
18348 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18349 .find(|hunk| hunk.row_range.start.0 > position.row)
18350 .map(|hunk| hunk.row_range.start);
18351
18352 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18353 // Outside of the project diff editor, wrap around to the beginning.
18354 if !all_diff_hunks_expanded {
18355 row = row.or_else(|| {
18356 snapshot
18357 .buffer_snapshot
18358 .diff_hunks_in_range(Point::zero()..position)
18359 .find(|hunk| hunk.row_range.end.0 < position.row)
18360 .map(|hunk| hunk.row_range.start)
18361 });
18362 }
18363
18364 if let Some(row) = row {
18365 let destination = Point::new(row.0, 0);
18366 let autoscroll = Autoscroll::center();
18367
18368 self.unfold_ranges(&[destination..destination], false, false, cx);
18369 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18370 s.select_ranges([destination..destination]);
18371 });
18372 }
18373 }
18374
18375 fn do_stage_or_unstage(
18376 &self,
18377 stage: bool,
18378 buffer_id: BufferId,
18379 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18380 cx: &mut App,
18381 ) -> Option<()> {
18382 let project = self.project()?;
18383 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18384 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18385 let buffer_snapshot = buffer.read(cx).snapshot();
18386 let file_exists = buffer_snapshot
18387 .file()
18388 .is_some_and(|file| file.disk_state().exists());
18389 diff.update(cx, |diff, cx| {
18390 diff.stage_or_unstage_hunks(
18391 stage,
18392 &hunks
18393 .map(|hunk| buffer_diff::DiffHunk {
18394 buffer_range: hunk.buffer_range,
18395 diff_base_byte_range: hunk.diff_base_byte_range,
18396 secondary_status: hunk.secondary_status,
18397 range: Point::zero()..Point::zero(), // unused
18398 })
18399 .collect::<Vec<_>>(),
18400 &buffer_snapshot,
18401 file_exists,
18402 cx,
18403 )
18404 });
18405 None
18406 }
18407
18408 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18409 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
18410 self.buffer
18411 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18412 }
18413
18414 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18415 self.buffer.update(cx, |buffer, cx| {
18416 let ranges = vec![Anchor::min()..Anchor::max()];
18417 if !buffer.all_diff_hunks_expanded()
18418 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18419 {
18420 buffer.collapse_diff_hunks(ranges, cx);
18421 true
18422 } else {
18423 false
18424 }
18425 })
18426 }
18427
18428 fn toggle_diff_hunks_in_ranges(
18429 &mut self,
18430 ranges: Vec<Range<Anchor>>,
18431 cx: &mut Context<Editor>,
18432 ) {
18433 self.buffer.update(cx, |buffer, cx| {
18434 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18435 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18436 })
18437 }
18438
18439 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18440 self.buffer.update(cx, |buffer, cx| {
18441 let snapshot = buffer.snapshot(cx);
18442 let excerpt_id = range.end.excerpt_id;
18443 let point_range = range.to_point(&snapshot);
18444 let expand = !buffer.single_hunk_is_expanded(range, cx);
18445 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18446 })
18447 }
18448
18449 pub(crate) fn apply_all_diff_hunks(
18450 &mut self,
18451 _: &ApplyAllDiffHunks,
18452 window: &mut Window,
18453 cx: &mut Context<Self>,
18454 ) {
18455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18456
18457 let buffers = self.buffer.read(cx).all_buffers();
18458 for branch_buffer in buffers {
18459 branch_buffer.update(cx, |branch_buffer, cx| {
18460 branch_buffer.merge_into_base(Vec::new(), cx);
18461 });
18462 }
18463
18464 if let Some(project) = self.project.clone() {
18465 self.save(
18466 SaveOptions {
18467 format: true,
18468 autosave: false,
18469 },
18470 project,
18471 window,
18472 cx,
18473 )
18474 .detach_and_log_err(cx);
18475 }
18476 }
18477
18478 pub(crate) fn apply_selected_diff_hunks(
18479 &mut self,
18480 _: &ApplyDiffHunk,
18481 window: &mut Window,
18482 cx: &mut Context<Self>,
18483 ) {
18484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18485 let snapshot = self.snapshot(window, cx);
18486 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18487 let mut ranges_by_buffer = HashMap::default();
18488 self.transact(window, cx, |editor, _window, cx| {
18489 for hunk in hunks {
18490 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18491 ranges_by_buffer
18492 .entry(buffer.clone())
18493 .or_insert_with(Vec::new)
18494 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18495 }
18496 }
18497
18498 for (buffer, ranges) in ranges_by_buffer {
18499 buffer.update(cx, |buffer, cx| {
18500 buffer.merge_into_base(ranges, cx);
18501 });
18502 }
18503 });
18504
18505 if let Some(project) = self.project.clone() {
18506 self.save(
18507 SaveOptions {
18508 format: true,
18509 autosave: false,
18510 },
18511 project,
18512 window,
18513 cx,
18514 )
18515 .detach_and_log_err(cx);
18516 }
18517 }
18518
18519 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18520 if hovered != self.gutter_hovered {
18521 self.gutter_hovered = hovered;
18522 cx.notify();
18523 }
18524 }
18525
18526 pub fn insert_blocks(
18527 &mut self,
18528 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18529 autoscroll: Option<Autoscroll>,
18530 cx: &mut Context<Self>,
18531 ) -> Vec<CustomBlockId> {
18532 let blocks = self
18533 .display_map
18534 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18535 if let Some(autoscroll) = autoscroll {
18536 self.request_autoscroll(autoscroll, cx);
18537 }
18538 cx.notify();
18539 blocks
18540 }
18541
18542 pub fn resize_blocks(
18543 &mut self,
18544 heights: HashMap<CustomBlockId, u32>,
18545 autoscroll: Option<Autoscroll>,
18546 cx: &mut Context<Self>,
18547 ) {
18548 self.display_map
18549 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18550 if let Some(autoscroll) = autoscroll {
18551 self.request_autoscroll(autoscroll, cx);
18552 }
18553 cx.notify();
18554 }
18555
18556 pub fn replace_blocks(
18557 &mut self,
18558 renderers: HashMap<CustomBlockId, RenderBlock>,
18559 autoscroll: Option<Autoscroll>,
18560 cx: &mut Context<Self>,
18561 ) {
18562 self.display_map
18563 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18564 if let Some(autoscroll) = autoscroll {
18565 self.request_autoscroll(autoscroll, cx);
18566 }
18567 cx.notify();
18568 }
18569
18570 pub fn remove_blocks(
18571 &mut self,
18572 block_ids: HashSet<CustomBlockId>,
18573 autoscroll: Option<Autoscroll>,
18574 cx: &mut Context<Self>,
18575 ) {
18576 self.display_map.update(cx, |display_map, cx| {
18577 display_map.remove_blocks(block_ids, cx)
18578 });
18579 if let Some(autoscroll) = autoscroll {
18580 self.request_autoscroll(autoscroll, cx);
18581 }
18582 cx.notify();
18583 }
18584
18585 pub fn row_for_block(
18586 &self,
18587 block_id: CustomBlockId,
18588 cx: &mut Context<Self>,
18589 ) -> Option<DisplayRow> {
18590 self.display_map
18591 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18592 }
18593
18594 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18595 self.focused_block = Some(focused_block);
18596 }
18597
18598 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18599 self.focused_block.take()
18600 }
18601
18602 pub fn insert_creases(
18603 &mut self,
18604 creases: impl IntoIterator<Item = Crease<Anchor>>,
18605 cx: &mut Context<Self>,
18606 ) -> Vec<CreaseId> {
18607 self.display_map
18608 .update(cx, |map, cx| map.insert_creases(creases, cx))
18609 }
18610
18611 pub fn remove_creases(
18612 &mut self,
18613 ids: impl IntoIterator<Item = CreaseId>,
18614 cx: &mut Context<Self>,
18615 ) -> Vec<(CreaseId, Range<Anchor>)> {
18616 self.display_map
18617 .update(cx, |map, cx| map.remove_creases(ids, cx))
18618 }
18619
18620 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18621 self.display_map
18622 .update(cx, |map, cx| map.snapshot(cx))
18623 .longest_row()
18624 }
18625
18626 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18627 self.display_map
18628 .update(cx, |map, cx| map.snapshot(cx))
18629 .max_point()
18630 }
18631
18632 pub fn text(&self, cx: &App) -> String {
18633 self.buffer.read(cx).read(cx).text()
18634 }
18635
18636 pub fn is_empty(&self, cx: &App) -> bool {
18637 self.buffer.read(cx).read(cx).is_empty()
18638 }
18639
18640 pub fn text_option(&self, cx: &App) -> Option<String> {
18641 let text = self.text(cx);
18642 let text = text.trim();
18643
18644 if text.is_empty() {
18645 return None;
18646 }
18647
18648 Some(text.to_string())
18649 }
18650
18651 pub fn set_text(
18652 &mut self,
18653 text: impl Into<Arc<str>>,
18654 window: &mut Window,
18655 cx: &mut Context<Self>,
18656 ) {
18657 self.transact(window, cx, |this, _, cx| {
18658 this.buffer
18659 .read(cx)
18660 .as_singleton()
18661 .expect("you can only call set_text on editors for singleton buffers")
18662 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18663 });
18664 }
18665
18666 pub fn display_text(&self, cx: &mut App) -> String {
18667 self.display_map
18668 .update(cx, |map, cx| map.snapshot(cx))
18669 .text()
18670 }
18671
18672 fn create_minimap(
18673 &self,
18674 minimap_settings: MinimapSettings,
18675 window: &mut Window,
18676 cx: &mut Context<Self>,
18677 ) -> Option<Entity<Self>> {
18678 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18679 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18680 }
18681
18682 fn initialize_new_minimap(
18683 &self,
18684 minimap_settings: MinimapSettings,
18685 window: &mut Window,
18686 cx: &mut Context<Self>,
18687 ) -> Entity<Self> {
18688 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18689
18690 let mut minimap = Editor::new_internal(
18691 EditorMode::Minimap {
18692 parent: cx.weak_entity(),
18693 },
18694 self.buffer.clone(),
18695 None,
18696 Some(self.display_map.clone()),
18697 window,
18698 cx,
18699 );
18700 minimap.scroll_manager.clone_state(&self.scroll_manager);
18701 minimap.set_text_style_refinement(TextStyleRefinement {
18702 font_size: Some(MINIMAP_FONT_SIZE),
18703 font_weight: Some(MINIMAP_FONT_WEIGHT),
18704 ..Default::default()
18705 });
18706 minimap.update_minimap_configuration(minimap_settings, cx);
18707 cx.new(|_| minimap)
18708 }
18709
18710 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18711 let current_line_highlight = minimap_settings
18712 .current_line_highlight
18713 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18714 self.set_current_line_highlight(Some(current_line_highlight));
18715 }
18716
18717 pub fn minimap(&self) -> Option<&Entity<Self>> {
18718 self.minimap
18719 .as_ref()
18720 .filter(|_| self.minimap_visibility.visible())
18721 }
18722
18723 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18724 let mut wrap_guides = smallvec![];
18725
18726 if self.show_wrap_guides == Some(false) {
18727 return wrap_guides;
18728 }
18729
18730 let settings = self.buffer.read(cx).language_settings(cx);
18731 if settings.show_wrap_guides {
18732 match self.soft_wrap_mode(cx) {
18733 SoftWrap::Column(soft_wrap) => {
18734 wrap_guides.push((soft_wrap as usize, true));
18735 }
18736 SoftWrap::Bounded(soft_wrap) => {
18737 wrap_guides.push((soft_wrap as usize, true));
18738 }
18739 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18740 }
18741 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18742 }
18743
18744 wrap_guides
18745 }
18746
18747 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18748 let settings = self.buffer.read(cx).language_settings(cx);
18749 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18750 match mode {
18751 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18752 SoftWrap::None
18753 }
18754 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18755 language_settings::SoftWrap::PreferredLineLength => {
18756 SoftWrap::Column(settings.preferred_line_length)
18757 }
18758 language_settings::SoftWrap::Bounded => {
18759 SoftWrap::Bounded(settings.preferred_line_length)
18760 }
18761 }
18762 }
18763
18764 pub fn set_soft_wrap_mode(
18765 &mut self,
18766 mode: language_settings::SoftWrap,
18767
18768 cx: &mut Context<Self>,
18769 ) {
18770 self.soft_wrap_mode_override = Some(mode);
18771 cx.notify();
18772 }
18773
18774 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
18775 self.hard_wrap = hard_wrap;
18776 cx.notify();
18777 }
18778
18779 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
18780 self.text_style_refinement = Some(style);
18781 }
18782
18783 /// called by the Element so we know what style we were most recently rendered with.
18784 pub(crate) fn set_style(
18785 &mut self,
18786 style: EditorStyle,
18787 window: &mut Window,
18788 cx: &mut Context<Self>,
18789 ) {
18790 // We intentionally do not inform the display map about the minimap style
18791 // so that wrapping is not recalculated and stays consistent for the editor
18792 // and its linked minimap.
18793 if !self.mode.is_minimap() {
18794 let rem_size = window.rem_size();
18795 self.display_map.update(cx, |map, cx| {
18796 map.set_font(
18797 style.text.font(),
18798 style.text.font_size.to_pixels(rem_size),
18799 cx,
18800 )
18801 });
18802 }
18803 self.style = Some(style);
18804 }
18805
18806 pub fn style(&self) -> Option<&EditorStyle> {
18807 self.style.as_ref()
18808 }
18809
18810 // Called by the element. This method is not designed to be called outside of the editor
18811 // element's layout code because it does not notify when rewrapping is computed synchronously.
18812 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
18813 self.display_map
18814 .update(cx, |map, cx| map.set_wrap_width(width, cx))
18815 }
18816
18817 pub fn set_soft_wrap(&mut self) {
18818 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
18819 }
18820
18821 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
18822 if self.soft_wrap_mode_override.is_some() {
18823 self.soft_wrap_mode_override.take();
18824 } else {
18825 let soft_wrap = match self.soft_wrap_mode(cx) {
18826 SoftWrap::GitDiff => return,
18827 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
18828 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
18829 language_settings::SoftWrap::None
18830 }
18831 };
18832 self.soft_wrap_mode_override = Some(soft_wrap);
18833 }
18834 cx.notify();
18835 }
18836
18837 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18838 let Some(workspace) = self.workspace() else {
18839 return;
18840 };
18841 let fs = workspace.read(cx).app_state().fs.clone();
18842 let current_show = TabBarSettings::get_global(cx).show;
18843 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18844 setting.show = Some(!current_show);
18845 });
18846 }
18847
18848 pub fn toggle_indent_guides(
18849 &mut self,
18850 _: &ToggleIndentGuides,
18851 _: &mut Window,
18852 cx: &mut Context<Self>,
18853 ) {
18854 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18855 self.buffer
18856 .read(cx)
18857 .language_settings(cx)
18858 .indent_guides
18859 .enabled
18860 });
18861 self.show_indent_guides = Some(!currently_enabled);
18862 cx.notify();
18863 }
18864
18865 fn should_show_indent_guides(&self) -> Option<bool> {
18866 self.show_indent_guides
18867 }
18868
18869 pub fn toggle_line_numbers(
18870 &mut self,
18871 _: &ToggleLineNumbers,
18872 _: &mut Window,
18873 cx: &mut Context<Self>,
18874 ) {
18875 let mut editor_settings = EditorSettings::get_global(cx).clone();
18876 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18877 EditorSettings::override_global(editor_settings, cx);
18878 }
18879
18880 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18881 if let Some(show_line_numbers) = self.show_line_numbers {
18882 return show_line_numbers;
18883 }
18884 EditorSettings::get_global(cx).gutter.line_numbers
18885 }
18886
18887 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18888 self.use_relative_line_numbers
18889 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18890 }
18891
18892 pub fn toggle_relative_line_numbers(
18893 &mut self,
18894 _: &ToggleRelativeLineNumbers,
18895 _: &mut Window,
18896 cx: &mut Context<Self>,
18897 ) {
18898 let is_relative = self.should_use_relative_line_numbers(cx);
18899 self.set_relative_line_number(Some(!is_relative), cx)
18900 }
18901
18902 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18903 self.use_relative_line_numbers = is_relative;
18904 cx.notify();
18905 }
18906
18907 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18908 self.show_gutter = show_gutter;
18909 cx.notify();
18910 }
18911
18912 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18913 self.show_scrollbars = ScrollbarAxes {
18914 horizontal: show,
18915 vertical: show,
18916 };
18917 cx.notify();
18918 }
18919
18920 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18921 self.show_scrollbars.vertical = show;
18922 cx.notify();
18923 }
18924
18925 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18926 self.show_scrollbars.horizontal = show;
18927 cx.notify();
18928 }
18929
18930 pub fn set_minimap_visibility(
18931 &mut self,
18932 minimap_visibility: MinimapVisibility,
18933 window: &mut Window,
18934 cx: &mut Context<Self>,
18935 ) {
18936 if self.minimap_visibility != minimap_visibility {
18937 if minimap_visibility.visible() && self.minimap.is_none() {
18938 let minimap_settings = EditorSettings::get_global(cx).minimap;
18939 self.minimap =
18940 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18941 }
18942 self.minimap_visibility = minimap_visibility;
18943 cx.notify();
18944 }
18945 }
18946
18947 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18948 self.set_show_scrollbars(false, cx);
18949 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18950 }
18951
18952 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18953 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18954 }
18955
18956 /// Normally the text in full mode and auto height editors is padded on the
18957 /// left side by roughly half a character width for improved hit testing.
18958 ///
18959 /// Use this method to disable this for cases where this is not wanted (e.g.
18960 /// if you want to align the editor text with some other text above or below)
18961 /// or if you want to add this padding to single-line editors.
18962 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18963 self.offset_content = offset_content;
18964 cx.notify();
18965 }
18966
18967 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18968 self.show_line_numbers = Some(show_line_numbers);
18969 cx.notify();
18970 }
18971
18972 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18973 self.disable_expand_excerpt_buttons = true;
18974 cx.notify();
18975 }
18976
18977 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18978 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18979 cx.notify();
18980 }
18981
18982 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18983 self.show_code_actions = Some(show_code_actions);
18984 cx.notify();
18985 }
18986
18987 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18988 self.show_runnables = Some(show_runnables);
18989 cx.notify();
18990 }
18991
18992 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18993 self.show_breakpoints = Some(show_breakpoints);
18994 cx.notify();
18995 }
18996
18997 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18998 if self.display_map.read(cx).masked != masked {
18999 self.display_map.update(cx, |map, _| map.masked = masked);
19000 }
19001 cx.notify()
19002 }
19003
19004 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19005 self.show_wrap_guides = Some(show_wrap_guides);
19006 cx.notify();
19007 }
19008
19009 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19010 self.show_indent_guides = Some(show_indent_guides);
19011 cx.notify();
19012 }
19013
19014 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19015 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19016 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19017 && let Some(dir) = file.abs_path(cx).parent()
19018 {
19019 return Some(dir.to_owned());
19020 }
19021
19022 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19023 return Some(project_path.path.to_path_buf());
19024 }
19025 }
19026
19027 None
19028 }
19029
19030 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19031 self.active_excerpt(cx)?
19032 .1
19033 .read(cx)
19034 .file()
19035 .and_then(|f| f.as_local())
19036 }
19037
19038 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19039 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19040 let buffer = buffer.read(cx);
19041 if let Some(project_path) = buffer.project_path(cx) {
19042 let project = self.project()?.read(cx);
19043 project.absolute_path(&project_path, cx)
19044 } else {
19045 buffer
19046 .file()
19047 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19048 }
19049 })
19050 }
19051
19052 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19053 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19054 let project_path = buffer.read(cx).project_path(cx)?;
19055 let project = self.project()?.read(cx);
19056 let entry = project.entry_for_path(&project_path, cx)?;
19057 let path = entry.path.to_path_buf();
19058 Some(path)
19059 })
19060 }
19061
19062 pub fn reveal_in_finder(
19063 &mut self,
19064 _: &RevealInFileManager,
19065 _window: &mut Window,
19066 cx: &mut Context<Self>,
19067 ) {
19068 if let Some(target) = self.target_file(cx) {
19069 cx.reveal_path(&target.abs_path(cx));
19070 }
19071 }
19072
19073 pub fn copy_path(
19074 &mut self,
19075 _: &zed_actions::workspace::CopyPath,
19076 _window: &mut Window,
19077 cx: &mut Context<Self>,
19078 ) {
19079 if let Some(path) = self.target_file_abs_path(cx)
19080 && let Some(path) = path.to_str()
19081 {
19082 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19083 }
19084 }
19085
19086 pub fn copy_relative_path(
19087 &mut self,
19088 _: &zed_actions::workspace::CopyRelativePath,
19089 _window: &mut Window,
19090 cx: &mut Context<Self>,
19091 ) {
19092 if let Some(path) = self.target_file_path(cx)
19093 && let Some(path) = path.to_str()
19094 {
19095 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19096 }
19097 }
19098
19099 /// Returns the project path for the editor's buffer, if any buffer is
19100 /// opened in the editor.
19101 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19102 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19103 buffer.read(cx).project_path(cx)
19104 } else {
19105 None
19106 }
19107 }
19108
19109 // Returns true if the editor handled a go-to-line request
19110 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19111 maybe!({
19112 let breakpoint_store = self.breakpoint_store.as_ref()?;
19113
19114 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19115 else {
19116 self.clear_row_highlights::<ActiveDebugLine>();
19117 return None;
19118 };
19119
19120 let position = active_stack_frame.position;
19121 let buffer_id = position.buffer_id?;
19122 let snapshot = self
19123 .project
19124 .as_ref()?
19125 .read(cx)
19126 .buffer_for_id(buffer_id, cx)?
19127 .read(cx)
19128 .snapshot();
19129
19130 let mut handled = false;
19131 for (id, ExcerptRange { context, .. }) in
19132 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19133 {
19134 if context.start.cmp(&position, &snapshot).is_ge()
19135 || context.end.cmp(&position, &snapshot).is_lt()
19136 {
19137 continue;
19138 }
19139 let snapshot = self.buffer.read(cx).snapshot(cx);
19140 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19141
19142 handled = true;
19143 self.clear_row_highlights::<ActiveDebugLine>();
19144
19145 self.go_to_line::<ActiveDebugLine>(
19146 multibuffer_anchor,
19147 Some(cx.theme().colors().editor_debugger_active_line_background),
19148 window,
19149 cx,
19150 );
19151
19152 cx.notify();
19153 }
19154
19155 handled.then_some(())
19156 })
19157 .is_some()
19158 }
19159
19160 pub fn copy_file_name_without_extension(
19161 &mut self,
19162 _: &CopyFileNameWithoutExtension,
19163 _: &mut Window,
19164 cx: &mut Context<Self>,
19165 ) {
19166 if let Some(file) = self.target_file(cx)
19167 && let Some(file_stem) = file.path().file_stem()
19168 && let Some(name) = file_stem.to_str()
19169 {
19170 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19171 }
19172 }
19173
19174 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19175 if let Some(file) = self.target_file(cx)
19176 && let Some(file_name) = file.path().file_name()
19177 && let Some(name) = file_name.to_str()
19178 {
19179 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19180 }
19181 }
19182
19183 pub fn toggle_git_blame(
19184 &mut self,
19185 _: &::git::Blame,
19186 window: &mut Window,
19187 cx: &mut Context<Self>,
19188 ) {
19189 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19190
19191 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19192 self.start_git_blame(true, window, cx);
19193 }
19194
19195 cx.notify();
19196 }
19197
19198 pub fn toggle_git_blame_inline(
19199 &mut self,
19200 _: &ToggleGitBlameInline,
19201 window: &mut Window,
19202 cx: &mut Context<Self>,
19203 ) {
19204 self.toggle_git_blame_inline_internal(true, window, cx);
19205 cx.notify();
19206 }
19207
19208 pub fn open_git_blame_commit(
19209 &mut self,
19210 _: &OpenGitBlameCommit,
19211 window: &mut Window,
19212 cx: &mut Context<Self>,
19213 ) {
19214 self.open_git_blame_commit_internal(window, cx);
19215 }
19216
19217 fn open_git_blame_commit_internal(
19218 &mut self,
19219 window: &mut Window,
19220 cx: &mut Context<Self>,
19221 ) -> Option<()> {
19222 let blame = self.blame.as_ref()?;
19223 let snapshot = self.snapshot(window, cx);
19224 let cursor = self.selections.newest::<Point>(cx).head();
19225 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19226 let (_, blame_entry) = blame
19227 .update(cx, |blame, cx| {
19228 blame
19229 .blame_for_rows(
19230 &[RowInfo {
19231 buffer_id: Some(buffer.remote_id()),
19232 buffer_row: Some(point.row),
19233 ..Default::default()
19234 }],
19235 cx,
19236 )
19237 .next()
19238 })
19239 .flatten()?;
19240 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19241 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19242 let workspace = self.workspace()?.downgrade();
19243 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19244 None
19245 }
19246
19247 pub fn git_blame_inline_enabled(&self) -> bool {
19248 self.git_blame_inline_enabled
19249 }
19250
19251 pub fn toggle_selection_menu(
19252 &mut self,
19253 _: &ToggleSelectionMenu,
19254 _: &mut Window,
19255 cx: &mut Context<Self>,
19256 ) {
19257 self.show_selection_menu = self
19258 .show_selection_menu
19259 .map(|show_selections_menu| !show_selections_menu)
19260 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19261
19262 cx.notify();
19263 }
19264
19265 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19266 self.show_selection_menu
19267 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19268 }
19269
19270 fn start_git_blame(
19271 &mut self,
19272 user_triggered: bool,
19273 window: &mut Window,
19274 cx: &mut Context<Self>,
19275 ) {
19276 if let Some(project) = self.project() {
19277 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19278 && buffer.read(cx).file().is_none()
19279 {
19280 return;
19281 }
19282
19283 let focused = self.focus_handle(cx).contains_focused(window, cx);
19284
19285 let project = project.clone();
19286 let blame = cx
19287 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19288 self.blame_subscription =
19289 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19290 self.blame = Some(blame);
19291 }
19292 }
19293
19294 fn toggle_git_blame_inline_internal(
19295 &mut self,
19296 user_triggered: bool,
19297 window: &mut Window,
19298 cx: &mut Context<Self>,
19299 ) {
19300 if self.git_blame_inline_enabled {
19301 self.git_blame_inline_enabled = false;
19302 self.show_git_blame_inline = false;
19303 self.show_git_blame_inline_delay_task.take();
19304 } else {
19305 self.git_blame_inline_enabled = true;
19306 self.start_git_blame_inline(user_triggered, window, cx);
19307 }
19308
19309 cx.notify();
19310 }
19311
19312 fn start_git_blame_inline(
19313 &mut self,
19314 user_triggered: bool,
19315 window: &mut Window,
19316 cx: &mut Context<Self>,
19317 ) {
19318 self.start_git_blame(user_triggered, window, cx);
19319
19320 if ProjectSettings::get_global(cx)
19321 .git
19322 .inline_blame_delay()
19323 .is_some()
19324 {
19325 self.start_inline_blame_timer(window, cx);
19326 } else {
19327 self.show_git_blame_inline = true
19328 }
19329 }
19330
19331 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19332 self.blame.as_ref()
19333 }
19334
19335 pub fn show_git_blame_gutter(&self) -> bool {
19336 self.show_git_blame_gutter
19337 }
19338
19339 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19340 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19341 }
19342
19343 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19344 self.show_git_blame_inline
19345 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19346 && !self.newest_selection_head_on_empty_line(cx)
19347 && self.has_blame_entries(cx)
19348 }
19349
19350 fn has_blame_entries(&self, cx: &App) -> bool {
19351 self.blame()
19352 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19353 }
19354
19355 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19356 let cursor_anchor = self.selections.newest_anchor().head();
19357
19358 let snapshot = self.buffer.read(cx).snapshot(cx);
19359 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19360
19361 snapshot.line_len(buffer_row) == 0
19362 }
19363
19364 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19365 let buffer_and_selection = maybe!({
19366 let selection = self.selections.newest::<Point>(cx);
19367 let selection_range = selection.range();
19368
19369 let multi_buffer = self.buffer().read(cx);
19370 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19371 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19372
19373 let (buffer, range, _) = if selection.reversed {
19374 buffer_ranges.first()
19375 } else {
19376 buffer_ranges.last()
19377 }?;
19378
19379 let selection = text::ToPoint::to_point(&range.start, buffer).row
19380 ..text::ToPoint::to_point(&range.end, buffer).row;
19381 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19382 });
19383
19384 let Some((buffer, selection)) = buffer_and_selection else {
19385 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19386 };
19387
19388 let Some(project) = self.project() else {
19389 return Task::ready(Err(anyhow!("editor does not have project")));
19390 };
19391
19392 project.update(cx, |project, cx| {
19393 project.get_permalink_to_line(&buffer, selection, cx)
19394 })
19395 }
19396
19397 pub fn copy_permalink_to_line(
19398 &mut self,
19399 _: &CopyPermalinkToLine,
19400 window: &mut Window,
19401 cx: &mut Context<Self>,
19402 ) {
19403 let permalink_task = self.get_permalink_to_line(cx);
19404 let workspace = self.workspace();
19405
19406 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19407 Ok(permalink) => {
19408 cx.update(|_, cx| {
19409 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19410 })
19411 .ok();
19412 }
19413 Err(err) => {
19414 let message = format!("Failed to copy permalink: {err}");
19415
19416 anyhow::Result::<()>::Err(err).log_err();
19417
19418 if let Some(workspace) = workspace {
19419 workspace
19420 .update_in(cx, |workspace, _, cx| {
19421 struct CopyPermalinkToLine;
19422
19423 workspace.show_toast(
19424 Toast::new(
19425 NotificationId::unique::<CopyPermalinkToLine>(),
19426 message,
19427 ),
19428 cx,
19429 )
19430 })
19431 .ok();
19432 }
19433 }
19434 })
19435 .detach();
19436 }
19437
19438 pub fn copy_file_location(
19439 &mut self,
19440 _: &CopyFileLocation,
19441 _: &mut Window,
19442 cx: &mut Context<Self>,
19443 ) {
19444 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19445 if let Some(file) = self.target_file(cx)
19446 && let Some(path) = file.path().to_str()
19447 {
19448 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19449 }
19450 }
19451
19452 pub fn open_permalink_to_line(
19453 &mut self,
19454 _: &OpenPermalinkToLine,
19455 window: &mut Window,
19456 cx: &mut Context<Self>,
19457 ) {
19458 let permalink_task = self.get_permalink_to_line(cx);
19459 let workspace = self.workspace();
19460
19461 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19462 Ok(permalink) => {
19463 cx.update(|_, cx| {
19464 cx.open_url(permalink.as_ref());
19465 })
19466 .ok();
19467 }
19468 Err(err) => {
19469 let message = format!("Failed to open permalink: {err}");
19470
19471 anyhow::Result::<()>::Err(err).log_err();
19472
19473 if let Some(workspace) = workspace {
19474 workspace
19475 .update(cx, |workspace, cx| {
19476 struct OpenPermalinkToLine;
19477
19478 workspace.show_toast(
19479 Toast::new(
19480 NotificationId::unique::<OpenPermalinkToLine>(),
19481 message,
19482 ),
19483 cx,
19484 )
19485 })
19486 .ok();
19487 }
19488 }
19489 })
19490 .detach();
19491 }
19492
19493 pub fn insert_uuid_v4(
19494 &mut self,
19495 _: &InsertUuidV4,
19496 window: &mut Window,
19497 cx: &mut Context<Self>,
19498 ) {
19499 self.insert_uuid(UuidVersion::V4, window, cx);
19500 }
19501
19502 pub fn insert_uuid_v7(
19503 &mut self,
19504 _: &InsertUuidV7,
19505 window: &mut Window,
19506 cx: &mut Context<Self>,
19507 ) {
19508 self.insert_uuid(UuidVersion::V7, window, cx);
19509 }
19510
19511 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19512 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19513 self.transact(window, cx, |this, window, cx| {
19514 let edits = this
19515 .selections
19516 .all::<Point>(cx)
19517 .into_iter()
19518 .map(|selection| {
19519 let uuid = match version {
19520 UuidVersion::V4 => uuid::Uuid::new_v4(),
19521 UuidVersion::V7 => uuid::Uuid::now_v7(),
19522 };
19523
19524 (selection.range(), uuid.to_string())
19525 });
19526 this.edit(edits, cx);
19527 this.refresh_edit_prediction(true, false, window, cx);
19528 });
19529 }
19530
19531 pub fn open_selections_in_multibuffer(
19532 &mut self,
19533 _: &OpenSelectionsInMultibuffer,
19534 window: &mut Window,
19535 cx: &mut Context<Self>,
19536 ) {
19537 let multibuffer = self.buffer.read(cx);
19538
19539 let Some(buffer) = multibuffer.as_singleton() else {
19540 return;
19541 };
19542
19543 let Some(workspace) = self.workspace() else {
19544 return;
19545 };
19546
19547 let title = multibuffer.title(cx).to_string();
19548
19549 let locations = self
19550 .selections
19551 .all_anchors(cx)
19552 .iter()
19553 .map(|selection| Location {
19554 buffer: buffer.clone(),
19555 range: selection.start.text_anchor..selection.end.text_anchor,
19556 })
19557 .collect::<Vec<_>>();
19558
19559 cx.spawn_in(window, async move |_, cx| {
19560 workspace.update_in(cx, |workspace, window, cx| {
19561 Self::open_locations_in_multibuffer(
19562 workspace,
19563 locations,
19564 format!("Selections for '{title}'"),
19565 false,
19566 MultibufferSelectionMode::All,
19567 window,
19568 cx,
19569 );
19570 })
19571 })
19572 .detach();
19573 }
19574
19575 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19576 /// last highlight added will be used.
19577 ///
19578 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19579 pub fn highlight_rows<T: 'static>(
19580 &mut self,
19581 range: Range<Anchor>,
19582 color: Hsla,
19583 options: RowHighlightOptions,
19584 cx: &mut Context<Self>,
19585 ) {
19586 let snapshot = self.buffer().read(cx).snapshot(cx);
19587 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19588 let ix = row_highlights.binary_search_by(|highlight| {
19589 Ordering::Equal
19590 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19591 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19592 });
19593
19594 if let Err(mut ix) = ix {
19595 let index = post_inc(&mut self.highlight_order);
19596
19597 // If this range intersects with the preceding highlight, then merge it with
19598 // the preceding highlight. Otherwise insert a new highlight.
19599 let mut merged = false;
19600 if ix > 0 {
19601 let prev_highlight = &mut row_highlights[ix - 1];
19602 if prev_highlight
19603 .range
19604 .end
19605 .cmp(&range.start, &snapshot)
19606 .is_ge()
19607 {
19608 ix -= 1;
19609 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19610 prev_highlight.range.end = range.end;
19611 }
19612 merged = true;
19613 prev_highlight.index = index;
19614 prev_highlight.color = color;
19615 prev_highlight.options = options;
19616 }
19617 }
19618
19619 if !merged {
19620 row_highlights.insert(
19621 ix,
19622 RowHighlight {
19623 range,
19624 index,
19625 color,
19626 options,
19627 type_id: TypeId::of::<T>(),
19628 },
19629 );
19630 }
19631
19632 // If any of the following highlights intersect with this one, merge them.
19633 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19634 let highlight = &row_highlights[ix];
19635 if next_highlight
19636 .range
19637 .start
19638 .cmp(&highlight.range.end, &snapshot)
19639 .is_le()
19640 {
19641 if next_highlight
19642 .range
19643 .end
19644 .cmp(&highlight.range.end, &snapshot)
19645 .is_gt()
19646 {
19647 row_highlights[ix].range.end = next_highlight.range.end;
19648 }
19649 row_highlights.remove(ix + 1);
19650 } else {
19651 break;
19652 }
19653 }
19654 }
19655 }
19656
19657 /// Remove any highlighted row ranges of the given type that intersect the
19658 /// given ranges.
19659 pub fn remove_highlighted_rows<T: 'static>(
19660 &mut self,
19661 ranges_to_remove: Vec<Range<Anchor>>,
19662 cx: &mut Context<Self>,
19663 ) {
19664 let snapshot = self.buffer().read(cx).snapshot(cx);
19665 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19666 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19667 row_highlights.retain(|highlight| {
19668 while let Some(range_to_remove) = ranges_to_remove.peek() {
19669 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19670 Ordering::Less | Ordering::Equal => {
19671 ranges_to_remove.next();
19672 }
19673 Ordering::Greater => {
19674 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19675 Ordering::Less | Ordering::Equal => {
19676 return false;
19677 }
19678 Ordering::Greater => break,
19679 }
19680 }
19681 }
19682 }
19683
19684 true
19685 })
19686 }
19687
19688 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19689 pub fn clear_row_highlights<T: 'static>(&mut self) {
19690 self.highlighted_rows.remove(&TypeId::of::<T>());
19691 }
19692
19693 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19694 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19695 self.highlighted_rows
19696 .get(&TypeId::of::<T>())
19697 .map_or(&[] as &[_], |vec| vec.as_slice())
19698 .iter()
19699 .map(|highlight| (highlight.range.clone(), highlight.color))
19700 }
19701
19702 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19703 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19704 /// Allows to ignore certain kinds of highlights.
19705 pub fn highlighted_display_rows(
19706 &self,
19707 window: &mut Window,
19708 cx: &mut App,
19709 ) -> BTreeMap<DisplayRow, LineHighlight> {
19710 let snapshot = self.snapshot(window, cx);
19711 let mut used_highlight_orders = HashMap::default();
19712 self.highlighted_rows
19713 .iter()
19714 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19715 .fold(
19716 BTreeMap::<DisplayRow, LineHighlight>::new(),
19717 |mut unique_rows, highlight| {
19718 let start = highlight.range.start.to_display_point(&snapshot);
19719 let end = highlight.range.end.to_display_point(&snapshot);
19720 let start_row = start.row().0;
19721 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19722 && end.column() == 0
19723 {
19724 end.row().0.saturating_sub(1)
19725 } else {
19726 end.row().0
19727 };
19728 for row in start_row..=end_row {
19729 let used_index =
19730 used_highlight_orders.entry(row).or_insert(highlight.index);
19731 if highlight.index >= *used_index {
19732 *used_index = highlight.index;
19733 unique_rows.insert(
19734 DisplayRow(row),
19735 LineHighlight {
19736 include_gutter: highlight.options.include_gutter,
19737 border: None,
19738 background: highlight.color.into(),
19739 type_id: Some(highlight.type_id),
19740 },
19741 );
19742 }
19743 }
19744 unique_rows
19745 },
19746 )
19747 }
19748
19749 pub fn highlighted_display_row_for_autoscroll(
19750 &self,
19751 snapshot: &DisplaySnapshot,
19752 ) -> Option<DisplayRow> {
19753 self.highlighted_rows
19754 .values()
19755 .flat_map(|highlighted_rows| highlighted_rows.iter())
19756 .filter_map(|highlight| {
19757 if highlight.options.autoscroll {
19758 Some(highlight.range.start.to_display_point(snapshot).row())
19759 } else {
19760 None
19761 }
19762 })
19763 .min()
19764 }
19765
19766 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
19767 self.highlight_background::<SearchWithinRange>(
19768 ranges,
19769 |colors| colors.colors().editor_document_highlight_read_background,
19770 cx,
19771 )
19772 }
19773
19774 pub fn set_breadcrumb_header(&mut self, new_header: String) {
19775 self.breadcrumb_header = Some(new_header);
19776 }
19777
19778 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
19779 self.clear_background_highlights::<SearchWithinRange>(cx);
19780 }
19781
19782 pub fn highlight_background<T: 'static>(
19783 &mut self,
19784 ranges: &[Range<Anchor>],
19785 color_fetcher: fn(&Theme) -> Hsla,
19786 cx: &mut Context<Self>,
19787 ) {
19788 self.background_highlights.insert(
19789 HighlightKey::Type(TypeId::of::<T>()),
19790 (color_fetcher, Arc::from(ranges)),
19791 );
19792 self.scrollbar_marker_state.dirty = true;
19793 cx.notify();
19794 }
19795
19796 pub fn highlight_background_key<T: 'static>(
19797 &mut self,
19798 key: usize,
19799 ranges: &[Range<Anchor>],
19800 color_fetcher: fn(&Theme) -> Hsla,
19801 cx: &mut Context<Self>,
19802 ) {
19803 self.background_highlights.insert(
19804 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19805 (color_fetcher, Arc::from(ranges)),
19806 );
19807 self.scrollbar_marker_state.dirty = true;
19808 cx.notify();
19809 }
19810
19811 pub fn clear_background_highlights<T: 'static>(
19812 &mut self,
19813 cx: &mut Context<Self>,
19814 ) -> Option<BackgroundHighlight> {
19815 let text_highlights = self
19816 .background_highlights
19817 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
19818 if !text_highlights.1.is_empty() {
19819 self.scrollbar_marker_state.dirty = true;
19820 cx.notify();
19821 }
19822 Some(text_highlights)
19823 }
19824
19825 pub fn highlight_gutter<T: 'static>(
19826 &mut self,
19827 ranges: impl Into<Vec<Range<Anchor>>>,
19828 color_fetcher: fn(&App) -> Hsla,
19829 cx: &mut Context<Self>,
19830 ) {
19831 self.gutter_highlights
19832 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19833 cx.notify();
19834 }
19835
19836 pub fn clear_gutter_highlights<T: 'static>(
19837 &mut self,
19838 cx: &mut Context<Self>,
19839 ) -> Option<GutterHighlight> {
19840 cx.notify();
19841 self.gutter_highlights.remove(&TypeId::of::<T>())
19842 }
19843
19844 pub fn insert_gutter_highlight<T: 'static>(
19845 &mut self,
19846 range: Range<Anchor>,
19847 color_fetcher: fn(&App) -> Hsla,
19848 cx: &mut Context<Self>,
19849 ) {
19850 let snapshot = self.buffer().read(cx).snapshot(cx);
19851 let mut highlights = self
19852 .gutter_highlights
19853 .remove(&TypeId::of::<T>())
19854 .map(|(_, highlights)| highlights)
19855 .unwrap_or_default();
19856 let ix = highlights.binary_search_by(|highlight| {
19857 Ordering::Equal
19858 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19859 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19860 });
19861 if let Err(ix) = ix {
19862 highlights.insert(ix, range);
19863 }
19864 self.gutter_highlights
19865 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19866 }
19867
19868 pub fn remove_gutter_highlights<T: 'static>(
19869 &mut self,
19870 ranges_to_remove: Vec<Range<Anchor>>,
19871 cx: &mut Context<Self>,
19872 ) {
19873 let snapshot = self.buffer().read(cx).snapshot(cx);
19874 let Some((color_fetcher, mut gutter_highlights)) =
19875 self.gutter_highlights.remove(&TypeId::of::<T>())
19876 else {
19877 return;
19878 };
19879 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19880 gutter_highlights.retain(|highlight| {
19881 while let Some(range_to_remove) = ranges_to_remove.peek() {
19882 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19883 Ordering::Less | Ordering::Equal => {
19884 ranges_to_remove.next();
19885 }
19886 Ordering::Greater => {
19887 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19888 Ordering::Less | Ordering::Equal => {
19889 return false;
19890 }
19891 Ordering::Greater => break,
19892 }
19893 }
19894 }
19895 }
19896
19897 true
19898 });
19899 self.gutter_highlights
19900 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19901 }
19902
19903 #[cfg(feature = "test-support")]
19904 pub fn all_text_highlights(
19905 &self,
19906 window: &mut Window,
19907 cx: &mut Context<Self>,
19908 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19909 let snapshot = self.snapshot(window, cx);
19910 self.display_map.update(cx, |display_map, _| {
19911 display_map
19912 .all_text_highlights()
19913 .map(|highlight| {
19914 let (style, ranges) = highlight.as_ref();
19915 (
19916 *style,
19917 ranges
19918 .iter()
19919 .map(|range| range.clone().to_display_points(&snapshot))
19920 .collect(),
19921 )
19922 })
19923 .collect()
19924 })
19925 }
19926
19927 #[cfg(feature = "test-support")]
19928 pub fn all_text_background_highlights(
19929 &self,
19930 window: &mut Window,
19931 cx: &mut Context<Self>,
19932 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19933 let snapshot = self.snapshot(window, cx);
19934 let buffer = &snapshot.buffer_snapshot;
19935 let start = buffer.anchor_before(0);
19936 let end = buffer.anchor_after(buffer.len());
19937 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
19938 }
19939
19940 #[cfg(any(test, feature = "test-support"))]
19941 pub fn sorted_background_highlights_in_range(
19942 &self,
19943 search_range: Range<Anchor>,
19944 display_snapshot: &DisplaySnapshot,
19945 theme: &Theme,
19946 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19947 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
19948 res.sort_by(|a, b| {
19949 a.0.start
19950 .cmp(&b.0.start)
19951 .then_with(|| a.0.end.cmp(&b.0.end))
19952 .then_with(|| a.1.cmp(&b.1))
19953 });
19954 res
19955 }
19956
19957 #[cfg(feature = "test-support")]
19958 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19959 let snapshot = self.buffer().read(cx).snapshot(cx);
19960
19961 let highlights = self
19962 .background_highlights
19963 .get(&HighlightKey::Type(TypeId::of::<
19964 items::BufferSearchHighlights,
19965 >()));
19966
19967 if let Some((_color, ranges)) = highlights {
19968 ranges
19969 .iter()
19970 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19971 .collect_vec()
19972 } else {
19973 vec![]
19974 }
19975 }
19976
19977 fn document_highlights_for_position<'a>(
19978 &'a self,
19979 position: Anchor,
19980 buffer: &'a MultiBufferSnapshot,
19981 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19982 let read_highlights = self
19983 .background_highlights
19984 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19985 .map(|h| &h.1);
19986 let write_highlights = self
19987 .background_highlights
19988 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19989 .map(|h| &h.1);
19990 let left_position = position.bias_left(buffer);
19991 let right_position = position.bias_right(buffer);
19992 read_highlights
19993 .into_iter()
19994 .chain(write_highlights)
19995 .flat_map(move |ranges| {
19996 let start_ix = match ranges.binary_search_by(|probe| {
19997 let cmp = probe.end.cmp(&left_position, buffer);
19998 if cmp.is_ge() {
19999 Ordering::Greater
20000 } else {
20001 Ordering::Less
20002 }
20003 }) {
20004 Ok(i) | Err(i) => i,
20005 };
20006
20007 ranges[start_ix..]
20008 .iter()
20009 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20010 })
20011 }
20012
20013 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20014 self.background_highlights
20015 .get(&HighlightKey::Type(TypeId::of::<T>()))
20016 .is_some_and(|(_, highlights)| !highlights.is_empty())
20017 }
20018
20019 /// Returns all background highlights for a given range.
20020 ///
20021 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20022 pub fn background_highlights_in_range(
20023 &self,
20024 search_range: Range<Anchor>,
20025 display_snapshot: &DisplaySnapshot,
20026 theme: &Theme,
20027 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20028 let mut results = Vec::new();
20029 for (color_fetcher, ranges) in self.background_highlights.values() {
20030 let color = color_fetcher(theme);
20031 let start_ix = match ranges.binary_search_by(|probe| {
20032 let cmp = probe
20033 .end
20034 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20035 if cmp.is_gt() {
20036 Ordering::Greater
20037 } else {
20038 Ordering::Less
20039 }
20040 }) {
20041 Ok(i) | Err(i) => i,
20042 };
20043 for range in &ranges[start_ix..] {
20044 if range
20045 .start
20046 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20047 .is_ge()
20048 {
20049 break;
20050 }
20051
20052 let start = range.start.to_display_point(display_snapshot);
20053 let end = range.end.to_display_point(display_snapshot);
20054 results.push((start..end, color))
20055 }
20056 }
20057 results
20058 }
20059
20060 pub fn gutter_highlights_in_range(
20061 &self,
20062 search_range: Range<Anchor>,
20063 display_snapshot: &DisplaySnapshot,
20064 cx: &App,
20065 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20066 let mut results = Vec::new();
20067 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20068 let color = color_fetcher(cx);
20069 let start_ix = match ranges.binary_search_by(|probe| {
20070 let cmp = probe
20071 .end
20072 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20073 if cmp.is_gt() {
20074 Ordering::Greater
20075 } else {
20076 Ordering::Less
20077 }
20078 }) {
20079 Ok(i) | Err(i) => i,
20080 };
20081 for range in &ranges[start_ix..] {
20082 if range
20083 .start
20084 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20085 .is_ge()
20086 {
20087 break;
20088 }
20089
20090 let start = range.start.to_display_point(display_snapshot);
20091 let end = range.end.to_display_point(display_snapshot);
20092 results.push((start..end, color))
20093 }
20094 }
20095 results
20096 }
20097
20098 /// Get the text ranges corresponding to the redaction query
20099 pub fn redacted_ranges(
20100 &self,
20101 search_range: Range<Anchor>,
20102 display_snapshot: &DisplaySnapshot,
20103 cx: &App,
20104 ) -> Vec<Range<DisplayPoint>> {
20105 display_snapshot
20106 .buffer_snapshot
20107 .redacted_ranges(search_range, |file| {
20108 if let Some(file) = file {
20109 file.is_private()
20110 && EditorSettings::get(
20111 Some(SettingsLocation {
20112 worktree_id: file.worktree_id(cx),
20113 path: file.path().as_ref(),
20114 }),
20115 cx,
20116 )
20117 .redact_private_values
20118 } else {
20119 false
20120 }
20121 })
20122 .map(|range| {
20123 range.start.to_display_point(display_snapshot)
20124 ..range.end.to_display_point(display_snapshot)
20125 })
20126 .collect()
20127 }
20128
20129 pub fn highlight_text_key<T: 'static>(
20130 &mut self,
20131 key: usize,
20132 ranges: Vec<Range<Anchor>>,
20133 style: HighlightStyle,
20134 cx: &mut Context<Self>,
20135 ) {
20136 self.display_map.update(cx, |map, _| {
20137 map.highlight_text(
20138 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20139 ranges,
20140 style,
20141 );
20142 });
20143 cx.notify();
20144 }
20145
20146 pub fn highlight_text<T: 'static>(
20147 &mut self,
20148 ranges: Vec<Range<Anchor>>,
20149 style: HighlightStyle,
20150 cx: &mut Context<Self>,
20151 ) {
20152 self.display_map.update(cx, |map, _| {
20153 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20154 });
20155 cx.notify();
20156 }
20157
20158 pub(crate) fn highlight_inlays<T: 'static>(
20159 &mut self,
20160 highlights: Vec<InlayHighlight>,
20161 style: HighlightStyle,
20162 cx: &mut Context<Self>,
20163 ) {
20164 self.display_map.update(cx, |map, _| {
20165 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20166 });
20167 cx.notify();
20168 }
20169
20170 pub fn text_highlights<'a, T: 'static>(
20171 &'a self,
20172 cx: &'a App,
20173 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20174 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20175 }
20176
20177 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20178 let cleared = self
20179 .display_map
20180 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20181 if cleared {
20182 cx.notify();
20183 }
20184 }
20185
20186 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20187 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20188 && self.focus_handle.is_focused(window)
20189 }
20190
20191 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20192 self.show_cursor_when_unfocused = is_enabled;
20193 cx.notify();
20194 }
20195
20196 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20197 cx.notify();
20198 }
20199
20200 fn on_debug_session_event(
20201 &mut self,
20202 _session: Entity<Session>,
20203 event: &SessionEvent,
20204 cx: &mut Context<Self>,
20205 ) {
20206 if let SessionEvent::InvalidateInlineValue = event {
20207 self.refresh_inline_values(cx);
20208 }
20209 }
20210
20211 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20212 let Some(project) = self.project.clone() else {
20213 return;
20214 };
20215
20216 if !self.inline_value_cache.enabled {
20217 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20218 self.splice_inlays(&inlays, Vec::new(), cx);
20219 return;
20220 }
20221
20222 let current_execution_position = self
20223 .highlighted_rows
20224 .get(&TypeId::of::<ActiveDebugLine>())
20225 .and_then(|lines| lines.last().map(|line| line.range.end));
20226
20227 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20228 let inline_values = editor
20229 .update(cx, |editor, cx| {
20230 let Some(current_execution_position) = current_execution_position else {
20231 return Some(Task::ready(Ok(Vec::new())));
20232 };
20233
20234 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20235 let snapshot = buffer.snapshot(cx);
20236
20237 let excerpt = snapshot.excerpt_containing(
20238 current_execution_position..current_execution_position,
20239 )?;
20240
20241 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20242 })?;
20243
20244 let range =
20245 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20246
20247 project.inline_values(buffer, range, cx)
20248 })
20249 .ok()
20250 .flatten()?
20251 .await
20252 .context("refreshing debugger inlays")
20253 .log_err()?;
20254
20255 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20256
20257 for (buffer_id, inline_value) in inline_values
20258 .into_iter()
20259 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20260 {
20261 buffer_inline_values
20262 .entry(buffer_id)
20263 .or_default()
20264 .push(inline_value);
20265 }
20266
20267 editor
20268 .update(cx, |editor, cx| {
20269 let snapshot = editor.buffer.read(cx).snapshot(cx);
20270 let mut new_inlays = Vec::default();
20271
20272 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20273 let buffer_id = buffer_snapshot.remote_id();
20274 buffer_inline_values
20275 .get(&buffer_id)
20276 .into_iter()
20277 .flatten()
20278 .for_each(|hint| {
20279 let inlay = Inlay::debugger(
20280 post_inc(&mut editor.next_inlay_id),
20281 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20282 hint.text(),
20283 );
20284 if !inlay.text.chars().contains(&'\n') {
20285 new_inlays.push(inlay);
20286 }
20287 });
20288 }
20289
20290 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20291 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20292
20293 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20294 })
20295 .ok()?;
20296 Some(())
20297 });
20298 }
20299
20300 fn on_buffer_event(
20301 &mut self,
20302 multibuffer: &Entity<MultiBuffer>,
20303 event: &multi_buffer::Event,
20304 window: &mut Window,
20305 cx: &mut Context<Self>,
20306 ) {
20307 match event {
20308 multi_buffer::Event::Edited {
20309 singleton_buffer_edited,
20310 edited_buffer,
20311 } => {
20312 self.scrollbar_marker_state.dirty = true;
20313 self.active_indent_guides_state.dirty = true;
20314 self.refresh_active_diagnostics(cx);
20315 self.refresh_code_actions(window, cx);
20316 self.refresh_selected_text_highlights(true, window, cx);
20317 self.refresh_single_line_folds(window, cx);
20318 refresh_matching_bracket_highlights(self, window, cx);
20319 if self.has_active_edit_prediction() {
20320 self.update_visible_edit_prediction(window, cx);
20321 }
20322 if let Some(project) = self.project.as_ref()
20323 && let Some(edited_buffer) = edited_buffer
20324 {
20325 project.update(cx, |project, cx| {
20326 self.registered_buffers
20327 .entry(edited_buffer.read(cx).remote_id())
20328 .or_insert_with(|| {
20329 project.register_buffer_with_language_servers(edited_buffer, cx)
20330 });
20331 });
20332 }
20333 cx.emit(EditorEvent::BufferEdited);
20334 cx.emit(SearchEvent::MatchesInvalidated);
20335
20336 if let Some(buffer) = edited_buffer {
20337 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20338 }
20339
20340 if *singleton_buffer_edited {
20341 if let Some(buffer) = edited_buffer
20342 && buffer.read(cx).file().is_none()
20343 {
20344 cx.emit(EditorEvent::TitleChanged);
20345 }
20346 if let Some(project) = &self.project {
20347 #[allow(clippy::mutable_key_type)]
20348 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20349 multibuffer
20350 .all_buffers()
20351 .into_iter()
20352 .filter_map(|buffer| {
20353 buffer.update(cx, |buffer, cx| {
20354 let language = buffer.language()?;
20355 let should_discard = project.update(cx, |project, cx| {
20356 project.is_local()
20357 && !project.has_language_servers_for(buffer, cx)
20358 });
20359 should_discard.not().then_some(language.clone())
20360 })
20361 })
20362 .collect::<HashSet<_>>()
20363 });
20364 if !languages_affected.is_empty() {
20365 self.refresh_inlay_hints(
20366 InlayHintRefreshReason::BufferEdited(languages_affected),
20367 cx,
20368 );
20369 }
20370 }
20371 }
20372
20373 let Some(project) = &self.project else { return };
20374 let (telemetry, is_via_ssh) = {
20375 let project = project.read(cx);
20376 let telemetry = project.client().telemetry().clone();
20377 let is_via_ssh = project.is_via_remote_server();
20378 (telemetry, is_via_ssh)
20379 };
20380 refresh_linked_ranges(self, window, cx);
20381 telemetry.log_edit_event("editor", is_via_ssh);
20382 }
20383 multi_buffer::Event::ExcerptsAdded {
20384 buffer,
20385 predecessor,
20386 excerpts,
20387 } => {
20388 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20389 let buffer_id = buffer.read(cx).remote_id();
20390 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20391 && let Some(project) = &self.project
20392 {
20393 update_uncommitted_diff_for_buffer(
20394 cx.entity(),
20395 project,
20396 [buffer.clone()],
20397 self.buffer.clone(),
20398 cx,
20399 )
20400 .detach();
20401 }
20402 self.update_lsp_data(false, Some(buffer_id), window, cx);
20403 cx.emit(EditorEvent::ExcerptsAdded {
20404 buffer: buffer.clone(),
20405 predecessor: *predecessor,
20406 excerpts: excerpts.clone(),
20407 });
20408 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20409 }
20410 multi_buffer::Event::ExcerptsRemoved {
20411 ids,
20412 removed_buffer_ids,
20413 } => {
20414 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20415 let buffer = self.buffer.read(cx);
20416 self.registered_buffers
20417 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20418 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20419 cx.emit(EditorEvent::ExcerptsRemoved {
20420 ids: ids.clone(),
20421 removed_buffer_ids: removed_buffer_ids.clone(),
20422 });
20423 }
20424 multi_buffer::Event::ExcerptsEdited {
20425 excerpt_ids,
20426 buffer_ids,
20427 } => {
20428 self.display_map.update(cx, |map, cx| {
20429 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20430 });
20431 cx.emit(EditorEvent::ExcerptsEdited {
20432 ids: excerpt_ids.clone(),
20433 });
20434 }
20435 multi_buffer::Event::ExcerptsExpanded { ids } => {
20436 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20437 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20438 }
20439 multi_buffer::Event::Reparsed(buffer_id) => {
20440 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20441 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20442
20443 cx.emit(EditorEvent::Reparsed(*buffer_id));
20444 }
20445 multi_buffer::Event::DiffHunksToggled => {
20446 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20447 }
20448 multi_buffer::Event::LanguageChanged(buffer_id) => {
20449 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20450 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20451 cx.emit(EditorEvent::Reparsed(*buffer_id));
20452 cx.notify();
20453 }
20454 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20455 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20456 multi_buffer::Event::FileHandleChanged
20457 | multi_buffer::Event::Reloaded
20458 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20459 multi_buffer::Event::DiagnosticsUpdated => {
20460 self.update_diagnostics_state(window, cx);
20461 }
20462 _ => {}
20463 };
20464 }
20465
20466 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20467 if !self.diagnostics_enabled() {
20468 return;
20469 }
20470 self.refresh_active_diagnostics(cx);
20471 self.refresh_inline_diagnostics(true, window, cx);
20472 self.scrollbar_marker_state.dirty = true;
20473 cx.notify();
20474 }
20475
20476 pub fn start_temporary_diff_override(&mut self) {
20477 self.load_diff_task.take();
20478 self.temporary_diff_override = true;
20479 }
20480
20481 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20482 self.temporary_diff_override = false;
20483 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20484 self.buffer.update(cx, |buffer, cx| {
20485 buffer.set_all_diff_hunks_collapsed(cx);
20486 });
20487
20488 if let Some(project) = self.project.clone() {
20489 self.load_diff_task = Some(
20490 update_uncommitted_diff_for_buffer(
20491 cx.entity(),
20492 &project,
20493 self.buffer.read(cx).all_buffers(),
20494 self.buffer.clone(),
20495 cx,
20496 )
20497 .shared(),
20498 );
20499 }
20500 }
20501
20502 fn on_display_map_changed(
20503 &mut self,
20504 _: Entity<DisplayMap>,
20505 _: &mut Window,
20506 cx: &mut Context<Self>,
20507 ) {
20508 cx.notify();
20509 }
20510
20511 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20512 if self.diagnostics_enabled() {
20513 let new_severity = EditorSettings::get_global(cx)
20514 .diagnostics_max_severity
20515 .unwrap_or(DiagnosticSeverity::Hint);
20516 self.set_max_diagnostics_severity(new_severity, cx);
20517 }
20518 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20519 self.update_edit_prediction_settings(cx);
20520 self.refresh_edit_prediction(true, false, window, cx);
20521 self.refresh_inline_values(cx);
20522 self.refresh_inlay_hints(
20523 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20524 self.selections.newest_anchor().head(),
20525 &self.buffer.read(cx).snapshot(cx),
20526 cx,
20527 )),
20528 cx,
20529 );
20530
20531 let old_cursor_shape = self.cursor_shape;
20532 let old_show_breadcrumbs = self.show_breadcrumbs;
20533
20534 {
20535 let editor_settings = EditorSettings::get_global(cx);
20536 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20537 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20538 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20539 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20540 }
20541
20542 if old_cursor_shape != self.cursor_shape {
20543 cx.emit(EditorEvent::CursorShapeChanged);
20544 }
20545
20546 if old_show_breadcrumbs != self.show_breadcrumbs {
20547 cx.emit(EditorEvent::BreadcrumbsChanged);
20548 }
20549
20550 let project_settings = ProjectSettings::get_global(cx);
20551 self.serialize_dirty_buffers =
20552 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20553
20554 if self.mode.is_full() {
20555 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20556 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
20557 if self.show_inline_diagnostics != show_inline_diagnostics {
20558 self.show_inline_diagnostics = show_inline_diagnostics;
20559 self.refresh_inline_diagnostics(false, window, cx);
20560 }
20561
20562 if self.git_blame_inline_enabled != inline_blame_enabled {
20563 self.toggle_git_blame_inline_internal(false, window, cx);
20564 }
20565
20566 let minimap_settings = EditorSettings::get_global(cx).minimap;
20567 if self.minimap_visibility != MinimapVisibility::Disabled {
20568 if self.minimap_visibility.settings_visibility()
20569 != minimap_settings.minimap_enabled()
20570 {
20571 self.set_minimap_visibility(
20572 MinimapVisibility::for_mode(self.mode(), cx),
20573 window,
20574 cx,
20575 );
20576 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20577 minimap_entity.update(cx, |minimap_editor, cx| {
20578 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20579 })
20580 }
20581 }
20582 }
20583
20584 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20585 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20586 }) {
20587 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20588 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20589 }
20590 self.refresh_colors(false, None, window, cx);
20591 }
20592
20593 cx.notify();
20594 }
20595
20596 pub fn set_searchable(&mut self, searchable: bool) {
20597 self.searchable = searchable;
20598 }
20599
20600 pub fn searchable(&self) -> bool {
20601 self.searchable
20602 }
20603
20604 fn open_proposed_changes_editor(
20605 &mut self,
20606 _: &OpenProposedChangesEditor,
20607 window: &mut Window,
20608 cx: &mut Context<Self>,
20609 ) {
20610 let Some(workspace) = self.workspace() else {
20611 cx.propagate();
20612 return;
20613 };
20614
20615 let selections = self.selections.all::<usize>(cx);
20616 let multi_buffer = self.buffer.read(cx);
20617 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20618 let mut new_selections_by_buffer = HashMap::default();
20619 for selection in selections {
20620 for (buffer, range, _) in
20621 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20622 {
20623 let mut range = range.to_point(buffer);
20624 range.start.column = 0;
20625 range.end.column = buffer.line_len(range.end.row);
20626 new_selections_by_buffer
20627 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20628 .or_insert(Vec::new())
20629 .push(range)
20630 }
20631 }
20632
20633 let proposed_changes_buffers = new_selections_by_buffer
20634 .into_iter()
20635 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20636 .collect::<Vec<_>>();
20637 let proposed_changes_editor = cx.new(|cx| {
20638 ProposedChangesEditor::new(
20639 "Proposed changes",
20640 proposed_changes_buffers,
20641 self.project.clone(),
20642 window,
20643 cx,
20644 )
20645 });
20646
20647 window.defer(cx, move |window, cx| {
20648 workspace.update(cx, |workspace, cx| {
20649 workspace.active_pane().update(cx, |pane, cx| {
20650 pane.add_item(
20651 Box::new(proposed_changes_editor),
20652 true,
20653 true,
20654 None,
20655 window,
20656 cx,
20657 );
20658 });
20659 });
20660 });
20661 }
20662
20663 pub fn open_excerpts_in_split(
20664 &mut self,
20665 _: &OpenExcerptsSplit,
20666 window: &mut Window,
20667 cx: &mut Context<Self>,
20668 ) {
20669 self.open_excerpts_common(None, true, window, cx)
20670 }
20671
20672 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20673 self.open_excerpts_common(None, false, window, cx)
20674 }
20675
20676 fn open_excerpts_common(
20677 &mut self,
20678 jump_data: Option<JumpData>,
20679 split: bool,
20680 window: &mut Window,
20681 cx: &mut Context<Self>,
20682 ) {
20683 let Some(workspace) = self.workspace() else {
20684 cx.propagate();
20685 return;
20686 };
20687
20688 if self.buffer.read(cx).is_singleton() {
20689 cx.propagate();
20690 return;
20691 }
20692
20693 let mut new_selections_by_buffer = HashMap::default();
20694 match &jump_data {
20695 Some(JumpData::MultiBufferPoint {
20696 excerpt_id,
20697 position,
20698 anchor,
20699 line_offset_from_top,
20700 }) => {
20701 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20702 if let Some(buffer) = multi_buffer_snapshot
20703 .buffer_id_for_excerpt(*excerpt_id)
20704 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20705 {
20706 let buffer_snapshot = buffer.read(cx).snapshot();
20707 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20708 language::ToPoint::to_point(anchor, &buffer_snapshot)
20709 } else {
20710 buffer_snapshot.clip_point(*position, Bias::Left)
20711 };
20712 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20713 new_selections_by_buffer.insert(
20714 buffer,
20715 (
20716 vec![jump_to_offset..jump_to_offset],
20717 Some(*line_offset_from_top),
20718 ),
20719 );
20720 }
20721 }
20722 Some(JumpData::MultiBufferRow {
20723 row,
20724 line_offset_from_top,
20725 }) => {
20726 let point = MultiBufferPoint::new(row.0, 0);
20727 if let Some((buffer, buffer_point, _)) =
20728 self.buffer.read(cx).point_to_buffer_point(point, cx)
20729 {
20730 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20731 new_selections_by_buffer
20732 .entry(buffer)
20733 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20734 .0
20735 .push(buffer_offset..buffer_offset)
20736 }
20737 }
20738 None => {
20739 let selections = self.selections.all::<usize>(cx);
20740 let multi_buffer = self.buffer.read(cx);
20741 for selection in selections {
20742 for (snapshot, range, _, anchor) in multi_buffer
20743 .snapshot(cx)
20744 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20745 {
20746 if let Some(anchor) = anchor {
20747 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20748 else {
20749 continue;
20750 };
20751 let offset = text::ToOffset::to_offset(
20752 &anchor.text_anchor,
20753 &buffer_handle.read(cx).snapshot(),
20754 );
20755 let range = offset..offset;
20756 new_selections_by_buffer
20757 .entry(buffer_handle)
20758 .or_insert((Vec::new(), None))
20759 .0
20760 .push(range)
20761 } else {
20762 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
20763 else {
20764 continue;
20765 };
20766 new_selections_by_buffer
20767 .entry(buffer_handle)
20768 .or_insert((Vec::new(), None))
20769 .0
20770 .push(range)
20771 }
20772 }
20773 }
20774 }
20775 }
20776
20777 new_selections_by_buffer
20778 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20779
20780 if new_selections_by_buffer.is_empty() {
20781 return;
20782 }
20783
20784 // We defer the pane interaction because we ourselves are a workspace item
20785 // and activating a new item causes the pane to call a method on us reentrantly,
20786 // which panics if we're on the stack.
20787 window.defer(cx, move |window, cx| {
20788 workspace.update(cx, |workspace, cx| {
20789 let pane = if split {
20790 workspace.adjacent_pane(window, cx)
20791 } else {
20792 workspace.active_pane().clone()
20793 };
20794
20795 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20796 let editor = buffer
20797 .read(cx)
20798 .file()
20799 .is_none()
20800 .then(|| {
20801 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20802 // so `workspace.open_project_item` will never find them, always opening a new editor.
20803 // Instead, we try to activate the existing editor in the pane first.
20804 let (editor, pane_item_index) =
20805 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20806 let editor = item.downcast::<Editor>()?;
20807 let singleton_buffer =
20808 editor.read(cx).buffer().read(cx).as_singleton()?;
20809 if singleton_buffer == buffer {
20810 Some((editor, i))
20811 } else {
20812 None
20813 }
20814 })?;
20815 pane.update(cx, |pane, cx| {
20816 pane.activate_item(pane_item_index, true, true, window, cx)
20817 });
20818 Some(editor)
20819 })
20820 .flatten()
20821 .unwrap_or_else(|| {
20822 workspace.open_project_item::<Self>(
20823 pane.clone(),
20824 buffer,
20825 true,
20826 true,
20827 window,
20828 cx,
20829 )
20830 });
20831
20832 editor.update(cx, |editor, cx| {
20833 let autoscroll = match scroll_offset {
20834 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20835 None => Autoscroll::newest(),
20836 };
20837 let nav_history = editor.nav_history.take();
20838 editor.change_selections(
20839 SelectionEffects::scroll(autoscroll),
20840 window,
20841 cx,
20842 |s| {
20843 s.select_ranges(ranges);
20844 },
20845 );
20846 editor.nav_history = nav_history;
20847 });
20848 }
20849 })
20850 });
20851 }
20852
20853 // For now, don't allow opening excerpts in buffers that aren't backed by
20854 // regular project files.
20855 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20856 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
20857 }
20858
20859 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20860 let snapshot = self.buffer.read(cx).read(cx);
20861 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20862 Some(
20863 ranges
20864 .iter()
20865 .map(move |range| {
20866 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20867 })
20868 .collect(),
20869 )
20870 }
20871
20872 fn selection_replacement_ranges(
20873 &self,
20874 range: Range<OffsetUtf16>,
20875 cx: &mut App,
20876 ) -> Vec<Range<OffsetUtf16>> {
20877 let selections = self.selections.all::<OffsetUtf16>(cx);
20878 let newest_selection = selections
20879 .iter()
20880 .max_by_key(|selection| selection.id)
20881 .unwrap();
20882 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20883 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20884 let snapshot = self.buffer.read(cx).read(cx);
20885 selections
20886 .into_iter()
20887 .map(|mut selection| {
20888 selection.start.0 =
20889 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20890 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20891 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20892 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20893 })
20894 .collect()
20895 }
20896
20897 fn report_editor_event(
20898 &self,
20899 reported_event: ReportEditorEvent,
20900 file_extension: Option<String>,
20901 cx: &App,
20902 ) {
20903 if cfg!(any(test, feature = "test-support")) {
20904 return;
20905 }
20906
20907 let Some(project) = &self.project else { return };
20908
20909 // If None, we are in a file without an extension
20910 let file = self
20911 .buffer
20912 .read(cx)
20913 .as_singleton()
20914 .and_then(|b| b.read(cx).file());
20915 let file_extension = file_extension.or(file
20916 .as_ref()
20917 .and_then(|file| Path::new(file.file_name(cx)).extension())
20918 .and_then(|e| e.to_str())
20919 .map(|a| a.to_string()));
20920
20921 let vim_mode = vim_enabled(cx);
20922
20923 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20924 let copilot_enabled = edit_predictions_provider
20925 == language::language_settings::EditPredictionProvider::Copilot;
20926 let copilot_enabled_for_language = self
20927 .buffer
20928 .read(cx)
20929 .language_settings(cx)
20930 .show_edit_predictions;
20931
20932 let project = project.read(cx);
20933 let event_type = reported_event.event_type();
20934
20935 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
20936 telemetry::event!(
20937 event_type,
20938 type = if auto_saved {"autosave"} else {"manual"},
20939 file_extension,
20940 vim_mode,
20941 copilot_enabled,
20942 copilot_enabled_for_language,
20943 edit_predictions_provider,
20944 is_via_ssh = project.is_via_remote_server(),
20945 );
20946 } else {
20947 telemetry::event!(
20948 event_type,
20949 file_extension,
20950 vim_mode,
20951 copilot_enabled,
20952 copilot_enabled_for_language,
20953 edit_predictions_provider,
20954 is_via_ssh = project.is_via_remote_server(),
20955 );
20956 };
20957 }
20958
20959 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20960 /// with each line being an array of {text, highlight} objects.
20961 fn copy_highlight_json(
20962 &mut self,
20963 _: &CopyHighlightJson,
20964 window: &mut Window,
20965 cx: &mut Context<Self>,
20966 ) {
20967 #[derive(Serialize)]
20968 struct Chunk<'a> {
20969 text: String,
20970 highlight: Option<&'a str>,
20971 }
20972
20973 let snapshot = self.buffer.read(cx).snapshot(cx);
20974 let range = self
20975 .selected_text_range(false, window, cx)
20976 .and_then(|selection| {
20977 if selection.range.is_empty() {
20978 None
20979 } else {
20980 Some(selection.range)
20981 }
20982 })
20983 .unwrap_or_else(|| 0..snapshot.len());
20984
20985 let chunks = snapshot.chunks(range, true);
20986 let mut lines = Vec::new();
20987 let mut line: VecDeque<Chunk> = VecDeque::new();
20988
20989 let Some(style) = self.style.as_ref() else {
20990 return;
20991 };
20992
20993 for chunk in chunks {
20994 let highlight = chunk
20995 .syntax_highlight_id
20996 .and_then(|id| id.name(&style.syntax));
20997 let mut chunk_lines = chunk.text.split('\n').peekable();
20998 while let Some(text) = chunk_lines.next() {
20999 let mut merged_with_last_token = false;
21000 if let Some(last_token) = line.back_mut()
21001 && last_token.highlight == highlight
21002 {
21003 last_token.text.push_str(text);
21004 merged_with_last_token = true;
21005 }
21006
21007 if !merged_with_last_token {
21008 line.push_back(Chunk {
21009 text: text.into(),
21010 highlight,
21011 });
21012 }
21013
21014 if chunk_lines.peek().is_some() {
21015 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21016 line.pop_front();
21017 }
21018 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21019 line.pop_back();
21020 }
21021
21022 lines.push(mem::take(&mut line));
21023 }
21024 }
21025 }
21026
21027 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21028 return;
21029 };
21030 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21031 }
21032
21033 pub fn open_context_menu(
21034 &mut self,
21035 _: &OpenContextMenu,
21036 window: &mut Window,
21037 cx: &mut Context<Self>,
21038 ) {
21039 self.request_autoscroll(Autoscroll::newest(), cx);
21040 let position = self.selections.newest_display(cx).start;
21041 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21042 }
21043
21044 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21045 &self.inlay_hint_cache
21046 }
21047
21048 pub fn replay_insert_event(
21049 &mut self,
21050 text: &str,
21051 relative_utf16_range: Option<Range<isize>>,
21052 window: &mut Window,
21053 cx: &mut Context<Self>,
21054 ) {
21055 if !self.input_enabled {
21056 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21057 return;
21058 }
21059 if let Some(relative_utf16_range) = relative_utf16_range {
21060 let selections = self.selections.all::<OffsetUtf16>(cx);
21061 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21062 let new_ranges = selections.into_iter().map(|range| {
21063 let start = OffsetUtf16(
21064 range
21065 .head()
21066 .0
21067 .saturating_add_signed(relative_utf16_range.start),
21068 );
21069 let end = OffsetUtf16(
21070 range
21071 .head()
21072 .0
21073 .saturating_add_signed(relative_utf16_range.end),
21074 );
21075 start..end
21076 });
21077 s.select_ranges(new_ranges);
21078 });
21079 }
21080
21081 self.handle_input(text, window, cx);
21082 }
21083
21084 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21085 let Some(provider) = self.semantics_provider.as_ref() else {
21086 return false;
21087 };
21088
21089 let mut supports = false;
21090 self.buffer().update(cx, |this, cx| {
21091 this.for_each_buffer(|buffer| {
21092 supports |= provider.supports_inlay_hints(buffer, cx);
21093 });
21094 });
21095
21096 supports
21097 }
21098
21099 pub fn is_focused(&self, window: &Window) -> bool {
21100 self.focus_handle.is_focused(window)
21101 }
21102
21103 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21104 cx.emit(EditorEvent::Focused);
21105
21106 if let Some(descendant) = self
21107 .last_focused_descendant
21108 .take()
21109 .and_then(|descendant| descendant.upgrade())
21110 {
21111 window.focus(&descendant);
21112 } else {
21113 if let Some(blame) = self.blame.as_ref() {
21114 blame.update(cx, GitBlame::focus)
21115 }
21116
21117 self.blink_manager.update(cx, BlinkManager::enable);
21118 self.show_cursor_names(window, cx);
21119 self.buffer.update(cx, |buffer, cx| {
21120 buffer.finalize_last_transaction(cx);
21121 if self.leader_id.is_none() {
21122 buffer.set_active_selections(
21123 &self.selections.disjoint_anchors(),
21124 self.selections.line_mode,
21125 self.cursor_shape,
21126 cx,
21127 );
21128 }
21129 });
21130 }
21131 }
21132
21133 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21134 cx.emit(EditorEvent::FocusedIn)
21135 }
21136
21137 fn handle_focus_out(
21138 &mut self,
21139 event: FocusOutEvent,
21140 _window: &mut Window,
21141 cx: &mut Context<Self>,
21142 ) {
21143 if event.blurred != self.focus_handle {
21144 self.last_focused_descendant = Some(event.blurred);
21145 }
21146 self.selection_drag_state = SelectionDragState::None;
21147 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21148 }
21149
21150 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21151 self.blink_manager.update(cx, BlinkManager::disable);
21152 self.buffer
21153 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21154
21155 if let Some(blame) = self.blame.as_ref() {
21156 blame.update(cx, GitBlame::blur)
21157 }
21158 if !self.hover_state.focused(window, cx) {
21159 hide_hover(self, cx);
21160 }
21161 if !self
21162 .context_menu
21163 .borrow()
21164 .as_ref()
21165 .is_some_and(|context_menu| context_menu.focused(window, cx))
21166 {
21167 self.hide_context_menu(window, cx);
21168 }
21169 self.discard_edit_prediction(false, cx);
21170 cx.emit(EditorEvent::Blurred);
21171 cx.notify();
21172 }
21173
21174 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21175 let mut pending: String = window
21176 .pending_input_keystrokes()
21177 .into_iter()
21178 .flatten()
21179 .filter_map(|keystroke| {
21180 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21181 keystroke.key_char.clone()
21182 } else {
21183 None
21184 }
21185 })
21186 .collect();
21187
21188 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21189 pending = "".to_string();
21190 }
21191
21192 let existing_pending = self
21193 .text_highlights::<PendingInput>(cx)
21194 .map(|(_, ranges)| ranges.to_vec());
21195 if existing_pending.is_none() && pending.is_empty() {
21196 return;
21197 }
21198 let transaction =
21199 self.transact(window, cx, |this, window, cx| {
21200 let selections = this.selections.all::<usize>(cx);
21201 let edits = selections
21202 .iter()
21203 .map(|selection| (selection.end..selection.end, pending.clone()));
21204 this.edit(edits, cx);
21205 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21206 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21207 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21208 }));
21209 });
21210 if let Some(existing_ranges) = existing_pending {
21211 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21212 this.edit(edits, cx);
21213 }
21214 });
21215
21216 let snapshot = self.snapshot(window, cx);
21217 let ranges = self
21218 .selections
21219 .all::<usize>(cx)
21220 .into_iter()
21221 .map(|selection| {
21222 snapshot.buffer_snapshot.anchor_after(selection.end)
21223 ..snapshot
21224 .buffer_snapshot
21225 .anchor_before(selection.end + pending.len())
21226 })
21227 .collect();
21228
21229 if pending.is_empty() {
21230 self.clear_highlights::<PendingInput>(cx);
21231 } else {
21232 self.highlight_text::<PendingInput>(
21233 ranges,
21234 HighlightStyle {
21235 underline: Some(UnderlineStyle {
21236 thickness: px(1.),
21237 color: None,
21238 wavy: false,
21239 }),
21240 ..Default::default()
21241 },
21242 cx,
21243 );
21244 }
21245
21246 self.ime_transaction = self.ime_transaction.or(transaction);
21247 if let Some(transaction) = self.ime_transaction {
21248 self.buffer.update(cx, |buffer, cx| {
21249 buffer.group_until_transaction(transaction, cx);
21250 });
21251 }
21252
21253 if self.text_highlights::<PendingInput>(cx).is_none() {
21254 self.ime_transaction.take();
21255 }
21256 }
21257
21258 pub fn register_action_renderer(
21259 &mut self,
21260 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21261 ) -> Subscription {
21262 let id = self.next_editor_action_id.post_inc();
21263 self.editor_actions
21264 .borrow_mut()
21265 .insert(id, Box::new(listener));
21266
21267 let editor_actions = self.editor_actions.clone();
21268 Subscription::new(move || {
21269 editor_actions.borrow_mut().remove(&id);
21270 })
21271 }
21272
21273 pub fn register_action<A: Action>(
21274 &mut self,
21275 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21276 ) -> Subscription {
21277 let id = self.next_editor_action_id.post_inc();
21278 let listener = Arc::new(listener);
21279 self.editor_actions.borrow_mut().insert(
21280 id,
21281 Box::new(move |_, window, _| {
21282 let listener = listener.clone();
21283 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21284 let action = action.downcast_ref().unwrap();
21285 if phase == DispatchPhase::Bubble {
21286 listener(action, window, cx)
21287 }
21288 })
21289 }),
21290 );
21291
21292 let editor_actions = self.editor_actions.clone();
21293 Subscription::new(move || {
21294 editor_actions.borrow_mut().remove(&id);
21295 })
21296 }
21297
21298 pub fn file_header_size(&self) -> u32 {
21299 FILE_HEADER_HEIGHT
21300 }
21301
21302 pub fn restore(
21303 &mut self,
21304 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21305 window: &mut Window,
21306 cx: &mut Context<Self>,
21307 ) {
21308 let workspace = self.workspace();
21309 let project = self.project();
21310 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21311 let mut tasks = Vec::new();
21312 for (buffer_id, changes) in revert_changes {
21313 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21314 buffer.update(cx, |buffer, cx| {
21315 buffer.edit(
21316 changes
21317 .into_iter()
21318 .map(|(range, text)| (range, text.to_string())),
21319 None,
21320 cx,
21321 );
21322 });
21323
21324 if let Some(project) =
21325 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21326 {
21327 project.update(cx, |project, cx| {
21328 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21329 })
21330 }
21331 }
21332 }
21333 tasks
21334 });
21335 cx.spawn_in(window, async move |_, cx| {
21336 for (buffer, task) in save_tasks {
21337 let result = task.await;
21338 if result.is_err() {
21339 let Some(path) = buffer
21340 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21341 .ok()
21342 else {
21343 continue;
21344 };
21345 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21346 let Some(task) = cx
21347 .update_window_entity(workspace, |workspace, window, cx| {
21348 workspace
21349 .open_path_preview(path, None, false, false, false, window, cx)
21350 })
21351 .ok()
21352 else {
21353 continue;
21354 };
21355 task.await.log_err();
21356 }
21357 }
21358 }
21359 })
21360 .detach();
21361 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21362 selections.refresh()
21363 });
21364 }
21365
21366 pub fn to_pixel_point(
21367 &self,
21368 source: multi_buffer::Anchor,
21369 editor_snapshot: &EditorSnapshot,
21370 window: &mut Window,
21371 ) -> Option<gpui::Point<Pixels>> {
21372 let source_point = source.to_display_point(editor_snapshot);
21373 self.display_to_pixel_point(source_point, editor_snapshot, window)
21374 }
21375
21376 pub fn display_to_pixel_point(
21377 &self,
21378 source: DisplayPoint,
21379 editor_snapshot: &EditorSnapshot,
21380 window: &mut Window,
21381 ) -> Option<gpui::Point<Pixels>> {
21382 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21383 let text_layout_details = self.text_layout_details(window);
21384 let scroll_top = text_layout_details
21385 .scroll_anchor
21386 .scroll_position(editor_snapshot)
21387 .y;
21388
21389 if source.row().as_f32() < scroll_top.floor() {
21390 return None;
21391 }
21392 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21393 let source_y = line_height * (source.row().as_f32() - scroll_top);
21394 Some(gpui::Point::new(source_x, source_y))
21395 }
21396
21397 pub fn has_visible_completions_menu(&self) -> bool {
21398 !self.edit_prediction_preview_is_active()
21399 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21400 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21401 })
21402 }
21403
21404 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21405 if self.mode.is_minimap() {
21406 return;
21407 }
21408 self.addons
21409 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21410 }
21411
21412 pub fn unregister_addon<T: Addon>(&mut self) {
21413 self.addons.remove(&std::any::TypeId::of::<T>());
21414 }
21415
21416 pub fn addon<T: Addon>(&self) -> Option<&T> {
21417 let type_id = std::any::TypeId::of::<T>();
21418 self.addons
21419 .get(&type_id)
21420 .and_then(|item| item.to_any().downcast_ref::<T>())
21421 }
21422
21423 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21424 let type_id = std::any::TypeId::of::<T>();
21425 self.addons
21426 .get_mut(&type_id)
21427 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21428 }
21429
21430 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21431 let text_layout_details = self.text_layout_details(window);
21432 let style = &text_layout_details.editor_style;
21433 let font_id = window.text_system().resolve_font(&style.text.font());
21434 let font_size = style.text.font_size.to_pixels(window.rem_size());
21435 let line_height = style.text.line_height_in_pixels(window.rem_size());
21436 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21437 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21438
21439 CharacterDimensions {
21440 em_width,
21441 em_advance,
21442 line_height,
21443 }
21444 }
21445
21446 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21447 self.load_diff_task.clone()
21448 }
21449
21450 fn read_metadata_from_db(
21451 &mut self,
21452 item_id: u64,
21453 workspace_id: WorkspaceId,
21454 window: &mut Window,
21455 cx: &mut Context<Editor>,
21456 ) {
21457 if self.is_singleton(cx)
21458 && !self.mode.is_minimap()
21459 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21460 {
21461 let buffer_snapshot = OnceCell::new();
21462
21463 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21464 && !folds.is_empty()
21465 {
21466 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21467 self.fold_ranges(
21468 folds
21469 .into_iter()
21470 .map(|(start, end)| {
21471 snapshot.clip_offset(start, Bias::Left)
21472 ..snapshot.clip_offset(end, Bias::Right)
21473 })
21474 .collect(),
21475 false,
21476 window,
21477 cx,
21478 );
21479 }
21480
21481 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21482 && !selections.is_empty()
21483 {
21484 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21485 // skip adding the initial selection to selection history
21486 self.selection_history.mode = SelectionHistoryMode::Skipping;
21487 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21488 s.select_ranges(selections.into_iter().map(|(start, end)| {
21489 snapshot.clip_offset(start, Bias::Left)
21490 ..snapshot.clip_offset(end, Bias::Right)
21491 }));
21492 });
21493 self.selection_history.mode = SelectionHistoryMode::Normal;
21494 };
21495 }
21496
21497 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21498 }
21499
21500 fn update_lsp_data(
21501 &mut self,
21502 ignore_cache: bool,
21503 for_buffer: Option<BufferId>,
21504 window: &mut Window,
21505 cx: &mut Context<'_, Self>,
21506 ) {
21507 self.pull_diagnostics(for_buffer, window, cx);
21508 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21509 }
21510}
21511
21512fn vim_enabled(cx: &App) -> bool {
21513 cx.global::<SettingsStore>()
21514 .raw_user_settings()
21515 .get("vim_mode")
21516 == Some(&serde_json::Value::Bool(true))
21517}
21518
21519fn process_completion_for_edit(
21520 completion: &Completion,
21521 intent: CompletionIntent,
21522 buffer: &Entity<Buffer>,
21523 cursor_position: &text::Anchor,
21524 cx: &mut Context<Editor>,
21525) -> CompletionEdit {
21526 let buffer = buffer.read(cx);
21527 let buffer_snapshot = buffer.snapshot();
21528 let (snippet, new_text) = if completion.is_snippet() {
21529 // Workaround for typescript language server issues so that methods don't expand within
21530 // strings and functions with type expressions. The previous point is used because the query
21531 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21532 let mut snippet_source = completion.new_text.clone();
21533 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21534 previous_point.column = previous_point.column.saturating_sub(1);
21535 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21536 && scope.prefers_label_for_snippet_in_completion()
21537 && let Some(label) = completion.label()
21538 && matches!(
21539 completion.kind(),
21540 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21541 )
21542 {
21543 snippet_source = label;
21544 }
21545 match Snippet::parse(&snippet_source).log_err() {
21546 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21547 None => (None, completion.new_text.clone()),
21548 }
21549 } else {
21550 (None, completion.new_text.clone())
21551 };
21552
21553 let mut range_to_replace = {
21554 let replace_range = &completion.replace_range;
21555 if let CompletionSource::Lsp {
21556 insert_range: Some(insert_range),
21557 ..
21558 } = &completion.source
21559 {
21560 debug_assert_eq!(
21561 insert_range.start, replace_range.start,
21562 "insert_range and replace_range should start at the same position"
21563 );
21564 debug_assert!(
21565 insert_range
21566 .start
21567 .cmp(cursor_position, &buffer_snapshot)
21568 .is_le(),
21569 "insert_range should start before or at cursor position"
21570 );
21571 debug_assert!(
21572 replace_range
21573 .start
21574 .cmp(cursor_position, &buffer_snapshot)
21575 .is_le(),
21576 "replace_range should start before or at cursor position"
21577 );
21578
21579 let should_replace = match intent {
21580 CompletionIntent::CompleteWithInsert => false,
21581 CompletionIntent::CompleteWithReplace => true,
21582 CompletionIntent::Complete | CompletionIntent::Compose => {
21583 let insert_mode =
21584 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21585 .completions
21586 .lsp_insert_mode;
21587 match insert_mode {
21588 LspInsertMode::Insert => false,
21589 LspInsertMode::Replace => true,
21590 LspInsertMode::ReplaceSubsequence => {
21591 let mut text_to_replace = buffer.chars_for_range(
21592 buffer.anchor_before(replace_range.start)
21593 ..buffer.anchor_after(replace_range.end),
21594 );
21595 let mut current_needle = text_to_replace.next();
21596 for haystack_ch in completion.label.text.chars() {
21597 if let Some(needle_ch) = current_needle
21598 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21599 {
21600 current_needle = text_to_replace.next();
21601 }
21602 }
21603 current_needle.is_none()
21604 }
21605 LspInsertMode::ReplaceSuffix => {
21606 if replace_range
21607 .end
21608 .cmp(cursor_position, &buffer_snapshot)
21609 .is_gt()
21610 {
21611 let range_after_cursor = *cursor_position..replace_range.end;
21612 let text_after_cursor = buffer
21613 .text_for_range(
21614 buffer.anchor_before(range_after_cursor.start)
21615 ..buffer.anchor_after(range_after_cursor.end),
21616 )
21617 .collect::<String>()
21618 .to_ascii_lowercase();
21619 completion
21620 .label
21621 .text
21622 .to_ascii_lowercase()
21623 .ends_with(&text_after_cursor)
21624 } else {
21625 true
21626 }
21627 }
21628 }
21629 }
21630 };
21631
21632 if should_replace {
21633 replace_range.clone()
21634 } else {
21635 insert_range.clone()
21636 }
21637 } else {
21638 replace_range.clone()
21639 }
21640 };
21641
21642 if range_to_replace
21643 .end
21644 .cmp(cursor_position, &buffer_snapshot)
21645 .is_lt()
21646 {
21647 range_to_replace.end = *cursor_position;
21648 }
21649
21650 CompletionEdit {
21651 new_text,
21652 replace_range: range_to_replace.to_offset(buffer),
21653 snippet,
21654 }
21655}
21656
21657struct CompletionEdit {
21658 new_text: String,
21659 replace_range: Range<usize>,
21660 snippet: Option<Snippet>,
21661}
21662
21663fn insert_extra_newline_brackets(
21664 buffer: &MultiBufferSnapshot,
21665 range: Range<usize>,
21666 language: &language::LanguageScope,
21667) -> bool {
21668 let leading_whitespace_len = buffer
21669 .reversed_chars_at(range.start)
21670 .take_while(|c| c.is_whitespace() && *c != '\n')
21671 .map(|c| c.len_utf8())
21672 .sum::<usize>();
21673 let trailing_whitespace_len = buffer
21674 .chars_at(range.end)
21675 .take_while(|c| c.is_whitespace() && *c != '\n')
21676 .map(|c| c.len_utf8())
21677 .sum::<usize>();
21678 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21679
21680 language.brackets().any(|(pair, enabled)| {
21681 let pair_start = pair.start.trim_end();
21682 let pair_end = pair.end.trim_start();
21683
21684 enabled
21685 && pair.newline
21686 && buffer.contains_str_at(range.end, pair_end)
21687 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21688 })
21689}
21690
21691fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21692 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21693 [(buffer, range, _)] => (*buffer, range.clone()),
21694 _ => return false,
21695 };
21696 let pair = {
21697 let mut result: Option<BracketMatch> = None;
21698
21699 for pair in buffer
21700 .all_bracket_ranges(range.clone())
21701 .filter(move |pair| {
21702 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21703 })
21704 {
21705 let len = pair.close_range.end - pair.open_range.start;
21706
21707 if let Some(existing) = &result {
21708 let existing_len = existing.close_range.end - existing.open_range.start;
21709 if len > existing_len {
21710 continue;
21711 }
21712 }
21713
21714 result = Some(pair);
21715 }
21716
21717 result
21718 };
21719 let Some(pair) = pair else {
21720 return false;
21721 };
21722 pair.newline_only
21723 && buffer
21724 .chars_for_range(pair.open_range.end..range.start)
21725 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21726 .all(|c| c.is_whitespace() && c != '\n')
21727}
21728
21729fn update_uncommitted_diff_for_buffer(
21730 editor: Entity<Editor>,
21731 project: &Entity<Project>,
21732 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21733 buffer: Entity<MultiBuffer>,
21734 cx: &mut App,
21735) -> Task<()> {
21736 let mut tasks = Vec::new();
21737 project.update(cx, |project, cx| {
21738 for buffer in buffers {
21739 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21740 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
21741 }
21742 }
21743 });
21744 cx.spawn(async move |cx| {
21745 let diffs = future::join_all(tasks).await;
21746 if editor
21747 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
21748 .unwrap_or(false)
21749 {
21750 return;
21751 }
21752
21753 buffer
21754 .update(cx, |buffer, cx| {
21755 for diff in diffs.into_iter().flatten() {
21756 buffer.add_diff(diff, cx);
21757 }
21758 })
21759 .ok();
21760 })
21761}
21762
21763fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
21764 let tab_size = tab_size.get() as usize;
21765 let mut width = offset;
21766
21767 for ch in text.chars() {
21768 width += if ch == '\t' {
21769 tab_size - (width % tab_size)
21770 } else {
21771 1
21772 };
21773 }
21774
21775 width - offset
21776}
21777
21778#[cfg(test)]
21779mod tests {
21780 use super::*;
21781
21782 #[test]
21783 fn test_string_size_with_expanded_tabs() {
21784 let nz = |val| NonZeroU32::new(val).unwrap();
21785 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21786 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21787 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21788 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21789 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21790 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21791 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21792 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21793 }
21794}
21795
21796/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21797struct WordBreakingTokenizer<'a> {
21798 input: &'a str,
21799}
21800
21801impl<'a> WordBreakingTokenizer<'a> {
21802 fn new(input: &'a str) -> Self {
21803 Self { input }
21804 }
21805}
21806
21807fn is_char_ideographic(ch: char) -> bool {
21808 use unicode_script::Script::*;
21809 use unicode_script::UnicodeScript;
21810 matches!(ch.script(), Han | Tangut | Yi)
21811}
21812
21813fn is_grapheme_ideographic(text: &str) -> bool {
21814 text.chars().any(is_char_ideographic)
21815}
21816
21817fn is_grapheme_whitespace(text: &str) -> bool {
21818 text.chars().any(|x| x.is_whitespace())
21819}
21820
21821fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21822 text.chars()
21823 .next()
21824 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
21825}
21826
21827#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21828enum WordBreakToken<'a> {
21829 Word { token: &'a str, grapheme_len: usize },
21830 InlineWhitespace { token: &'a str, grapheme_len: usize },
21831 Newline,
21832}
21833
21834impl<'a> Iterator for WordBreakingTokenizer<'a> {
21835 /// Yields a span, the count of graphemes in the token, and whether it was
21836 /// whitespace. Note that it also breaks at word boundaries.
21837 type Item = WordBreakToken<'a>;
21838
21839 fn next(&mut self) -> Option<Self::Item> {
21840 use unicode_segmentation::UnicodeSegmentation;
21841 if self.input.is_empty() {
21842 return None;
21843 }
21844
21845 let mut iter = self.input.graphemes(true).peekable();
21846 let mut offset = 0;
21847 let mut grapheme_len = 0;
21848 if let Some(first_grapheme) = iter.next() {
21849 let is_newline = first_grapheme == "\n";
21850 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21851 offset += first_grapheme.len();
21852 grapheme_len += 1;
21853 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21854 if let Some(grapheme) = iter.peek().copied()
21855 && should_stay_with_preceding_ideograph(grapheme)
21856 {
21857 offset += grapheme.len();
21858 grapheme_len += 1;
21859 }
21860 } else {
21861 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21862 let mut next_word_bound = words.peek().copied();
21863 if next_word_bound.is_some_and(|(i, _)| i == 0) {
21864 next_word_bound = words.next();
21865 }
21866 while let Some(grapheme) = iter.peek().copied() {
21867 if next_word_bound.is_some_and(|(i, _)| i == offset) {
21868 break;
21869 };
21870 if is_grapheme_whitespace(grapheme) != is_whitespace
21871 || (grapheme == "\n") != is_newline
21872 {
21873 break;
21874 };
21875 offset += grapheme.len();
21876 grapheme_len += 1;
21877 iter.next();
21878 }
21879 }
21880 let token = &self.input[..offset];
21881 self.input = &self.input[offset..];
21882 if token == "\n" {
21883 Some(WordBreakToken::Newline)
21884 } else if is_whitespace {
21885 Some(WordBreakToken::InlineWhitespace {
21886 token,
21887 grapheme_len,
21888 })
21889 } else {
21890 Some(WordBreakToken::Word {
21891 token,
21892 grapheme_len,
21893 })
21894 }
21895 } else {
21896 None
21897 }
21898 }
21899}
21900
21901#[test]
21902fn test_word_breaking_tokenizer() {
21903 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21904 ("", &[]),
21905 (" ", &[whitespace(" ", 2)]),
21906 ("Ʒ", &[word("Ʒ", 1)]),
21907 ("Ǽ", &[word("Ǽ", 1)]),
21908 ("⋑", &[word("⋑", 1)]),
21909 ("⋑⋑", &[word("⋑⋑", 2)]),
21910 (
21911 "原理,进而",
21912 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21913 ),
21914 (
21915 "hello world",
21916 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21917 ),
21918 (
21919 "hello, world",
21920 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21921 ),
21922 (
21923 " hello world",
21924 &[
21925 whitespace(" ", 2),
21926 word("hello", 5),
21927 whitespace(" ", 1),
21928 word("world", 5),
21929 ],
21930 ),
21931 (
21932 "这是什么 \n 钢笔",
21933 &[
21934 word("这", 1),
21935 word("是", 1),
21936 word("什", 1),
21937 word("么", 1),
21938 whitespace(" ", 1),
21939 newline(),
21940 whitespace(" ", 1),
21941 word("钢", 1),
21942 word("笔", 1),
21943 ],
21944 ),
21945 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21946 ];
21947
21948 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21949 WordBreakToken::Word {
21950 token,
21951 grapheme_len,
21952 }
21953 }
21954
21955 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21956 WordBreakToken::InlineWhitespace {
21957 token,
21958 grapheme_len,
21959 }
21960 }
21961
21962 fn newline() -> WordBreakToken<'static> {
21963 WordBreakToken::Newline
21964 }
21965
21966 for (input, result) in tests {
21967 assert_eq!(
21968 WordBreakingTokenizer::new(input)
21969 .collect::<Vec<_>>()
21970 .as_slice(),
21971 *result,
21972 );
21973 }
21974}
21975
21976fn wrap_with_prefix(
21977 first_line_prefix: String,
21978 subsequent_lines_prefix: String,
21979 unwrapped_text: String,
21980 wrap_column: usize,
21981 tab_size: NonZeroU32,
21982 preserve_existing_whitespace: bool,
21983) -> String {
21984 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
21985 let subsequent_lines_prefix_len =
21986 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
21987 let mut wrapped_text = String::new();
21988 let mut current_line = first_line_prefix;
21989 let mut is_first_line = true;
21990
21991 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21992 let mut current_line_len = first_line_prefix_len;
21993 let mut in_whitespace = false;
21994 for token in tokenizer {
21995 let have_preceding_whitespace = in_whitespace;
21996 match token {
21997 WordBreakToken::Word {
21998 token,
21999 grapheme_len,
22000 } => {
22001 in_whitespace = false;
22002 let current_prefix_len = if is_first_line {
22003 first_line_prefix_len
22004 } else {
22005 subsequent_lines_prefix_len
22006 };
22007 if current_line_len + grapheme_len > wrap_column
22008 && current_line_len != current_prefix_len
22009 {
22010 wrapped_text.push_str(current_line.trim_end());
22011 wrapped_text.push('\n');
22012 is_first_line = false;
22013 current_line = subsequent_lines_prefix.clone();
22014 current_line_len = subsequent_lines_prefix_len;
22015 }
22016 current_line.push_str(token);
22017 current_line_len += grapheme_len;
22018 }
22019 WordBreakToken::InlineWhitespace {
22020 mut token,
22021 mut grapheme_len,
22022 } => {
22023 in_whitespace = true;
22024 if have_preceding_whitespace && !preserve_existing_whitespace {
22025 continue;
22026 }
22027 if !preserve_existing_whitespace {
22028 token = " ";
22029 grapheme_len = 1;
22030 }
22031 let current_prefix_len = if is_first_line {
22032 first_line_prefix_len
22033 } else {
22034 subsequent_lines_prefix_len
22035 };
22036 if current_line_len + grapheme_len > wrap_column {
22037 wrapped_text.push_str(current_line.trim_end());
22038 wrapped_text.push('\n');
22039 is_first_line = false;
22040 current_line = subsequent_lines_prefix.clone();
22041 current_line_len = subsequent_lines_prefix_len;
22042 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22043 current_line.push_str(token);
22044 current_line_len += grapheme_len;
22045 }
22046 }
22047 WordBreakToken::Newline => {
22048 in_whitespace = true;
22049 let current_prefix_len = if is_first_line {
22050 first_line_prefix_len
22051 } else {
22052 subsequent_lines_prefix_len
22053 };
22054 if preserve_existing_whitespace {
22055 wrapped_text.push_str(current_line.trim_end());
22056 wrapped_text.push('\n');
22057 is_first_line = false;
22058 current_line = subsequent_lines_prefix.clone();
22059 current_line_len = subsequent_lines_prefix_len;
22060 } else if have_preceding_whitespace {
22061 continue;
22062 } else if current_line_len + 1 > wrap_column
22063 && current_line_len != current_prefix_len
22064 {
22065 wrapped_text.push_str(current_line.trim_end());
22066 wrapped_text.push('\n');
22067 is_first_line = false;
22068 current_line = subsequent_lines_prefix.clone();
22069 current_line_len = subsequent_lines_prefix_len;
22070 } else if current_line_len != current_prefix_len {
22071 current_line.push(' ');
22072 current_line_len += 1;
22073 }
22074 }
22075 }
22076 }
22077
22078 if !current_line.is_empty() {
22079 wrapped_text.push_str(¤t_line);
22080 }
22081 wrapped_text
22082}
22083
22084#[test]
22085fn test_wrap_with_prefix() {
22086 assert_eq!(
22087 wrap_with_prefix(
22088 "# ".to_string(),
22089 "# ".to_string(),
22090 "abcdefg".to_string(),
22091 4,
22092 NonZeroU32::new(4).unwrap(),
22093 false,
22094 ),
22095 "# abcdefg"
22096 );
22097 assert_eq!(
22098 wrap_with_prefix(
22099 "".to_string(),
22100 "".to_string(),
22101 "\thello world".to_string(),
22102 8,
22103 NonZeroU32::new(4).unwrap(),
22104 false,
22105 ),
22106 "hello\nworld"
22107 );
22108 assert_eq!(
22109 wrap_with_prefix(
22110 "// ".to_string(),
22111 "// ".to_string(),
22112 "xx \nyy zz aa bb cc".to_string(),
22113 12,
22114 NonZeroU32::new(4).unwrap(),
22115 false,
22116 ),
22117 "// xx yy zz\n// aa bb cc"
22118 );
22119 assert_eq!(
22120 wrap_with_prefix(
22121 String::new(),
22122 String::new(),
22123 "这是什么 \n 钢笔".to_string(),
22124 3,
22125 NonZeroU32::new(4).unwrap(),
22126 false,
22127 ),
22128 "这是什\n么 钢\n笔"
22129 );
22130}
22131
22132pub trait CollaborationHub {
22133 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22134 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22135 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22136}
22137
22138impl CollaborationHub for Entity<Project> {
22139 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22140 self.read(cx).collaborators()
22141 }
22142
22143 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22144 self.read(cx).user_store().read(cx).participant_indices()
22145 }
22146
22147 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22148 let this = self.read(cx);
22149 let user_ids = this.collaborators().values().map(|c| c.user_id);
22150 this.user_store().read(cx).participant_names(user_ids, cx)
22151 }
22152}
22153
22154pub trait SemanticsProvider {
22155 fn hover(
22156 &self,
22157 buffer: &Entity<Buffer>,
22158 position: text::Anchor,
22159 cx: &mut App,
22160 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22161
22162 fn inline_values(
22163 &self,
22164 buffer_handle: Entity<Buffer>,
22165 range: Range<text::Anchor>,
22166 cx: &mut App,
22167 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22168
22169 fn inlay_hints(
22170 &self,
22171 buffer_handle: Entity<Buffer>,
22172 range: Range<text::Anchor>,
22173 cx: &mut App,
22174 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22175
22176 fn resolve_inlay_hint(
22177 &self,
22178 hint: InlayHint,
22179 buffer_handle: Entity<Buffer>,
22180 server_id: LanguageServerId,
22181 cx: &mut App,
22182 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22183
22184 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22185
22186 fn document_highlights(
22187 &self,
22188 buffer: &Entity<Buffer>,
22189 position: text::Anchor,
22190 cx: &mut App,
22191 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22192
22193 fn definitions(
22194 &self,
22195 buffer: &Entity<Buffer>,
22196 position: text::Anchor,
22197 kind: GotoDefinitionKind,
22198 cx: &mut App,
22199 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22200
22201 fn range_for_rename(
22202 &self,
22203 buffer: &Entity<Buffer>,
22204 position: text::Anchor,
22205 cx: &mut App,
22206 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22207
22208 fn perform_rename(
22209 &self,
22210 buffer: &Entity<Buffer>,
22211 position: text::Anchor,
22212 new_name: String,
22213 cx: &mut App,
22214 ) -> Option<Task<Result<ProjectTransaction>>>;
22215}
22216
22217pub trait CompletionProvider {
22218 fn completions(
22219 &self,
22220 excerpt_id: ExcerptId,
22221 buffer: &Entity<Buffer>,
22222 buffer_position: text::Anchor,
22223 trigger: CompletionContext,
22224 window: &mut Window,
22225 cx: &mut Context<Editor>,
22226 ) -> Task<Result<Vec<CompletionResponse>>>;
22227
22228 fn resolve_completions(
22229 &self,
22230 _buffer: Entity<Buffer>,
22231 _completion_indices: Vec<usize>,
22232 _completions: Rc<RefCell<Box<[Completion]>>>,
22233 _cx: &mut Context<Editor>,
22234 ) -> Task<Result<bool>> {
22235 Task::ready(Ok(false))
22236 }
22237
22238 fn apply_additional_edits_for_completion(
22239 &self,
22240 _buffer: Entity<Buffer>,
22241 _completions: Rc<RefCell<Box<[Completion]>>>,
22242 _completion_index: usize,
22243 _push_to_history: bool,
22244 _cx: &mut Context<Editor>,
22245 ) -> Task<Result<Option<language::Transaction>>> {
22246 Task::ready(Ok(None))
22247 }
22248
22249 fn is_completion_trigger(
22250 &self,
22251 buffer: &Entity<Buffer>,
22252 position: language::Anchor,
22253 text: &str,
22254 trigger_in_words: bool,
22255 menu_is_open: bool,
22256 cx: &mut Context<Editor>,
22257 ) -> bool;
22258
22259 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22260
22261 fn sort_completions(&self) -> bool {
22262 true
22263 }
22264
22265 fn filter_completions(&self) -> bool {
22266 true
22267 }
22268}
22269
22270pub trait CodeActionProvider {
22271 fn id(&self) -> Arc<str>;
22272
22273 fn code_actions(
22274 &self,
22275 buffer: &Entity<Buffer>,
22276 range: Range<text::Anchor>,
22277 window: &mut Window,
22278 cx: &mut App,
22279 ) -> Task<Result<Vec<CodeAction>>>;
22280
22281 fn apply_code_action(
22282 &self,
22283 buffer_handle: Entity<Buffer>,
22284 action: CodeAction,
22285 excerpt_id: ExcerptId,
22286 push_to_history: bool,
22287 window: &mut Window,
22288 cx: &mut App,
22289 ) -> Task<Result<ProjectTransaction>>;
22290}
22291
22292impl CodeActionProvider for Entity<Project> {
22293 fn id(&self) -> Arc<str> {
22294 "project".into()
22295 }
22296
22297 fn code_actions(
22298 &self,
22299 buffer: &Entity<Buffer>,
22300 range: Range<text::Anchor>,
22301 _window: &mut Window,
22302 cx: &mut App,
22303 ) -> Task<Result<Vec<CodeAction>>> {
22304 self.update(cx, |project, cx| {
22305 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22306 let code_actions = project.code_actions(buffer, range, None, cx);
22307 cx.background_spawn(async move {
22308 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22309 Ok(code_lens_actions
22310 .context("code lens fetch")?
22311 .into_iter()
22312 .flatten()
22313 .chain(
22314 code_actions
22315 .context("code action fetch")?
22316 .into_iter()
22317 .flatten(),
22318 )
22319 .collect())
22320 })
22321 })
22322 }
22323
22324 fn apply_code_action(
22325 &self,
22326 buffer_handle: Entity<Buffer>,
22327 action: CodeAction,
22328 _excerpt_id: ExcerptId,
22329 push_to_history: bool,
22330 _window: &mut Window,
22331 cx: &mut App,
22332 ) -> Task<Result<ProjectTransaction>> {
22333 self.update(cx, |project, cx| {
22334 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22335 })
22336 }
22337}
22338
22339fn snippet_completions(
22340 project: &Project,
22341 buffer: &Entity<Buffer>,
22342 buffer_position: text::Anchor,
22343 cx: &mut App,
22344) -> Task<Result<CompletionResponse>> {
22345 let languages = buffer.read(cx).languages_at(buffer_position);
22346 let snippet_store = project.snippets().read(cx);
22347
22348 let scopes: Vec<_> = languages
22349 .iter()
22350 .filter_map(|language| {
22351 let language_name = language.lsp_id();
22352 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22353
22354 if snippets.is_empty() {
22355 None
22356 } else {
22357 Some((language.default_scope(), snippets))
22358 }
22359 })
22360 .collect();
22361
22362 if scopes.is_empty() {
22363 return Task::ready(Ok(CompletionResponse {
22364 completions: vec![],
22365 display_options: CompletionDisplayOptions::default(),
22366 is_incomplete: false,
22367 }));
22368 }
22369
22370 let snapshot = buffer.read(cx).text_snapshot();
22371 let chars: String = snapshot
22372 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22373 .collect();
22374 let executor = cx.background_executor().clone();
22375
22376 cx.background_spawn(async move {
22377 let mut is_incomplete = false;
22378 let mut completions: Vec<Completion> = Vec::new();
22379 for (scope, snippets) in scopes.into_iter() {
22380 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
22381 let mut last_word = chars
22382 .chars()
22383 .take_while(|c| classifier.is_word(*c))
22384 .collect::<String>();
22385 last_word = last_word.chars().rev().collect();
22386
22387 if last_word.is_empty() {
22388 return Ok(CompletionResponse {
22389 completions: vec![],
22390 display_options: CompletionDisplayOptions::default(),
22391 is_incomplete: true,
22392 });
22393 }
22394
22395 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22396 let to_lsp = |point: &text::Anchor| {
22397 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22398 point_to_lsp(end)
22399 };
22400 let lsp_end = to_lsp(&buffer_position);
22401
22402 let candidates = snippets
22403 .iter()
22404 .enumerate()
22405 .flat_map(|(ix, snippet)| {
22406 snippet
22407 .prefix
22408 .iter()
22409 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22410 })
22411 .collect::<Vec<StringMatchCandidate>>();
22412
22413 const MAX_RESULTS: usize = 100;
22414 let mut matches = fuzzy::match_strings(
22415 &candidates,
22416 &last_word,
22417 last_word.chars().any(|c| c.is_uppercase()),
22418 true,
22419 MAX_RESULTS,
22420 &Default::default(),
22421 executor.clone(),
22422 )
22423 .await;
22424
22425 if matches.len() >= MAX_RESULTS {
22426 is_incomplete = true;
22427 }
22428
22429 // Remove all candidates where the query's start does not match the start of any word in the candidate
22430 if let Some(query_start) = last_word.chars().next() {
22431 matches.retain(|string_match| {
22432 split_words(&string_match.string).any(|word| {
22433 // Check that the first codepoint of the word as lowercase matches the first
22434 // codepoint of the query as lowercase
22435 word.chars()
22436 .flat_map(|codepoint| codepoint.to_lowercase())
22437 .zip(query_start.to_lowercase())
22438 .all(|(word_cp, query_cp)| word_cp == query_cp)
22439 })
22440 });
22441 }
22442
22443 let matched_strings = matches
22444 .into_iter()
22445 .map(|m| m.string)
22446 .collect::<HashSet<_>>();
22447
22448 completions.extend(snippets.iter().filter_map(|snippet| {
22449 let matching_prefix = snippet
22450 .prefix
22451 .iter()
22452 .find(|prefix| matched_strings.contains(*prefix))?;
22453 let start = as_offset - last_word.len();
22454 let start = snapshot.anchor_before(start);
22455 let range = start..buffer_position;
22456 let lsp_start = to_lsp(&start);
22457 let lsp_range = lsp::Range {
22458 start: lsp_start,
22459 end: lsp_end,
22460 };
22461 Some(Completion {
22462 replace_range: range,
22463 new_text: snippet.body.clone(),
22464 source: CompletionSource::Lsp {
22465 insert_range: None,
22466 server_id: LanguageServerId(usize::MAX),
22467 resolved: true,
22468 lsp_completion: Box::new(lsp::CompletionItem {
22469 label: snippet.prefix.first().unwrap().clone(),
22470 kind: Some(CompletionItemKind::SNIPPET),
22471 label_details: snippet.description.as_ref().map(|description| {
22472 lsp::CompletionItemLabelDetails {
22473 detail: Some(description.clone()),
22474 description: None,
22475 }
22476 }),
22477 insert_text_format: Some(InsertTextFormat::SNIPPET),
22478 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22479 lsp::InsertReplaceEdit {
22480 new_text: snippet.body.clone(),
22481 insert: lsp_range,
22482 replace: lsp_range,
22483 },
22484 )),
22485 filter_text: Some(snippet.body.clone()),
22486 sort_text: Some(char::MAX.to_string()),
22487 ..lsp::CompletionItem::default()
22488 }),
22489 lsp_defaults: None,
22490 },
22491 label: CodeLabel {
22492 text: matching_prefix.clone(),
22493 runs: Vec::new(),
22494 filter_range: 0..matching_prefix.len(),
22495 },
22496 icon_path: None,
22497 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22498 single_line: snippet.name.clone().into(),
22499 plain_text: snippet
22500 .description
22501 .clone()
22502 .map(|description| description.into()),
22503 }),
22504 insert_text_mode: None,
22505 confirm: None,
22506 })
22507 }))
22508 }
22509
22510 Ok(CompletionResponse {
22511 completions,
22512 display_options: CompletionDisplayOptions::default(),
22513 is_incomplete,
22514 })
22515 })
22516}
22517
22518impl CompletionProvider for Entity<Project> {
22519 fn completions(
22520 &self,
22521 _excerpt_id: ExcerptId,
22522 buffer: &Entity<Buffer>,
22523 buffer_position: text::Anchor,
22524 options: CompletionContext,
22525 _window: &mut Window,
22526 cx: &mut Context<Editor>,
22527 ) -> Task<Result<Vec<CompletionResponse>>> {
22528 self.update(cx, |project, cx| {
22529 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22530 let project_completions = project.completions(buffer, buffer_position, options, cx);
22531 cx.background_spawn(async move {
22532 let mut responses = project_completions.await?;
22533 let snippets = snippets.await?;
22534 if !snippets.completions.is_empty() {
22535 responses.push(snippets);
22536 }
22537 Ok(responses)
22538 })
22539 })
22540 }
22541
22542 fn resolve_completions(
22543 &self,
22544 buffer: Entity<Buffer>,
22545 completion_indices: Vec<usize>,
22546 completions: Rc<RefCell<Box<[Completion]>>>,
22547 cx: &mut Context<Editor>,
22548 ) -> Task<Result<bool>> {
22549 self.update(cx, |project, cx| {
22550 project.lsp_store().update(cx, |lsp_store, cx| {
22551 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22552 })
22553 })
22554 }
22555
22556 fn apply_additional_edits_for_completion(
22557 &self,
22558 buffer: Entity<Buffer>,
22559 completions: Rc<RefCell<Box<[Completion]>>>,
22560 completion_index: usize,
22561 push_to_history: bool,
22562 cx: &mut Context<Editor>,
22563 ) -> Task<Result<Option<language::Transaction>>> {
22564 self.update(cx, |project, cx| {
22565 project.lsp_store().update(cx, |lsp_store, cx| {
22566 lsp_store.apply_additional_edits_for_completion(
22567 buffer,
22568 completions,
22569 completion_index,
22570 push_to_history,
22571 cx,
22572 )
22573 })
22574 })
22575 }
22576
22577 fn is_completion_trigger(
22578 &self,
22579 buffer: &Entity<Buffer>,
22580 position: language::Anchor,
22581 text: &str,
22582 trigger_in_words: bool,
22583 menu_is_open: bool,
22584 cx: &mut Context<Editor>,
22585 ) -> bool {
22586 let mut chars = text.chars();
22587 let char = if let Some(char) = chars.next() {
22588 char
22589 } else {
22590 return false;
22591 };
22592 if chars.next().is_some() {
22593 return false;
22594 }
22595
22596 let buffer = buffer.read(cx);
22597 let snapshot = buffer.snapshot();
22598 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22599 return false;
22600 }
22601 let classifier = snapshot.char_classifier_at(position).for_completion(true);
22602 if trigger_in_words && classifier.is_word(char) {
22603 return true;
22604 }
22605
22606 buffer.completion_triggers().contains(text)
22607 }
22608}
22609
22610impl SemanticsProvider for Entity<Project> {
22611 fn hover(
22612 &self,
22613 buffer: &Entity<Buffer>,
22614 position: text::Anchor,
22615 cx: &mut App,
22616 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22617 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22618 }
22619
22620 fn document_highlights(
22621 &self,
22622 buffer: &Entity<Buffer>,
22623 position: text::Anchor,
22624 cx: &mut App,
22625 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22626 Some(self.update(cx, |project, cx| {
22627 project.document_highlights(buffer, position, cx)
22628 }))
22629 }
22630
22631 fn definitions(
22632 &self,
22633 buffer: &Entity<Buffer>,
22634 position: text::Anchor,
22635 kind: GotoDefinitionKind,
22636 cx: &mut App,
22637 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22638 Some(self.update(cx, |project, cx| match kind {
22639 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22640 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22641 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22642 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22643 }))
22644 }
22645
22646 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22647 self.update(cx, |project, cx| {
22648 if project
22649 .active_debug_session(cx)
22650 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22651 {
22652 return true;
22653 }
22654
22655 buffer.update(cx, |buffer, cx| {
22656 project.any_language_server_supports_inlay_hints(buffer, cx)
22657 })
22658 })
22659 }
22660
22661 fn inline_values(
22662 &self,
22663 buffer_handle: Entity<Buffer>,
22664 range: Range<text::Anchor>,
22665 cx: &mut App,
22666 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22667 self.update(cx, |project, cx| {
22668 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22669
22670 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22671 })
22672 }
22673
22674 fn inlay_hints(
22675 &self,
22676 buffer_handle: Entity<Buffer>,
22677 range: Range<text::Anchor>,
22678 cx: &mut App,
22679 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22680 Some(self.update(cx, |project, cx| {
22681 project.inlay_hints(buffer_handle, range, cx)
22682 }))
22683 }
22684
22685 fn resolve_inlay_hint(
22686 &self,
22687 hint: InlayHint,
22688 buffer_handle: Entity<Buffer>,
22689 server_id: LanguageServerId,
22690 cx: &mut App,
22691 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22692 Some(self.update(cx, |project, cx| {
22693 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22694 }))
22695 }
22696
22697 fn range_for_rename(
22698 &self,
22699 buffer: &Entity<Buffer>,
22700 position: text::Anchor,
22701 cx: &mut App,
22702 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22703 Some(self.update(cx, |project, cx| {
22704 let buffer = buffer.clone();
22705 let task = project.prepare_rename(buffer.clone(), position, cx);
22706 cx.spawn(async move |_, cx| {
22707 Ok(match task.await? {
22708 PrepareRenameResponse::Success(range) => Some(range),
22709 PrepareRenameResponse::InvalidPosition => None,
22710 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22711 // Fallback on using TreeSitter info to determine identifier range
22712 buffer.read_with(cx, |buffer, _| {
22713 let snapshot = buffer.snapshot();
22714 let (range, kind) = snapshot.surrounding_word(position, false);
22715 if kind != Some(CharKind::Word) {
22716 return None;
22717 }
22718 Some(
22719 snapshot.anchor_before(range.start)
22720 ..snapshot.anchor_after(range.end),
22721 )
22722 })?
22723 }
22724 })
22725 })
22726 }))
22727 }
22728
22729 fn perform_rename(
22730 &self,
22731 buffer: &Entity<Buffer>,
22732 position: text::Anchor,
22733 new_name: String,
22734 cx: &mut App,
22735 ) -> Option<Task<Result<ProjectTransaction>>> {
22736 Some(self.update(cx, |project, cx| {
22737 project.perform_rename(buffer.clone(), position, new_name, cx)
22738 }))
22739 }
22740}
22741
22742fn inlay_hint_settings(
22743 location: Anchor,
22744 snapshot: &MultiBufferSnapshot,
22745 cx: &mut Context<Editor>,
22746) -> InlayHintSettings {
22747 let file = snapshot.file_at(location);
22748 let language = snapshot.language_at(location).map(|l| l.name());
22749 language_settings(language, file, cx).inlay_hints
22750}
22751
22752fn consume_contiguous_rows(
22753 contiguous_row_selections: &mut Vec<Selection<Point>>,
22754 selection: &Selection<Point>,
22755 display_map: &DisplaySnapshot,
22756 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
22757) -> (MultiBufferRow, MultiBufferRow) {
22758 contiguous_row_selections.push(selection.clone());
22759 let start_row = starting_row(selection, display_map);
22760 let mut end_row = ending_row(selection, display_map);
22761
22762 while let Some(next_selection) = selections.peek() {
22763 if next_selection.start.row <= end_row.0 {
22764 end_row = ending_row(next_selection, display_map);
22765 contiguous_row_selections.push(selections.next().unwrap().clone());
22766 } else {
22767 break;
22768 }
22769 }
22770 (start_row, end_row)
22771}
22772
22773fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22774 if selection.start.column > 0 {
22775 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
22776 } else {
22777 MultiBufferRow(selection.start.row)
22778 }
22779}
22780
22781fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
22782 if next_selection.end.column > 0 || next_selection.is_empty() {
22783 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
22784 } else {
22785 MultiBufferRow(next_selection.end.row)
22786 }
22787}
22788
22789impl EditorSnapshot {
22790 pub fn remote_selections_in_range<'a>(
22791 &'a self,
22792 range: &'a Range<Anchor>,
22793 collaboration_hub: &dyn CollaborationHub,
22794 cx: &'a App,
22795 ) -> impl 'a + Iterator<Item = RemoteSelection> {
22796 let participant_names = collaboration_hub.user_names(cx);
22797 let participant_indices = collaboration_hub.user_participant_indices(cx);
22798 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
22799 let collaborators_by_replica_id = collaborators_by_peer_id
22800 .values()
22801 .map(|collaborator| (collaborator.replica_id, collaborator))
22802 .collect::<HashMap<_, _>>();
22803 self.buffer_snapshot
22804 .selections_in_range(range, false)
22805 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
22806 if replica_id == AGENT_REPLICA_ID {
22807 Some(RemoteSelection {
22808 replica_id,
22809 selection,
22810 cursor_shape,
22811 line_mode,
22812 collaborator_id: CollaboratorId::Agent,
22813 user_name: Some("Agent".into()),
22814 color: cx.theme().players().agent(),
22815 })
22816 } else {
22817 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22818 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22819 let user_name = participant_names.get(&collaborator.user_id).cloned();
22820 Some(RemoteSelection {
22821 replica_id,
22822 selection,
22823 cursor_shape,
22824 line_mode,
22825 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22826 user_name,
22827 color: if let Some(index) = participant_index {
22828 cx.theme().players().color_for_participant(index.0)
22829 } else {
22830 cx.theme().players().absent()
22831 },
22832 })
22833 }
22834 })
22835 }
22836
22837 pub fn hunks_for_ranges(
22838 &self,
22839 ranges: impl IntoIterator<Item = Range<Point>>,
22840 ) -> Vec<MultiBufferDiffHunk> {
22841 let mut hunks = Vec::new();
22842 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22843 HashMap::default();
22844 for query_range in ranges {
22845 let query_rows =
22846 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22847 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22848 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22849 ) {
22850 // Include deleted hunks that are adjacent to the query range, because
22851 // otherwise they would be missed.
22852 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22853 if hunk.status().is_deleted() {
22854 intersects_range |= hunk.row_range.start == query_rows.end;
22855 intersects_range |= hunk.row_range.end == query_rows.start;
22856 }
22857 if intersects_range {
22858 if !processed_buffer_rows
22859 .entry(hunk.buffer_id)
22860 .or_default()
22861 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22862 {
22863 continue;
22864 }
22865 hunks.push(hunk);
22866 }
22867 }
22868 }
22869
22870 hunks
22871 }
22872
22873 fn display_diff_hunks_for_rows<'a>(
22874 &'a self,
22875 display_rows: Range<DisplayRow>,
22876 folded_buffers: &'a HashSet<BufferId>,
22877 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22878 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22879 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22880
22881 self.buffer_snapshot
22882 .diff_hunks_in_range(buffer_start..buffer_end)
22883 .filter_map(|hunk| {
22884 if folded_buffers.contains(&hunk.buffer_id) {
22885 return None;
22886 }
22887
22888 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22889 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22890
22891 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22892 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22893
22894 let display_hunk = if hunk_display_start.column() != 0 {
22895 DisplayDiffHunk::Folded {
22896 display_row: hunk_display_start.row(),
22897 }
22898 } else {
22899 let mut end_row = hunk_display_end.row();
22900 if hunk_display_end.column() > 0 {
22901 end_row.0 += 1;
22902 }
22903 let is_created_file = hunk.is_created_file();
22904 DisplayDiffHunk::Unfolded {
22905 status: hunk.status(),
22906 diff_base_byte_range: hunk.diff_base_byte_range,
22907 display_row_range: hunk_display_start.row()..end_row,
22908 multi_buffer_range: Anchor::range_in_buffer(
22909 hunk.excerpt_id,
22910 hunk.buffer_id,
22911 hunk.buffer_range,
22912 ),
22913 is_created_file,
22914 }
22915 };
22916
22917 Some(display_hunk)
22918 })
22919 }
22920
22921 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22922 self.display_snapshot.buffer_snapshot.language_at(position)
22923 }
22924
22925 pub fn is_focused(&self) -> bool {
22926 self.is_focused
22927 }
22928
22929 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22930 self.placeholder_text.as_ref()
22931 }
22932
22933 pub fn scroll_position(&self) -> gpui::Point<f32> {
22934 self.scroll_anchor.scroll_position(&self.display_snapshot)
22935 }
22936
22937 fn gutter_dimensions(
22938 &self,
22939 font_id: FontId,
22940 font_size: Pixels,
22941 max_line_number_width: Pixels,
22942 cx: &App,
22943 ) -> Option<GutterDimensions> {
22944 if !self.show_gutter {
22945 return None;
22946 }
22947
22948 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22949 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22950
22951 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22952 matches!(
22953 ProjectSettings::get_global(cx).git.git_gutter,
22954 Some(GitGutterSetting::TrackedFiles)
22955 )
22956 });
22957 let gutter_settings = EditorSettings::get_global(cx).gutter;
22958 let show_line_numbers = self
22959 .show_line_numbers
22960 .unwrap_or(gutter_settings.line_numbers);
22961 let line_gutter_width = if show_line_numbers {
22962 // Avoid flicker-like gutter resizes when the line number gains another digit by
22963 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22964 let min_width_for_number_on_gutter =
22965 ch_advance * gutter_settings.min_line_number_digits as f32;
22966 max_line_number_width.max(min_width_for_number_on_gutter)
22967 } else {
22968 0.0.into()
22969 };
22970
22971 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22972 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22973
22974 let git_blame_entries_width =
22975 self.git_blame_gutter_max_author_length
22976 .map(|max_author_length| {
22977 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22978 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22979
22980 /// The number of characters to dedicate to gaps and margins.
22981 const SPACING_WIDTH: usize = 4;
22982
22983 let max_char_count = max_author_length.min(renderer.max_author_length())
22984 + ::git::SHORT_SHA_LENGTH
22985 + MAX_RELATIVE_TIMESTAMP.len()
22986 + SPACING_WIDTH;
22987
22988 ch_advance * max_char_count
22989 });
22990
22991 let is_singleton = self.buffer_snapshot.is_singleton();
22992
22993 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22994 left_padding += if !is_singleton {
22995 ch_width * 4.0
22996 } else if show_runnables || show_breakpoints {
22997 ch_width * 3.0
22998 } else if show_git_gutter && show_line_numbers {
22999 ch_width * 2.0
23000 } else if show_git_gutter || show_line_numbers {
23001 ch_width
23002 } else {
23003 px(0.)
23004 };
23005
23006 let shows_folds = is_singleton && gutter_settings.folds;
23007
23008 let right_padding = if shows_folds && show_line_numbers {
23009 ch_width * 4.0
23010 } else if shows_folds || (!is_singleton && show_line_numbers) {
23011 ch_width * 3.0
23012 } else if show_line_numbers {
23013 ch_width
23014 } else {
23015 px(0.)
23016 };
23017
23018 Some(GutterDimensions {
23019 left_padding,
23020 right_padding,
23021 width: line_gutter_width + left_padding + right_padding,
23022 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23023 git_blame_entries_width,
23024 })
23025 }
23026
23027 pub fn render_crease_toggle(
23028 &self,
23029 buffer_row: MultiBufferRow,
23030 row_contains_cursor: bool,
23031 editor: Entity<Editor>,
23032 window: &mut Window,
23033 cx: &mut App,
23034 ) -> Option<AnyElement> {
23035 let folded = self.is_line_folded(buffer_row);
23036 let mut is_foldable = false;
23037
23038 if let Some(crease) = self
23039 .crease_snapshot
23040 .query_row(buffer_row, &self.buffer_snapshot)
23041 {
23042 is_foldable = true;
23043 match crease {
23044 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23045 if let Some(render_toggle) = render_toggle {
23046 let toggle_callback =
23047 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23048 if folded {
23049 editor.update(cx, |editor, cx| {
23050 editor.fold_at(buffer_row, window, cx)
23051 });
23052 } else {
23053 editor.update(cx, |editor, cx| {
23054 editor.unfold_at(buffer_row, window, cx)
23055 });
23056 }
23057 });
23058 return Some((render_toggle)(
23059 buffer_row,
23060 folded,
23061 toggle_callback,
23062 window,
23063 cx,
23064 ));
23065 }
23066 }
23067 }
23068 }
23069
23070 is_foldable |= self.starts_indent(buffer_row);
23071
23072 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23073 Some(
23074 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23075 .toggle_state(folded)
23076 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23077 if folded {
23078 this.unfold_at(buffer_row, window, cx);
23079 } else {
23080 this.fold_at(buffer_row, window, cx);
23081 }
23082 }))
23083 .into_any_element(),
23084 )
23085 } else {
23086 None
23087 }
23088 }
23089
23090 pub fn render_crease_trailer(
23091 &self,
23092 buffer_row: MultiBufferRow,
23093 window: &mut Window,
23094 cx: &mut App,
23095 ) -> Option<AnyElement> {
23096 let folded = self.is_line_folded(buffer_row);
23097 if let Crease::Inline { render_trailer, .. } = self
23098 .crease_snapshot
23099 .query_row(buffer_row, &self.buffer_snapshot)?
23100 {
23101 let render_trailer = render_trailer.as_ref()?;
23102 Some(render_trailer(buffer_row, folded, window, cx))
23103 } else {
23104 None
23105 }
23106 }
23107}
23108
23109impl Deref for EditorSnapshot {
23110 type Target = DisplaySnapshot;
23111
23112 fn deref(&self) -> &Self::Target {
23113 &self.display_snapshot
23114 }
23115}
23116
23117#[derive(Clone, Debug, PartialEq, Eq)]
23118pub enum EditorEvent {
23119 InputIgnored {
23120 text: Arc<str>,
23121 },
23122 InputHandled {
23123 utf16_range_to_replace: Option<Range<isize>>,
23124 text: Arc<str>,
23125 },
23126 ExcerptsAdded {
23127 buffer: Entity<Buffer>,
23128 predecessor: ExcerptId,
23129 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23130 },
23131 ExcerptsRemoved {
23132 ids: Vec<ExcerptId>,
23133 removed_buffer_ids: Vec<BufferId>,
23134 },
23135 BufferFoldToggled {
23136 ids: Vec<ExcerptId>,
23137 folded: bool,
23138 },
23139 ExcerptsEdited {
23140 ids: Vec<ExcerptId>,
23141 },
23142 ExcerptsExpanded {
23143 ids: Vec<ExcerptId>,
23144 },
23145 BufferEdited,
23146 Edited {
23147 transaction_id: clock::Lamport,
23148 },
23149 Reparsed(BufferId),
23150 Focused,
23151 FocusedIn,
23152 Blurred,
23153 DirtyChanged,
23154 Saved,
23155 TitleChanged,
23156 SelectionsChanged {
23157 local: bool,
23158 },
23159 ScrollPositionChanged {
23160 local: bool,
23161 autoscroll: bool,
23162 },
23163 TransactionUndone {
23164 transaction_id: clock::Lamport,
23165 },
23166 TransactionBegun {
23167 transaction_id: clock::Lamport,
23168 },
23169 CursorShapeChanged,
23170 BreadcrumbsChanged,
23171 PushedToNavHistory {
23172 anchor: Anchor,
23173 is_deactivate: bool,
23174 },
23175}
23176
23177impl EventEmitter<EditorEvent> for Editor {}
23178
23179impl Focusable for Editor {
23180 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23181 self.focus_handle.clone()
23182 }
23183}
23184
23185impl Render for Editor {
23186 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23187 let settings = ThemeSettings::get_global(cx);
23188
23189 let mut text_style = match self.mode {
23190 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23191 color: cx.theme().colors().editor_foreground,
23192 font_family: settings.ui_font.family.clone(),
23193 font_features: settings.ui_font.features.clone(),
23194 font_fallbacks: settings.ui_font.fallbacks.clone(),
23195 font_size: rems(0.875).into(),
23196 font_weight: settings.ui_font.weight,
23197 line_height: relative(settings.buffer_line_height.value()),
23198 ..Default::default()
23199 },
23200 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23201 color: cx.theme().colors().editor_foreground,
23202 font_family: settings.buffer_font.family.clone(),
23203 font_features: settings.buffer_font.features.clone(),
23204 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23205 font_size: settings.buffer_font_size(cx).into(),
23206 font_weight: settings.buffer_font.weight,
23207 line_height: relative(settings.buffer_line_height.value()),
23208 ..Default::default()
23209 },
23210 };
23211 if let Some(text_style_refinement) = &self.text_style_refinement {
23212 text_style.refine(text_style_refinement)
23213 }
23214
23215 let background = match self.mode {
23216 EditorMode::SingleLine => cx.theme().system().transparent,
23217 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23218 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23219 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23220 };
23221
23222 EditorElement::new(
23223 &cx.entity(),
23224 EditorStyle {
23225 background,
23226 border: cx.theme().colors().border,
23227 local_player: cx.theme().players().local(),
23228 text: text_style,
23229 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23230 syntax: cx.theme().syntax().clone(),
23231 status: cx.theme().status().clone(),
23232 inlay_hints_style: make_inlay_hints_style(cx),
23233 edit_prediction_styles: make_suggestion_styles(cx),
23234 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23235 show_underlines: self.diagnostics_enabled(),
23236 },
23237 )
23238 }
23239}
23240
23241impl EntityInputHandler for Editor {
23242 fn text_for_range(
23243 &mut self,
23244 range_utf16: Range<usize>,
23245 adjusted_range: &mut Option<Range<usize>>,
23246 _: &mut Window,
23247 cx: &mut Context<Self>,
23248 ) -> Option<String> {
23249 let snapshot = self.buffer.read(cx).read(cx);
23250 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23251 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23252 if (start.0..end.0) != range_utf16 {
23253 adjusted_range.replace(start.0..end.0);
23254 }
23255 Some(snapshot.text_for_range(start..end).collect())
23256 }
23257
23258 fn selected_text_range(
23259 &mut self,
23260 ignore_disabled_input: bool,
23261 _: &mut Window,
23262 cx: &mut Context<Self>,
23263 ) -> Option<UTF16Selection> {
23264 // Prevent the IME menu from appearing when holding down an alphabetic key
23265 // while input is disabled.
23266 if !ignore_disabled_input && !self.input_enabled {
23267 return None;
23268 }
23269
23270 let selection = self.selections.newest::<OffsetUtf16>(cx);
23271 let range = selection.range();
23272
23273 Some(UTF16Selection {
23274 range: range.start.0..range.end.0,
23275 reversed: selection.reversed,
23276 })
23277 }
23278
23279 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23280 let snapshot = self.buffer.read(cx).read(cx);
23281 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23282 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23283 }
23284
23285 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23286 self.clear_highlights::<InputComposition>(cx);
23287 self.ime_transaction.take();
23288 }
23289
23290 fn replace_text_in_range(
23291 &mut self,
23292 range_utf16: Option<Range<usize>>,
23293 text: &str,
23294 window: &mut Window,
23295 cx: &mut Context<Self>,
23296 ) {
23297 if !self.input_enabled {
23298 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23299 return;
23300 }
23301
23302 self.transact(window, cx, |this, window, cx| {
23303 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23304 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23305 Some(this.selection_replacement_ranges(range_utf16, cx))
23306 } else {
23307 this.marked_text_ranges(cx)
23308 };
23309
23310 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23311 let newest_selection_id = this.selections.newest_anchor().id;
23312 this.selections
23313 .all::<OffsetUtf16>(cx)
23314 .iter()
23315 .zip(ranges_to_replace.iter())
23316 .find_map(|(selection, range)| {
23317 if selection.id == newest_selection_id {
23318 Some(
23319 (range.start.0 as isize - selection.head().0 as isize)
23320 ..(range.end.0 as isize - selection.head().0 as isize),
23321 )
23322 } else {
23323 None
23324 }
23325 })
23326 });
23327
23328 cx.emit(EditorEvent::InputHandled {
23329 utf16_range_to_replace: range_to_replace,
23330 text: text.into(),
23331 });
23332
23333 if let Some(new_selected_ranges) = new_selected_ranges {
23334 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23335 selections.select_ranges(new_selected_ranges)
23336 });
23337 this.backspace(&Default::default(), window, cx);
23338 }
23339
23340 this.handle_input(text, window, cx);
23341 });
23342
23343 if let Some(transaction) = self.ime_transaction {
23344 self.buffer.update(cx, |buffer, cx| {
23345 buffer.group_until_transaction(transaction, cx);
23346 });
23347 }
23348
23349 self.unmark_text(window, cx);
23350 }
23351
23352 fn replace_and_mark_text_in_range(
23353 &mut self,
23354 range_utf16: Option<Range<usize>>,
23355 text: &str,
23356 new_selected_range_utf16: Option<Range<usize>>,
23357 window: &mut Window,
23358 cx: &mut Context<Self>,
23359 ) {
23360 if !self.input_enabled {
23361 return;
23362 }
23363
23364 let transaction = self.transact(window, cx, |this, window, cx| {
23365 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23366 let snapshot = this.buffer.read(cx).read(cx);
23367 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23368 for marked_range in &mut marked_ranges {
23369 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23370 marked_range.start.0 += relative_range_utf16.start;
23371 marked_range.start =
23372 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23373 marked_range.end =
23374 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23375 }
23376 }
23377 Some(marked_ranges)
23378 } else if let Some(range_utf16) = range_utf16 {
23379 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23380 Some(this.selection_replacement_ranges(range_utf16, cx))
23381 } else {
23382 None
23383 };
23384
23385 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23386 let newest_selection_id = this.selections.newest_anchor().id;
23387 this.selections
23388 .all::<OffsetUtf16>(cx)
23389 .iter()
23390 .zip(ranges_to_replace.iter())
23391 .find_map(|(selection, range)| {
23392 if selection.id == newest_selection_id {
23393 Some(
23394 (range.start.0 as isize - selection.head().0 as isize)
23395 ..(range.end.0 as isize - selection.head().0 as isize),
23396 )
23397 } else {
23398 None
23399 }
23400 })
23401 });
23402
23403 cx.emit(EditorEvent::InputHandled {
23404 utf16_range_to_replace: range_to_replace,
23405 text: text.into(),
23406 });
23407
23408 if let Some(ranges) = ranges_to_replace {
23409 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23410 s.select_ranges(ranges)
23411 });
23412 }
23413
23414 let marked_ranges = {
23415 let snapshot = this.buffer.read(cx).read(cx);
23416 this.selections
23417 .disjoint_anchors()
23418 .iter()
23419 .map(|selection| {
23420 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23421 })
23422 .collect::<Vec<_>>()
23423 };
23424
23425 if text.is_empty() {
23426 this.unmark_text(window, cx);
23427 } else {
23428 this.highlight_text::<InputComposition>(
23429 marked_ranges.clone(),
23430 HighlightStyle {
23431 underline: Some(UnderlineStyle {
23432 thickness: px(1.),
23433 color: None,
23434 wavy: false,
23435 }),
23436 ..Default::default()
23437 },
23438 cx,
23439 );
23440 }
23441
23442 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23443 let use_autoclose = this.use_autoclose;
23444 let use_auto_surround = this.use_auto_surround;
23445 this.set_use_autoclose(false);
23446 this.set_use_auto_surround(false);
23447 this.handle_input(text, window, cx);
23448 this.set_use_autoclose(use_autoclose);
23449 this.set_use_auto_surround(use_auto_surround);
23450
23451 if let Some(new_selected_range) = new_selected_range_utf16 {
23452 let snapshot = this.buffer.read(cx).read(cx);
23453 let new_selected_ranges = marked_ranges
23454 .into_iter()
23455 .map(|marked_range| {
23456 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23457 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23458 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23459 snapshot.clip_offset_utf16(new_start, Bias::Left)
23460 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23461 })
23462 .collect::<Vec<_>>();
23463
23464 drop(snapshot);
23465 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23466 selections.select_ranges(new_selected_ranges)
23467 });
23468 }
23469 });
23470
23471 self.ime_transaction = self.ime_transaction.or(transaction);
23472 if let Some(transaction) = self.ime_transaction {
23473 self.buffer.update(cx, |buffer, cx| {
23474 buffer.group_until_transaction(transaction, cx);
23475 });
23476 }
23477
23478 if self.text_highlights::<InputComposition>(cx).is_none() {
23479 self.ime_transaction.take();
23480 }
23481 }
23482
23483 fn bounds_for_range(
23484 &mut self,
23485 range_utf16: Range<usize>,
23486 element_bounds: gpui::Bounds<Pixels>,
23487 window: &mut Window,
23488 cx: &mut Context<Self>,
23489 ) -> Option<gpui::Bounds<Pixels>> {
23490 let text_layout_details = self.text_layout_details(window);
23491 let CharacterDimensions {
23492 em_width,
23493 em_advance,
23494 line_height,
23495 } = self.character_dimensions(window);
23496
23497 let snapshot = self.snapshot(window, cx);
23498 let scroll_position = snapshot.scroll_position();
23499 let scroll_left = scroll_position.x * em_advance;
23500
23501 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23502 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23503 + self.gutter_dimensions.full_width();
23504 let y = line_height * (start.row().as_f32() - scroll_position.y);
23505
23506 Some(Bounds {
23507 origin: element_bounds.origin + point(x, y),
23508 size: size(em_width, line_height),
23509 })
23510 }
23511
23512 fn character_index_for_point(
23513 &mut self,
23514 point: gpui::Point<Pixels>,
23515 _window: &mut Window,
23516 _cx: &mut Context<Self>,
23517 ) -> Option<usize> {
23518 let position_map = self.last_position_map.as_ref()?;
23519 if !position_map.text_hitbox.contains(&point) {
23520 return None;
23521 }
23522 let display_point = position_map.point_for_position(point).previous_valid;
23523 let anchor = position_map
23524 .snapshot
23525 .display_point_to_anchor(display_point, Bias::Left);
23526 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23527 Some(utf16_offset.0)
23528 }
23529}
23530
23531trait SelectionExt {
23532 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23533 fn spanned_rows(
23534 &self,
23535 include_end_if_at_line_start: bool,
23536 map: &DisplaySnapshot,
23537 ) -> Range<MultiBufferRow>;
23538}
23539
23540impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23541 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23542 let start = self
23543 .start
23544 .to_point(&map.buffer_snapshot)
23545 .to_display_point(map);
23546 let end = self
23547 .end
23548 .to_point(&map.buffer_snapshot)
23549 .to_display_point(map);
23550 if self.reversed {
23551 end..start
23552 } else {
23553 start..end
23554 }
23555 }
23556
23557 fn spanned_rows(
23558 &self,
23559 include_end_if_at_line_start: bool,
23560 map: &DisplaySnapshot,
23561 ) -> Range<MultiBufferRow> {
23562 let start = self.start.to_point(&map.buffer_snapshot);
23563 let mut end = self.end.to_point(&map.buffer_snapshot);
23564 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23565 end.row -= 1;
23566 }
23567
23568 let buffer_start = map.prev_line_boundary(start).0;
23569 let buffer_end = map.next_line_boundary(end).0;
23570 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23571 }
23572}
23573
23574impl<T: InvalidationRegion> InvalidationStack<T> {
23575 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23576 where
23577 S: Clone + ToOffset,
23578 {
23579 while let Some(region) = self.last() {
23580 let all_selections_inside_invalidation_ranges =
23581 if selections.len() == region.ranges().len() {
23582 selections
23583 .iter()
23584 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23585 .all(|(selection, invalidation_range)| {
23586 let head = selection.head().to_offset(buffer);
23587 invalidation_range.start <= head && invalidation_range.end >= head
23588 })
23589 } else {
23590 false
23591 };
23592
23593 if all_selections_inside_invalidation_ranges {
23594 break;
23595 } else {
23596 self.pop();
23597 }
23598 }
23599 }
23600}
23601
23602impl<T> Default for InvalidationStack<T> {
23603 fn default() -> Self {
23604 Self(Default::default())
23605 }
23606}
23607
23608impl<T> Deref for InvalidationStack<T> {
23609 type Target = Vec<T>;
23610
23611 fn deref(&self) -> &Self::Target {
23612 &self.0
23613 }
23614}
23615
23616impl<T> DerefMut for InvalidationStack<T> {
23617 fn deref_mut(&mut self) -> &mut Self::Target {
23618 &mut self.0
23619 }
23620}
23621
23622impl InvalidationRegion for SnippetState {
23623 fn ranges(&self) -> &[Range<Anchor>] {
23624 &self.ranges[self.active_index]
23625 }
23626}
23627
23628fn edit_prediction_edit_text(
23629 current_snapshot: &BufferSnapshot,
23630 edits: &[(Range<Anchor>, String)],
23631 edit_preview: &EditPreview,
23632 include_deletions: bool,
23633 cx: &App,
23634) -> HighlightedText {
23635 let edits = edits
23636 .iter()
23637 .map(|(anchor, text)| {
23638 (
23639 anchor.start.text_anchor..anchor.end.text_anchor,
23640 text.clone(),
23641 )
23642 })
23643 .collect::<Vec<_>>();
23644
23645 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23646}
23647
23648fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23649 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23650 // Just show the raw edit text with basic styling
23651 let mut text = String::new();
23652 let mut highlights = Vec::new();
23653
23654 let insertion_highlight_style = HighlightStyle {
23655 color: Some(cx.theme().colors().text),
23656 ..Default::default()
23657 };
23658
23659 for (_, edit_text) in edits {
23660 let start_offset = text.len();
23661 text.push_str(edit_text);
23662 let end_offset = text.len();
23663
23664 if start_offset < end_offset {
23665 highlights.push((start_offset..end_offset, insertion_highlight_style));
23666 }
23667 }
23668
23669 HighlightedText {
23670 text: text.into(),
23671 highlights,
23672 }
23673}
23674
23675pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23676 match severity {
23677 lsp::DiagnosticSeverity::ERROR => colors.error,
23678 lsp::DiagnosticSeverity::WARNING => colors.warning,
23679 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23680 lsp::DiagnosticSeverity::HINT => colors.info,
23681 _ => colors.ignored,
23682 }
23683}
23684
23685pub fn styled_runs_for_code_label<'a>(
23686 label: &'a CodeLabel,
23687 syntax_theme: &'a theme::SyntaxTheme,
23688) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23689 let fade_out = HighlightStyle {
23690 fade_out: Some(0.35),
23691 ..Default::default()
23692 };
23693
23694 let mut prev_end = label.filter_range.end;
23695 label
23696 .runs
23697 .iter()
23698 .enumerate()
23699 .flat_map(move |(ix, (range, highlight_id))| {
23700 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23701 style
23702 } else {
23703 return Default::default();
23704 };
23705 let mut muted_style = style;
23706 muted_style.highlight(fade_out);
23707
23708 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23709 if range.start >= label.filter_range.end {
23710 if range.start > prev_end {
23711 runs.push((prev_end..range.start, fade_out));
23712 }
23713 runs.push((range.clone(), muted_style));
23714 } else if range.end <= label.filter_range.end {
23715 runs.push((range.clone(), style));
23716 } else {
23717 runs.push((range.start..label.filter_range.end, style));
23718 runs.push((label.filter_range.end..range.end, muted_style));
23719 }
23720 prev_end = cmp::max(prev_end, range.end);
23721
23722 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23723 runs.push((prev_end..label.text.len(), fade_out));
23724 }
23725
23726 runs
23727 })
23728}
23729
23730pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23731 let mut prev_index = 0;
23732 let mut prev_codepoint: Option<char> = None;
23733 text.char_indices()
23734 .chain([(text.len(), '\0')])
23735 .filter_map(move |(index, codepoint)| {
23736 let prev_codepoint = prev_codepoint.replace(codepoint)?;
23737 let is_boundary = index == text.len()
23738 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
23739 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
23740 if is_boundary {
23741 let chunk = &text[prev_index..index];
23742 prev_index = index;
23743 Some(chunk)
23744 } else {
23745 None
23746 }
23747 })
23748}
23749
23750pub trait RangeToAnchorExt: Sized {
23751 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
23752
23753 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
23754 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
23755 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
23756 }
23757}
23758
23759impl<T: ToOffset> RangeToAnchorExt for Range<T> {
23760 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
23761 let start_offset = self.start.to_offset(snapshot);
23762 let end_offset = self.end.to_offset(snapshot);
23763 if start_offset == end_offset {
23764 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
23765 } else {
23766 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
23767 }
23768 }
23769}
23770
23771pub trait RowExt {
23772 fn as_f32(&self) -> f32;
23773
23774 fn next_row(&self) -> Self;
23775
23776 fn previous_row(&self) -> Self;
23777
23778 fn minus(&self, other: Self) -> u32;
23779}
23780
23781impl RowExt for DisplayRow {
23782 fn as_f32(&self) -> f32 {
23783 self.0 as f32
23784 }
23785
23786 fn next_row(&self) -> Self {
23787 Self(self.0 + 1)
23788 }
23789
23790 fn previous_row(&self) -> Self {
23791 Self(self.0.saturating_sub(1))
23792 }
23793
23794 fn minus(&self, other: Self) -> u32 {
23795 self.0 - other.0
23796 }
23797}
23798
23799impl RowExt for MultiBufferRow {
23800 fn as_f32(&self) -> f32 {
23801 self.0 as f32
23802 }
23803
23804 fn next_row(&self) -> Self {
23805 Self(self.0 + 1)
23806 }
23807
23808 fn previous_row(&self) -> Self {
23809 Self(self.0.saturating_sub(1))
23810 }
23811
23812 fn minus(&self, other: Self) -> u32 {
23813 self.0 - other.0
23814 }
23815}
23816
23817trait RowRangeExt {
23818 type Row;
23819
23820 fn len(&self) -> usize;
23821
23822 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
23823}
23824
23825impl RowRangeExt for Range<MultiBufferRow> {
23826 type Row = MultiBufferRow;
23827
23828 fn len(&self) -> usize {
23829 (self.end.0 - self.start.0) as usize
23830 }
23831
23832 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
23833 (self.start.0..self.end.0).map(MultiBufferRow)
23834 }
23835}
23836
23837impl RowRangeExt for Range<DisplayRow> {
23838 type Row = DisplayRow;
23839
23840 fn len(&self) -> usize {
23841 (self.end.0 - self.start.0) as usize
23842 }
23843
23844 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23845 (self.start.0..self.end.0).map(DisplayRow)
23846 }
23847}
23848
23849/// If select range has more than one line, we
23850/// just point the cursor to range.start.
23851fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23852 if range.start.row == range.end.row {
23853 range
23854 } else {
23855 range.start..range.start
23856 }
23857}
23858pub struct KillRing(ClipboardItem);
23859impl Global for KillRing {}
23860
23861const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23862
23863enum BreakpointPromptEditAction {
23864 Log,
23865 Condition,
23866 HitCondition,
23867}
23868
23869struct BreakpointPromptEditor {
23870 pub(crate) prompt: Entity<Editor>,
23871 editor: WeakEntity<Editor>,
23872 breakpoint_anchor: Anchor,
23873 breakpoint: Breakpoint,
23874 edit_action: BreakpointPromptEditAction,
23875 block_ids: HashSet<CustomBlockId>,
23876 editor_margins: Arc<Mutex<EditorMargins>>,
23877 _subscriptions: Vec<Subscription>,
23878}
23879
23880impl BreakpointPromptEditor {
23881 const MAX_LINES: u8 = 4;
23882
23883 fn new(
23884 editor: WeakEntity<Editor>,
23885 breakpoint_anchor: Anchor,
23886 breakpoint: Breakpoint,
23887 edit_action: BreakpointPromptEditAction,
23888 window: &mut Window,
23889 cx: &mut Context<Self>,
23890 ) -> Self {
23891 let base_text = match edit_action {
23892 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23893 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23894 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23895 }
23896 .map(|msg| msg.to_string())
23897 .unwrap_or_default();
23898
23899 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23900 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23901
23902 let prompt = cx.new(|cx| {
23903 let mut prompt = Editor::new(
23904 EditorMode::AutoHeight {
23905 min_lines: 1,
23906 max_lines: Some(Self::MAX_LINES as usize),
23907 },
23908 buffer,
23909 None,
23910 window,
23911 cx,
23912 );
23913 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23914 prompt.set_show_cursor_when_unfocused(false, cx);
23915 prompt.set_placeholder_text(
23916 match edit_action {
23917 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23918 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23919 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23920 },
23921 cx,
23922 );
23923
23924 prompt
23925 });
23926
23927 Self {
23928 prompt,
23929 editor,
23930 breakpoint_anchor,
23931 breakpoint,
23932 edit_action,
23933 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23934 block_ids: Default::default(),
23935 _subscriptions: vec![],
23936 }
23937 }
23938
23939 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23940 self.block_ids.extend(block_ids)
23941 }
23942
23943 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23944 if let Some(editor) = self.editor.upgrade() {
23945 let message = self
23946 .prompt
23947 .read(cx)
23948 .buffer
23949 .read(cx)
23950 .as_singleton()
23951 .expect("A multi buffer in breakpoint prompt isn't possible")
23952 .read(cx)
23953 .as_rope()
23954 .to_string();
23955
23956 editor.update(cx, |editor, cx| {
23957 editor.edit_breakpoint_at_anchor(
23958 self.breakpoint_anchor,
23959 self.breakpoint.clone(),
23960 match self.edit_action {
23961 BreakpointPromptEditAction::Log => {
23962 BreakpointEditAction::EditLogMessage(message.into())
23963 }
23964 BreakpointPromptEditAction::Condition => {
23965 BreakpointEditAction::EditCondition(message.into())
23966 }
23967 BreakpointPromptEditAction::HitCondition => {
23968 BreakpointEditAction::EditHitCondition(message.into())
23969 }
23970 },
23971 cx,
23972 );
23973
23974 editor.remove_blocks(self.block_ids.clone(), None, cx);
23975 cx.focus_self(window);
23976 });
23977 }
23978 }
23979
23980 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23981 self.editor
23982 .update(cx, |editor, cx| {
23983 editor.remove_blocks(self.block_ids.clone(), None, cx);
23984 window.focus(&editor.focus_handle);
23985 })
23986 .log_err();
23987 }
23988
23989 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23990 let settings = ThemeSettings::get_global(cx);
23991 let text_style = TextStyle {
23992 color: if self.prompt.read(cx).read_only(cx) {
23993 cx.theme().colors().text_disabled
23994 } else {
23995 cx.theme().colors().text
23996 },
23997 font_family: settings.buffer_font.family.clone(),
23998 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23999 font_size: settings.buffer_font_size(cx).into(),
24000 font_weight: settings.buffer_font.weight,
24001 line_height: relative(settings.buffer_line_height.value()),
24002 ..Default::default()
24003 };
24004 EditorElement::new(
24005 &self.prompt,
24006 EditorStyle {
24007 background: cx.theme().colors().editor_background,
24008 local_player: cx.theme().players().local(),
24009 text: text_style,
24010 ..Default::default()
24011 },
24012 )
24013 }
24014}
24015
24016impl Render for BreakpointPromptEditor {
24017 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24018 let editor_margins = *self.editor_margins.lock();
24019 let gutter_dimensions = editor_margins.gutter;
24020 h_flex()
24021 .key_context("Editor")
24022 .bg(cx.theme().colors().editor_background)
24023 .border_y_1()
24024 .border_color(cx.theme().status().info_border)
24025 .size_full()
24026 .py(window.line_height() / 2.5)
24027 .on_action(cx.listener(Self::confirm))
24028 .on_action(cx.listener(Self::cancel))
24029 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24030 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24031 }
24032}
24033
24034impl Focusable for BreakpointPromptEditor {
24035 fn focus_handle(&self, cx: &App) -> FocusHandle {
24036 self.prompt.focus_handle(cx)
24037 }
24038}
24039
24040fn all_edits_insertions_or_deletions(
24041 edits: &Vec<(Range<Anchor>, String)>,
24042 snapshot: &MultiBufferSnapshot,
24043) -> bool {
24044 let mut all_insertions = true;
24045 let mut all_deletions = true;
24046
24047 for (range, new_text) in edits.iter() {
24048 let range_is_empty = range.to_offset(snapshot).is_empty();
24049 let text_is_empty = new_text.is_empty();
24050
24051 if range_is_empty != text_is_empty {
24052 if range_is_empty {
24053 all_deletions = false;
24054 } else {
24055 all_insertions = false;
24056 }
24057 } else {
24058 return false;
24059 }
24060
24061 if !all_insertions && !all_deletions {
24062 return false;
24063 }
24064 }
24065 all_insertions || all_deletions
24066}
24067
24068struct MissingEditPredictionKeybindingTooltip;
24069
24070impl Render for MissingEditPredictionKeybindingTooltip {
24071 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24072 ui::tooltip_container(window, cx, |container, _, cx| {
24073 container
24074 .flex_shrink_0()
24075 .max_w_80()
24076 .min_h(rems_from_px(124.))
24077 .justify_between()
24078 .child(
24079 v_flex()
24080 .flex_1()
24081 .text_ui_sm(cx)
24082 .child(Label::new("Conflict with Accept Keybinding"))
24083 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24084 )
24085 .child(
24086 h_flex()
24087 .pb_1()
24088 .gap_1()
24089 .items_end()
24090 .w_full()
24091 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24092 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24093 }))
24094 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24095 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24096 })),
24097 )
24098 })
24099 }
24100}
24101
24102#[derive(Debug, Clone, Copy, PartialEq)]
24103pub struct LineHighlight {
24104 pub background: Background,
24105 pub border: Option<gpui::Hsla>,
24106 pub include_gutter: bool,
24107 pub type_id: Option<TypeId>,
24108}
24109
24110struct LineManipulationResult {
24111 pub new_text: String,
24112 pub line_count_before: usize,
24113 pub line_count_after: usize,
24114}
24115
24116fn render_diff_hunk_controls(
24117 row: u32,
24118 status: &DiffHunkStatus,
24119 hunk_range: Range<Anchor>,
24120 is_created_file: bool,
24121 line_height: Pixels,
24122 editor: &Entity<Editor>,
24123 _window: &mut Window,
24124 cx: &mut App,
24125) -> AnyElement {
24126 h_flex()
24127 .h(line_height)
24128 .mr_1()
24129 .gap_1()
24130 .px_0p5()
24131 .pb_1()
24132 .border_x_1()
24133 .border_b_1()
24134 .border_color(cx.theme().colors().border_variant)
24135 .rounded_b_lg()
24136 .bg(cx.theme().colors().editor_background)
24137 .gap_1()
24138 .block_mouse_except_scroll()
24139 .shadow_md()
24140 .child(if status.has_secondary_hunk() {
24141 Button::new(("stage", row as u64), "Stage")
24142 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24143 .tooltip({
24144 let focus_handle = editor.focus_handle(cx);
24145 move |window, cx| {
24146 Tooltip::for_action_in(
24147 "Stage Hunk",
24148 &::git::ToggleStaged,
24149 &focus_handle,
24150 window,
24151 cx,
24152 )
24153 }
24154 })
24155 .on_click({
24156 let editor = editor.clone();
24157 move |_event, _window, cx| {
24158 editor.update(cx, |editor, cx| {
24159 editor.stage_or_unstage_diff_hunks(
24160 true,
24161 vec![hunk_range.start..hunk_range.start],
24162 cx,
24163 );
24164 });
24165 }
24166 })
24167 } else {
24168 Button::new(("unstage", row as u64), "Unstage")
24169 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24170 .tooltip({
24171 let focus_handle = editor.focus_handle(cx);
24172 move |window, cx| {
24173 Tooltip::for_action_in(
24174 "Unstage Hunk",
24175 &::git::ToggleStaged,
24176 &focus_handle,
24177 window,
24178 cx,
24179 )
24180 }
24181 })
24182 .on_click({
24183 let editor = editor.clone();
24184 move |_event, _window, cx| {
24185 editor.update(cx, |editor, cx| {
24186 editor.stage_or_unstage_diff_hunks(
24187 false,
24188 vec![hunk_range.start..hunk_range.start],
24189 cx,
24190 );
24191 });
24192 }
24193 })
24194 })
24195 .child(
24196 Button::new(("restore", row as u64), "Restore")
24197 .tooltip({
24198 let focus_handle = editor.focus_handle(cx);
24199 move |window, cx| {
24200 Tooltip::for_action_in(
24201 "Restore Hunk",
24202 &::git::Restore,
24203 &focus_handle,
24204 window,
24205 cx,
24206 )
24207 }
24208 })
24209 .on_click({
24210 let editor = editor.clone();
24211 move |_event, window, cx| {
24212 editor.update(cx, |editor, cx| {
24213 let snapshot = editor.snapshot(window, cx);
24214 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24215 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24216 });
24217 }
24218 })
24219 .disabled(is_created_file),
24220 )
24221 .when(
24222 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24223 |el| {
24224 el.child(
24225 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24226 .shape(IconButtonShape::Square)
24227 .icon_size(IconSize::Small)
24228 // .disabled(!has_multiple_hunks)
24229 .tooltip({
24230 let focus_handle = editor.focus_handle(cx);
24231 move |window, cx| {
24232 Tooltip::for_action_in(
24233 "Next Hunk",
24234 &GoToHunk,
24235 &focus_handle,
24236 window,
24237 cx,
24238 )
24239 }
24240 })
24241 .on_click({
24242 let editor = editor.clone();
24243 move |_event, window, cx| {
24244 editor.update(cx, |editor, cx| {
24245 let snapshot = editor.snapshot(window, cx);
24246 let position =
24247 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24248 editor.go_to_hunk_before_or_after_position(
24249 &snapshot,
24250 position,
24251 Direction::Next,
24252 window,
24253 cx,
24254 );
24255 editor.expand_selected_diff_hunks(cx);
24256 });
24257 }
24258 }),
24259 )
24260 .child(
24261 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24262 .shape(IconButtonShape::Square)
24263 .icon_size(IconSize::Small)
24264 // .disabled(!has_multiple_hunks)
24265 .tooltip({
24266 let focus_handle = editor.focus_handle(cx);
24267 move |window, cx| {
24268 Tooltip::for_action_in(
24269 "Previous Hunk",
24270 &GoToPreviousHunk,
24271 &focus_handle,
24272 window,
24273 cx,
24274 )
24275 }
24276 })
24277 .on_click({
24278 let editor = editor.clone();
24279 move |_event, window, cx| {
24280 editor.update(cx, |editor, cx| {
24281 let snapshot = editor.snapshot(window, cx);
24282 let point =
24283 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24284 editor.go_to_hunk_before_or_after_position(
24285 &snapshot,
24286 point,
24287 Direction::Prev,
24288 window,
24289 cx,
24290 );
24291 editor.expand_selected_diff_hunks(cx);
24292 });
24293 }
24294 }),
24295 )
24296 },
24297 )
24298 .into_any_element()
24299}
24300
24301pub fn multibuffer_context_lines(cx: &App) -> u32 {
24302 EditorSettings::try_get(cx)
24303 .map(|settings| settings.excerpt_context_lines)
24304 .unwrap_or(2)
24305 .clamp(1, 32)
24306}